fix: button validate in detail media tracking

This commit is contained in:
Sabda Yagra 2026-01-09 10:22:43 +07:00
parent 280ea508e9
commit 933cdb1100
6 changed files with 195 additions and 36 deletions

View File

@ -32,6 +32,7 @@ import {
} from "@/components/ui/dialog";
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
import { validateMediaLink } from "@/service/media-tracking/media-tracking";
import toast from "react-hot-toast";
const columns: ColumnDef<any>[] = [
{
@ -82,7 +83,7 @@ const columns: ColumnDef<any>[] = [
);
},
},
{
{
id: "validation",
header: "Validasi",
cell: ({ row, table }) => {
@ -96,13 +97,27 @@ const columns: ColumnDef<any>[] = [
};
const handleValid = async () => {
await validateMediaLink(original.id, true);
updateRow({ isValid: true });
try {
await validateMediaLink(original.id, true);
updateRow({
isValid: true,
});
} catch (err: any) {
toast.error(err.message);
}
};
const handleInvalid = async () => {
await validateMediaLink(original.id, false);
updateRow({ isValid: false, link: undefined });
try {
await validateMediaLink(original.id, false);
updateRow({
isValid: false,
});
} catch (err: any) {
toast.error(err.message);
}
};
if (!link) {
@ -111,7 +126,11 @@ const columns: ColumnDef<any>[] = [
if (isValid === true) {
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
</Button>
);
@ -120,14 +139,10 @@ const columns: ColumnDef<any>[] = [
return (
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={handleValid}>
Valid
Relevan
</Button>
<Button
size="sm"
variant="outline"
onClick={handleInvalid}
>
Tidak Valid
<Button size="sm" variant="outline" onClick={handleInvalid}>
Tidak Relevan
</Button>
</div>
);

View File

@ -116,6 +116,15 @@ const NewsDetailTable = () => {
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
meta: {
updateData: (rowIndex: number, value: Partial<any>) => {
setDataTable((old) =>
old.map((row, index) =>
index === rowIndex ? { ...row, ...value } : row
)
);
},
},
state: {
sorting,
columnFilters,
@ -253,7 +262,7 @@ const NewsDetailTable = () => {
totalData={totalData}
totalPage={totalPage}
/>
</div>
</div>
);
};

View File

@ -1,7 +1,15 @@
import * as React from "react";
import { ColumnDef } from "@tanstack/react-table";
import { DownloadIcon, Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { exportMediaTrackingToExcel } from "@/utils/export-media-tracking";
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 {
DropdownMenu,
@ -44,18 +52,39 @@ const columns: ColumnDef<any>[] = [
header: "Judul",
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",
header: () => <div className="text-center w-full">Jumlah Amplifikasi</div>,
cell: ({ row }) => {
const value = row.getValue("resultTotal") as number | string | null;
const totalRaw = row.getValue("resultTotal") as number | string | null;
const finalValue =
value === null || value === undefined || value === ""
const total =
totalRaw === null || totalRaw === undefined || totalRaw === ""
? 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()}
</DropdownMenuItem>
</Link>
<DropdownMenuItem className="p-2 border-b cursor-pointer text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
<DownloadIcon className="w-4 h-4 me-1.5"/>
<DropdownMenuItem
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>
</DropdownMenuItem>
</DropdownMenuContent>

View File

@ -1,3 +1,4 @@
import api from "@/src/lib/api";
import {
httpGetInterceptor,
httpPostInterceptor,
@ -62,25 +63,31 @@ export async function listDataTracking(
);
}
export async function listDataAllNonPagination(search: string) {
return await httpGetInterceptor(
`media/public/list?enablePage=0&sort=desc&title=${search || ""}`
);
}
// service/media-validation.ts
export const validateMediaLink = async (
id: string | number,
isValid: boolean
resultId: number,
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) => {
setTimeout(() => {
resolve({
success: true,
isValid,
});
}, 600);
});
};
return res.data;
} catch (error: any) {
throw new Error(
error?.response?.data?.messages?.[0] ||
"Gagal memperbarui status relevansi"
);
}
};

12
src/lib/api.ts Normal file
View File

@ -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;

View File

@ -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`);
}