update application page, table.
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
||||
confirmUpload,
|
||||
getPresignedUrl,
|
||||
uploadFileToS3,
|
||||
uploadMedia,
|
||||
} from "@/service/mediaService";
|
||||
import React, { useState } from "react";
|
||||
import Image from "next/image";
|
||||
@@ -16,6 +15,8 @@ import Captions from "yet-another-react-lightbox/plugins/captions";
|
||||
import "yet-another-react-lightbox/styles.css";
|
||||
import "yet-another-react-lightbox/plugins/captions.css";
|
||||
import { createHistorianCV } from "@/service/historianService";
|
||||
import { toast } from "sonner";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
type PendingFile = {
|
||||
id: string;
|
||||
@@ -25,17 +26,20 @@ type PendingFile = {
|
||||
size: number;
|
||||
type: "image" | "document";
|
||||
extension: string;
|
||||
presigned?: any;
|
||||
presigned?: any;
|
||||
};
|
||||
|
||||
export default function RoleUpgrade() {
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [verifyType, setVerifyType] = useState<string>("OTHER");
|
||||
|
||||
const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(-1);
|
||||
|
||||
const [isPreparingFiles, setIsPreparingFiles] = useState(false);
|
||||
|
||||
const handleContentChange = (value: string) => {
|
||||
setContent(value);
|
||||
};
|
||||
@@ -83,38 +87,45 @@ export default function RoleUpgrade() {
|
||||
setPendingFiles((prev) => prev.filter((item) => item.id !== idToRemove));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
|
||||
const uploadPromises = pendingFiles.map(async (item) => {
|
||||
await uploadFileToS3(item.file, item.presigned);
|
||||
const confirmRes = await confirmUpload(item.presigned.token_id);
|
||||
return confirmRes?.data?.id || confirmRes?.id;
|
||||
});
|
||||
const uploadedMediaIds = await Promise.all(uploadPromises);
|
||||
const uploadPromises = pendingFiles.map(async (item) => {
|
||||
await uploadFileToS3(item.file, item.presigned);
|
||||
const confirmRes = await confirmUpload(item.presigned.token_id);
|
||||
return confirmRes?.data?.id || confirmRes?.id;
|
||||
});
|
||||
const uploadedMediaIds = await Promise.all(uploadPromises);
|
||||
|
||||
const payload = {
|
||||
content: content,
|
||||
media_ids: uploadedMediaIds,
|
||||
verify_type: "ID_CARD"
|
||||
};
|
||||
const payload = {
|
||||
content: content,
|
||||
media_ids: uploadedMediaIds,
|
||||
verify_type: verifyType,
|
||||
};
|
||||
|
||||
console.log("Payload chuẩn bị gửi (JSON):", payload);
|
||||
console.log("Payload chuẩn bị gửi (JSON):", payload);
|
||||
|
||||
const cvResponse = await createHistorianCV(payload);
|
||||
console.log("Response từ API:", cvResponse);
|
||||
const cvResponse = await createHistorianCV(payload);
|
||||
console.log("Response từ API:", cvResponse);
|
||||
|
||||
alert("Gửi thành công!");
|
||||
setContent("");
|
||||
setPendingFiles([]);
|
||||
} catch (error) {
|
||||
console.error("Lỗi:", error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
Swal.fire({
|
||||
title: "Gửi yêu cầu thành công!",
|
||||
text: "Yêu cầu nâng cấp đã được gửi đi. Chúng tôi sẽ xem xét và phản hồi sớm nhất có thể. Vui lòng kiểm tra Email khi có thông báo mới.",
|
||||
icon: "success",
|
||||
confirmButtonText: "OK",
|
||||
});
|
||||
setContent("");
|
||||
setPendingFiles([]);
|
||||
setVerifyType("ID_CARD");
|
||||
} catch (error) {
|
||||
console.error("Lỗi:", error);
|
||||
toast.error("Có lỗi xảy ra khi gửi yêu cầu. Vui lòng kiểm tra lại!");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const imageFiles = pendingFiles.filter((f) => f.type === "image");
|
||||
const slides = imageFiles.map((item) => ({
|
||||
@@ -149,6 +160,28 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
<div className="flex flex-col gap-6">
|
||||
<RichTextEditor value={content} onChange={handleContentChange} />
|
||||
|
||||
<div className="p-5 bg-white border border-gray-200 rounded-2xl dark:border-gray-800 dark:bg-gray-900">
|
||||
<label className="block mb-3 text-sm font-semibold text-gray-700 dark:text-white">
|
||||
Loại hồ sơ xác minh <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={verifyType}
|
||||
onChange={(e) => setVerifyType(e.target.value)}
|
||||
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:bg-gray-800 dark:border-gray-700 transition-all cursor-pointer text-gray-700 dark:text-gray-200"
|
||||
>
|
||||
<option value="OTHER">Tài liệu khác (Other)</option>
|
||||
<option value="ID_CARD">Thẻ nhận dạng nhà nghiên cứu (ID Card)</option>
|
||||
<option value="EDUCATION">Bằng cấp giáo dục (Education)</option>
|
||||
<option value="EXPERT">Chứng nhận chuyên gia (Expert)</option>
|
||||
</select>
|
||||
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
* Vui lòng chọn loại tài liệu tương ứng với các tệp bạn đính kèm bên dưới.
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-red-400 dark:text-gray-400">
|
||||
* Nếu bạn đính kèm nhiều loại tài liệu, vui lòng chọn mục " Tài liệu khác (Other) ".
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-5 bg-white border border-gray-200 rounded-2xl dark:border-gray-800 dark:bg-gray-900">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
@@ -182,7 +215,7 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
</div>
|
||||
|
||||
{pendingFiles.length > 0 && (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-5 gap-4 pt-4 border-t border-gray-100 dark:border-gray-800 mt-4">
|
||||
<div className="grid grid-cols-2 gap-4 pt-4 mt-4 border-t border-gray-100 sm:grid-cols-4 md:grid-cols-5 dark:border-gray-800">
|
||||
{pendingFiles.map((item) => {
|
||||
const docStyle = getDocumentStyle(item.extension);
|
||||
|
||||
@@ -190,14 +223,14 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
<div
|
||||
key={item.id}
|
||||
onClick={() => handleItemClick(item)}
|
||||
className="group relative aspect-square cursor-pointer overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 hover:border-blue-400 transition-colors"
|
||||
className="relative overflow-hidden transition-colors border border-gray-200 cursor-pointer group aspect-square rounded-xl dark:border-gray-700 bg-gray-50 dark:bg-gray-800 hover:border-blue-400"
|
||||
>
|
||||
{item.type === "image" ? (
|
||||
<Image
|
||||
src={item.previewUrl}
|
||||
alt={item.name}
|
||||
fill
|
||||
className="object-cover group-hover:scale-110 transition-transform"
|
||||
className="object-cover transition-transform group-hover:scale-110"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -210,7 +243,7 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 1.5L18.5 9H13V3.5zM6 20V4h5v7h7v9H6z" />
|
||||
</svg>
|
||||
<span className="text-xs font-medium text-center text-gray-700 dark:text-gray-300 break-all line-clamp-2">
|
||||
<span className="text-xs font-medium text-center text-gray-700 break-all line-clamp-2 dark:text-gray-300">
|
||||
{item.name}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-500 mt-1 uppercase font-bold">
|
||||
@@ -221,7 +254,7 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
|
||||
<button
|
||||
onClick={(e) => removePendingFile(item.id, e)}
|
||||
className="absolute top-1 right-1 bg-red-500 text-white rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity z-10 hover:bg-red-600"
|
||||
className="absolute z-10 p-1 text-white transition-opacity bg-red-500 rounded-full opacity-0 top-1 right-1 group-hover:opacity-100 hover:bg-red-600"
|
||||
>
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
@@ -239,7 +272,7 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
</button>
|
||||
|
||||
{item.type === "image" && (
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none">
|
||||
<div className="absolute inset-0 flex items-center justify-center transition-opacity opacity-0 pointer-events-none bg-black/40 group-hover:opacity-100">
|
||||
<svg
|
||||
className="w-6 h-6 text-white"
|
||||
fill="none"
|
||||
@@ -266,7 +299,7 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting || isPreparingFiles}
|
||||
className={`w-full py-4 text-white font-bold rounded-2xl shadow-lg transition-all transform flex justify-center items-center gap-2
|
||||
${isSubmitting ? "bg-gray-400 cursor-not-allowed" : "bg-blue-600 hover:bg-blue-700 active:scale-[0.98]"}`}
|
||||
${isSubmitting || isPreparingFiles ? "bg-gray-400 cursor-not-allowed" : "bg-blue-600 hover:bg-blue-700 active:scale-[0.98]"}`}
|
||||
>
|
||||
{isSubmitting ? "Đang tải dữ liệu lên..." : "Gửi yêu cầu nâng cấp"}
|
||||
</button>
|
||||
@@ -287,4 +320,4 @@ const handleSubmit = async (e: React.FormEvent) => {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user