fix: button validate in detail media tracking
This commit is contained in:
parent
280ea508e9
commit
933cdb1100
|
|
@ -32,6 +32,7 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
import { validateMediaLink } from "@/service/media-tracking/media-tracking";
|
import { validateMediaLink } from "@/service/media-tracking/media-tracking";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -82,7 +83,7 @@ const columns: ColumnDef<any>[] = [
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "validation",
|
id: "validation",
|
||||||
header: "Validasi",
|
header: "Validasi",
|
||||||
cell: ({ row, table }) => {
|
cell: ({ row, table }) => {
|
||||||
|
|
@ -96,13 +97,27 @@ const columns: ColumnDef<any>[] = [
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleValid = async () => {
|
const handleValid = async () => {
|
||||||
await validateMediaLink(original.id, true);
|
try {
|
||||||
updateRow({ isValid: true });
|
await validateMediaLink(original.id, true);
|
||||||
|
|
||||||
|
updateRow({
|
||||||
|
isValid: true,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.error(err.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInvalid = async () => {
|
const handleInvalid = async () => {
|
||||||
await validateMediaLink(original.id, false);
|
try {
|
||||||
updateRow({ isValid: false, link: undefined });
|
await validateMediaLink(original.id, false);
|
||||||
|
|
||||||
|
updateRow({
|
||||||
|
isValid: false,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.error(err.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!link) {
|
if (!link) {
|
||||||
|
|
@ -111,7 +126,11 @@ const columns: ColumnDef<any>[] = [
|
||||||
|
|
||||||
if (isValid === true) {
|
if (isValid === true) {
|
||||||
return (
|
return (
|
||||||
<Button size="sm" className="bg-green-600 hover:bg-green-700">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-green-600 hover:bg-green-700"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
Valid
|
Valid
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
@ -120,14 +139,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button size="sm" variant="outline" onClick={handleValid}>
|
<Button size="sm" variant="outline" onClick={handleValid}>
|
||||||
Valid
|
Relevan
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button size="sm" variant="outline" onClick={handleInvalid}>
|
||||||
size="sm"
|
Tidak Relevan
|
||||||
variant="outline"
|
|
||||||
onClick={handleInvalid}
|
|
||||||
>
|
|
||||||
Tidak Valid
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,15 @@ const NewsDetailTable = () => {
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
onRowSelectionChange: setRowSelection,
|
onRowSelectionChange: setRowSelection,
|
||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
|
meta: {
|
||||||
|
updateData: (rowIndex: number, value: Partial<any>) => {
|
||||||
|
setDataTable((old) =>
|
||||||
|
old.map((row, index) =>
|
||||||
|
index === rowIndex ? { ...row, ...value } : row
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
|
|
@ -253,7 +262,7 @@ const NewsDetailTable = () => {
|
||||||
totalData={totalData}
|
totalData={totalData}
|
||||||
totalPage={totalPage}
|
totalPage={totalPage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { exportMediaTrackingToExcel } from "@/utils/export-media-tracking";
|
||||||
import { DownloadIcon, Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
import { loading, close } from "@/config/swal";
|
||||||
|
import { error } from "@/lib/swal";
|
||||||
|
import {
|
||||||
|
DownloadIcon,
|
||||||
|
Eye,
|
||||||
|
MoreVertical,
|
||||||
|
SquarePen,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -44,18 +52,39 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Judul",
|
header: "Judul",
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// accessorKey: "resultTotal",
|
||||||
|
// header: () => <div className="text-center w-full">Jumlah Amplifikasi</div>,
|
||||||
|
// cell: ({ row }) => {
|
||||||
|
// const value = row.getValue("resultTotal") as number | string | null;
|
||||||
|
|
||||||
|
// const finalValue =
|
||||||
|
// value === null || value === undefined || value === ""
|
||||||
|
// ? 0
|
||||||
|
// : Number(value);
|
||||||
|
|
||||||
|
// return <div className="text-center w-full">{finalValue}</div>;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
accessorKey: "resultTotal",
|
accessorKey: "resultTotal",
|
||||||
header: () => <div className="text-center w-full">Jumlah Amplifikasi</div>,
|
header: () => <div className="text-center w-full">Jumlah Amplifikasi</div>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const value = row.getValue("resultTotal") as number | string | null;
|
const totalRaw = row.getValue("resultTotal") as number | string | null;
|
||||||
|
|
||||||
const finalValue =
|
const total =
|
||||||
value === null || value === undefined || value === ""
|
totalRaw === null || totalRaw === undefined || totalRaw === ""
|
||||||
? 0
|
? 0
|
||||||
: Number(value);
|
: Number(totalRaw);
|
||||||
|
|
||||||
return <div className="text-center w-full">{finalValue}</div>;
|
const invalidTotal = 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-center w-full font-medium">
|
||||||
|
{total}
|
||||||
|
<span className="text-muted-foreground">/{invalidTotal}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -148,8 +177,22 @@ const columns: ColumnDef<any>[] = [
|
||||||
row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
|
row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b cursor-pointer text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
|
<DropdownMenuItem
|
||||||
<DownloadIcon className="w-4 h-4 me-1.5"/>
|
className="p-2 border-b cursor-pointer text-default-700 group rounded-none focus:bg-default focus:text-primary-foreground "
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
await exportMediaTrackingToExcel({
|
||||||
|
mediaTrackingId: row.original.id,
|
||||||
|
});
|
||||||
|
close();
|
||||||
|
} catch (e: any) {
|
||||||
|
close();
|
||||||
|
error(e.message || "Gagal export data");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadIcon className="w-4 h-4 me-1.5" />
|
||||||
<p>Download</p>
|
<p>Download</p>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import api from "@/src/lib/api";
|
||||||
import {
|
import {
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
|
|
@ -62,25 +63,31 @@ export async function listDataTracking(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function listDataAllNonPagination(search: string) {
|
export async function listDataAllNonPagination(search: string) {
|
||||||
return await httpGetInterceptor(
|
return await httpGetInterceptor(
|
||||||
`media/public/list?enablePage=0&sort=desc&title=${search || ""}`
|
`media/public/list?enablePage=0&sort=desc&title=${search || ""}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// service/media-validation.ts
|
|
||||||
export const validateMediaLink = async (
|
export const validateMediaLink = async (
|
||||||
id: string | number,
|
resultId: number,
|
||||||
isValid: boolean
|
isRelevant: boolean
|
||||||
) => {
|
) => {
|
||||||
console.log("API DUMMY:", { id, isValid });
|
try {
|
||||||
|
const res = await api.put(
|
||||||
|
"/media/tracking/monitoring/results/relevant",
|
||||||
|
{
|
||||||
|
resultId,
|
||||||
|
isRelevant,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return res.data;
|
||||||
setTimeout(() => {
|
} catch (error: any) {
|
||||||
resolve({
|
throw new Error(
|
||||||
success: true,
|
error?.response?.data?.messages?.[0] ||
|
||||||
isValid,
|
"Gagal memperbarui status relevansi"
|
||||||
});
|
);
|
||||||
}, 600);
|
}
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: "https://mediahub.polri.go.id/api/v2",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default api;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
import { getMediaTrackingResult } from "@/service/media-tracking/media-tracking";
|
||||||
|
import { saveAs } from "file-saver";
|
||||||
|
|
||||||
|
type ExportParams = {
|
||||||
|
mediaTrackingId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function exportMediaTrackingToExcel({
|
||||||
|
mediaTrackingId,
|
||||||
|
}: ExportParams) {
|
||||||
|
let page = 0;
|
||||||
|
let totalPages = 1;
|
||||||
|
const allData: any[] = [];
|
||||||
|
|
||||||
|
while (page < totalPages) {
|
||||||
|
const res = await getMediaTrackingResult({
|
||||||
|
id: mediaTrackingId,
|
||||||
|
page,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = res?.data?.data;
|
||||||
|
if (!data) break;
|
||||||
|
|
||||||
|
totalPages = data.totalPages;
|
||||||
|
allData.push(...data.content);
|
||||||
|
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allData.length === 0) {
|
||||||
|
throw new Error("Tidak ada data untuk di-export");
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractDomain(url?: string): string {
|
||||||
|
if (!url) return "-";
|
||||||
|
try {
|
||||||
|
return new URL(url).hostname;
|
||||||
|
} catch {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = allData.map((item, index) => ({
|
||||||
|
No: index + 1,
|
||||||
|
"Media Online": extractDomain(item.link),
|
||||||
|
"Judul Berita": item.title ?? "-",
|
||||||
|
"Link Berita": item.link ?? "-",
|
||||||
|
Validasi: item.validationStatus ?? "-",
|
||||||
|
View: item.viewCount ?? 0,
|
||||||
|
Share: item.shareCount ?? 0,
|
||||||
|
Komentar: item.commentCount ?? 0,
|
||||||
|
Tanggal: item.createdAt
|
||||||
|
? new Date(item.createdAt).toLocaleString("id-ID")
|
||||||
|
: "-",
|
||||||
|
}));
|
||||||
|
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Media Tracking");
|
||||||
|
|
||||||
|
const excelBuffer = XLSX.write(workbook, {
|
||||||
|
bookType: "xlsx",
|
||||||
|
type: "array",
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([excelBuffer], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
saveAs(blob, `media-tracking-${mediaTrackingId}.xlsx`);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue