Commit 2c208555 authored by ThinhNC's avatar ThinhNC

Merge develop into staging: comprehensive bilingual support for exports modal and toasts

parents 433d5d7a 276ff0f9
Pipeline #53762 passed with stage
in 1 minute and 23 seconds
......@@ -276,7 +276,7 @@ export default function ExportsPage() {
) : isError ? (
<div className="p-8 text-center rounded-3xl border border-red-500/20 bg-red-500/5 space-y-3">
<p className="text-sm text-red-500 font-medium">
Đã có lỗi xảy ra khi tải danh sách tệp xuất.
{t.exports.loadError}
</p>
<Button
variant="outline"
......
......@@ -52,45 +52,41 @@ export function isJobExportable(job?: {
);
}
const EXPORT_FORMATS: {
interface FormatMeta {
type: ExportType;
label: string;
desc: string;
key: "csv" | "json" | "xlsx" | "markdown" | "zip";
icon: React.ElementType;
badgeColor: string;
}[] = [
}
const EXPORT_FORMAT_OPTIONS: FormatMeta[] = [
{
type: "CSV",
label: "CSV (Comma-Separated)",
desc: "Bảng tính chuẩn, phân tích dữ liệu nhanh",
key: "csv",
icon: FileText,
badgeColor: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20",
},
{
type: "JSON",
label: "JSON (Structured)",
desc: "Cấu trúc lồng ghép, tích hợp API & lập trình",
key: "json",
icon: FileJson,
badgeColor: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
},
{
type: "XLSX",
label: "Excel Spreadsheet (XLSX)",
desc: "Định dạng bảng Microsoft Excel chuyên nghiệp",
key: "xlsx",
icon: FileSpreadsheet,
badgeColor: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
},
{
type: "MARKDOWN",
label: "Markdown (.md)",
desc: "Tài liệu văn bản sạch cho RAG và mô hình LLM",
key: "markdown",
icon: FileCode,
badgeColor: "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20",
},
{
type: "ZIP",
label: "ZIP Archive",
desc: "Gói nén toàn bộ tài nguyên hình ảnh và tệp",
key: "zip",
icon: Archive,
badgeColor: "bg-slate-500/10 text-slate-600 dark:text-slate-400 border-slate-500/20",
},
......@@ -221,7 +217,7 @@ export function CreateExportModal({
</Label>
{displayJobs.length > 0 && (
<span className="text-[11px] text-muted-foreground font-medium">
({displayJobs.length} tác vụ có thể xuất)
({displayJobs.length} {t.exports.modal.exportableTasksCount})
</span>
)}
</div>
......@@ -244,11 +240,11 @@ export function CreateExportModal({
{job.startUrl}
</p>
<span className="text-[10px] font-semibold px-2 py-0.5 rounded-full bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 shrink-0">
{job.successPages ?? 0} trang
{job.successPages ?? 0} {t.exports.modal.pages}
</span>
</div>
<p className="text-[10px] text-muted-foreground font-mono truncate pt-0.5">
ID: {job.id.slice(0, 16)}...{job.status ? ` • ${job.status === "COMPLETED" ? "Hoàn tất" : job.status}` : ""}{job.createdAt ? ` • ${formatDate(job.createdAt, locale)}` : ""}
ID: {job.id.slice(0, 16)}...{job.status ? ` • ${job.status === "COMPLETED" ? t.exports.status.completed : job.status}` : ""}{job.createdAt ? ` • ${formatDate(job.createdAt, locale)}` : ""}
</p>
</div>
{selectedJobId === job.id && (
......@@ -260,7 +256,7 @@ export function CreateExportModal({
) : (
<div className="rounded-2xl border border-amber-500/30 bg-amber-500/10 p-3.5 text-xs text-amber-700 dark:text-amber-400 flex items-center gap-2.5">
<AlertCircle className="h-4 w-4 shrink-0 text-amber-500" />
<span>Không có tác vụ cào nào có trang thu thập thành công để xuất dữ liệu.</span>
<span>{t.exports.modal.emptyExportableJobs}</span>
</div>
)}
</div>
......@@ -271,9 +267,10 @@ export function CreateExportModal({
{t.exports.modal.selectFormat}
</Label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{EXPORT_FORMATS.map((fmt) => {
{EXPORT_FORMAT_OPTIONS.map((fmt) => {
const Icon = fmt.icon;
const isSelected = selectedFormat === fmt.type;
const formatTrans = t.exports.modal.formats[fmt.key];
return (
<button
key={fmt.type}
......@@ -291,12 +288,12 @@ export function CreateExportModal({
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-foreground">
{fmt.type}
{formatTrans?.label || fmt.type}
</span>
{isSelected && <Check className="h-3.5 w-3.5 text-emerald-500" />}
</div>
<p className="text-[10px] text-muted-foreground line-clamp-1 mt-0.5">
{fmt.desc}
{formatTrans?.desc}
</p>
</div>
</button>
......@@ -308,12 +305,12 @@ export function CreateExportModal({
{/* Custom File Name (Optional) */}
<div className="space-y-1 pt-1">
<Label className="text-[11px] text-muted-foreground">
Tên tệp tùy chỉnh (Tùy chọn)
{t.exports.modal.customFileName}
</Label>
<Input
value={customFileName}
onChange={(e) => setCustomFileName(e.target.value)}
placeholder="crawl_dataset_custom"
placeholder={t.exports.modal.customFileNamePlaceholder}
className="rounded-2xl text-xs"
/>
</div>
......
......@@ -5,6 +5,8 @@ import { crawlExportService } from "@/services/crawl-export.service";
import { CrawlExportQueryDto, ExportType } from "@/types/export";
import { toast } from "sonner";
import { useLanguage } from "@/providers/language-provider";
export const CRAWL_EXPORTS_KEYS = {
all: ["crawl-exports"] as const,
lists: () => [...CRAWL_EXPORTS_KEYS.all, "list"] as const,
......@@ -21,44 +23,48 @@ export function useCrawlExportsList(params?: CrawlExportQueryDto) {
export function useCreateCrawlExport() {
const queryClient = useQueryClient();
const { t } = useLanguage();
return useMutation({
mutationFn: ({ jobId, exportType, fileName }: { jobId: string; exportType: ExportType; fileName?: string }) =>
crawlExportService.createExport(jobId, { exportType, fileName }),
onSuccess: () => {
toast.success("Khởi tạo yêu cầu xuất dữ liệu thành công!");
toast.success(t.exports.toasts.createSuccess);
queryClient.invalidateQueries({ queryKey: CRAWL_EXPORTS_KEYS.all });
},
onError: (error: Error) => {
toast.error(`Không thể tạo yêu cầu xuất: ${error.message}`);
toast.error(`${t.exports.toasts.createError} ${error.message}`);
},
});
}
export function useDeleteCrawlExport() {
const queryClient = useQueryClient();
const { t } = useLanguage();
return useMutation({
mutationFn: (id: string) => crawlExportService.deleteExport(id),
onSuccess: () => {
toast.success("Đã xóa tệp dữ liệu đã xuất!");
toast.success(t.exports.toasts.deleteSuccess);
queryClient.invalidateQueries({ queryKey: CRAWL_EXPORTS_KEYS.all });
},
onError: (error: Error) => {
toast.error(`Không thể xóa tệp: ${error.message}`);
toast.error(`${t.exports.toasts.deleteError} ${error.message}`);
},
});
}
export function useDownloadExport() {
const { t } = useLanguage();
return useMutation({
mutationFn: ({ exportId, fileName }: { exportId: string; fileName?: string }) =>
crawlExportService.downloadExport(exportId, fileName),
onSuccess: () => {
toast.success("Đang tải tệp về máy tính của bạn...");
toast.success(t.exports.toasts.downloadSuccess);
},
onError: (error: Error) => {
toast.error(`Tải tệp thất bại: ${error.message}`);
toast.error(`${t.exports.toasts.downloadError} ${error.message}`);
},
});
}
......@@ -622,6 +622,7 @@ export const translations = {
emptyTitle: "Chưa có tệp xuất nào",
emptyDesc: "Khởi tạo yêu cầu xuất dữ liệu từ các Crawl Job đã hoàn tất để tải về máy tính.",
retry: "Tải lại",
loadError: "Đã có lỗi xảy ra khi tải danh sách tệp xuất.",
stats: {
total: "Tổng Tệp Xuất",
totalDesc: "Tệp dữ liệu đã sinh",
......@@ -656,11 +657,46 @@ export const translations = {
desc: "Chọn tác vụ cào đã hoàn tất và định dạng tệp mong muốn.",
selectJob: "Chọn Tác Vụ Cào",
selectJobPlaceholder: "Chọn từ danh sách Job đã hoàn thành...",
exportableTasksCount: "tác vụ có thể xuất",
pages: "trang",
emptyExportableJobs: "Không có tác vụ cào nào có trang thu thập thành công để xuất dữ liệu.",
selectFormat: "Định dạng tệp đầu ra",
formats: {
csv: {
label: "CSV (Comma-Separated)",
desc: "Bảng tính chuẩn, phân tích dữ liệu nhanh",
},
json: {
label: "JSON (Structured)",
desc: "Cấu trúc lồng ghép, tích hợp API & lập trình",
},
xlsx: {
label: "Excel Spreadsheet (XLSX)",
desc: "Định dạng bảng Microsoft Excel chuyên nghiệp",
},
markdown: {
label: "Markdown (.md)",
desc: "Tài liệu văn bản sạch cho RAG và mô hình LLM",
},
zip: {
label: "ZIP Archive",
desc: "Gói nén toàn bộ tài nguyên hình ảnh và tệp",
},
},
customFileName: "Tên tệp tùy chỉnh (Tùy chọn)",
customFileNamePlaceholder: "vd: crawl_dataset_custom",
cancel: "Hủy bỏ",
submit: "Bắt Đầu Xuất File",
submitting: "Đang gửi yêu cầu...",
},
toasts: {
createSuccess: "Khởi tạo yêu cầu xuất dữ liệu thành công!",
createError: "Không thể tạo yêu cầu xuất:",
deleteSuccess: "Đã xóa tệp dữ liệu đã xuất!",
deleteError: "Không thể xóa tệp:",
downloadSuccess: "Đang tải tệp về máy tính của bạn...",
downloadError: "Không thể tải tệp:",
},
},
// Developer Settings Module
......@@ -2089,6 +2125,7 @@ export const translations = {
emptyTitle: "No export files found",
emptyDesc: "Generate export files from completed crawl jobs to download them directly.",
retry: "Reload",
loadError: "An error occurred while loading export files.",
stats: {
total: "Total Exports",
totalDesc: "Generated data files",
......@@ -2123,11 +2160,46 @@ export const translations = {
desc: "Select a completed crawl job and desired output format.",
selectJob: "Select Crawl Job",
selectJobPlaceholder: "Choose from completed crawl jobs...",
exportableTasksCount: "exportable tasks",
pages: "pages",
emptyExportableJobs: "No crawl jobs with successfully collected pages available for export.",
selectFormat: "Export Format",
formats: {
csv: {
label: "CSV (Comma-Separated)",
desc: "Standard spreadsheet, fast data analysis",
},
json: {
label: "JSON (Structured)",
desc: "Structured nested data for APIs & developers",
},
xlsx: {
label: "Excel Spreadsheet (XLSX)",
desc: "Professional Microsoft Excel workbook format",
},
markdown: {
label: "Markdown (.md)",
desc: "Clean text document for RAG & LLM pipelines",
},
zip: {
label: "ZIP Archive",
desc: "Compressed archive of full datasets and media",
},
},
customFileName: "Custom File Name (Optional)",
customFileNamePlaceholder: "e.g. crawl_dataset_custom",
cancel: "Cancel",
submit: "Start Export",
submitting: "Submitting...",
},
toasts: {
createSuccess: "Export request initiated successfully!",
createError: "Failed to create export request:",
deleteSuccess: "Export file deleted successfully!",
deleteError: "Failed to delete export file:",
downloadSuccess: "Downloading file to your computer...",
downloadError: "Failed to download file:",
},
},
// Developer Settings Module
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment