feat: new page for satker-content

This commit is contained in:
Sabda Yagra 2025-11-13 23:59:06 +07:00
parent 15a395ccd2
commit 0c5a7f86bb
16 changed files with 255 additions and 45 deletions

View File

@ -49,15 +49,47 @@ const columns: ColumnDef<any>[] = [
header: "Jumlah Amplifikasi", header: "Jumlah Amplifikasi",
cell: ({ row }) => <span>{row.getValue("link")}</span>, cell: ({ row }) => <span>{row.getValue("link")}</span>,
}, },
// {
// accessorKey: "status",
// header: "Status",
// cell: ({ row }) => <span>{row.getValue("status")}</span>,
// },
{ {
accessorKey: "status", accessorKey: "status",
header: "Status", header: () => <div className="text-center">Status</div>,
cell: ({ row }) => <span>{row.getValue("status")}</span>, cell: ({ row }) => {
const raw = row.getValue("status");
let value: string | number = "-";
if (typeof raw === "string" || typeof raw === "number") {
value = raw;
} else if (raw && typeof raw === "object") {
value = JSON.stringify(raw);
}
return <div className="text-center">{value}</div>;
},
}, },
{ {
accessorKey: "date", accessorKey: "createdAt",
header: "Tanggal Penarikan", header: () => <div className="text-center">Tanggal Penarikan</div>,
cell: ({ row }) => <span>{row.getValue("date")}</span>, cell: ({ row }) => {
const raw = row.getValue("createdAt");
if (!raw || typeof raw !== "string")
return <div className="text-center">-</div>;
const date = new Date(raw);
if (isNaN(date.getTime())) return <div className="text-center">-</div>;
const formatted = date.toLocaleDateString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
});
return <div className="text-center">{formatted}</div>;
},
}, },
{ {
id: "actions", id: "actions",
@ -78,9 +110,9 @@ const columns: ColumnDef<any>[] = [
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/media-tracking/detail/${row.original.id}`}> <Link href={`/admin/media-tracking/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
View View&nbsp;
{row.original.mediaUpload.fileType.secondaryName && {row.original.mediaUpload.fileType.secondaryName &&
row.original.mediaUpload.fileType.secondaryName.toLowerCase()} row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormAudioDetail from "@/components/form/content/audio-detail-form";
const AudioDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormAudioDetail />
</div>
</div>
);
};
export default AudioDetailPage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormAudioUpdate from "@/components/form/content/audio-update-form";
const AudioUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormAudioUpdate />
</div>
</div>
);
};
export default AudioUpdatePage;

View File

@ -83,6 +83,42 @@ const useTableColumns = () => {
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span> <span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
), ),
}, },
{
accessorKey: "fileTypeId",
header: "Jenis Konten",
cell: ({ row }) => {
const type = Number(row.getValue("fileTypeId"));
const name = row.original.fileTypeName;
const label =
type === 1
? "Image"
: type === 2
? "Audio Visual"
: type === 3
? "Text"
: type === 4
? "Audio"
: name || "-";
const color =
type === 1
? "bg-blue-100 text-blue-600"
: type === 2
? "bg-red-100 text-red-600"
: type === 3
? "bg-green-100 text-green-600"
: type === 4
? "bg-yellow-100 text-yellow-600"
: "bg-gray-200 text-gray-600";
return (
<Badge className={`${color} whitespace-nowrap rounded-full px-3`}>
{label}
</Badge>
);
},
},
{ {
accessorKey: "creatorGroupLevelName", accessorKey: "creatorGroupLevelName",
header: t("source", { defaultValue: "Source" }), header: t("source", { defaultValue: "Source" }),
@ -187,6 +223,32 @@ const useTableColumns = () => {
const router = useRouter(); const router = useRouter();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const typeId = Number(row.original.fileTypeId);
// mapping route detail
const detailRoute =
typeId === 1
? `/contributor/content/satker/image/detail/${row.original.id}`
: typeId === 2
? `/contributor/content/satker/video/detail/${row.original.id}`
: typeId === 3
? `/contributor/content/satker/text/detail/${row.original.id}`
: typeId === 4
? `/contributor/content/satker/audio/detail/${row.original.id}`
: `/contributor/content/satker/detail/${row.original.id}`;
// mapping route update
const updateRoute =
typeId === 1
? `/contributor/content/satker/image/update/${row.original.id}`
: typeId === 2
? `/contributor/content/satker/video/update/${row.original.id}`
: typeId === 3
? `/contributor/content/satker/text/update/${row.original.id}`
: typeId === 4
? `/contributor/content/satker/audio/update/${row.original.id}`
: `/contributor/content/satker/update/${row.original.id}`;
async function doDelete(id: any) { async function doDelete(id: any) {
const data = { id }; const data = { id };
const response = await deleteMedia(data); const response = await deleteMedia(data);
@ -257,16 +319,22 @@ const useTableColumns = () => {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link {/* <Link
href={`/contributor/content/satker/detail/${row.original.id}`} href={`/contributor/content/satker/detail/${row.original.id}`}
> >
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
View View
</DropdownMenuItem> </DropdownMenuItem>
</Link> */}
<Link href={detailRoute}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link> </Link>
{canEdit && ( {/* {canEdit && (
<Link <Link
href={`/contributor/content/satker/update/${row.original.id}`} href={`/contributor/content/satker/update/${row.original.id}`}
> >
@ -275,6 +343,14 @@ const useTableColumns = () => {
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
)} */}
{canEdit && (
<Link href={updateRoute}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
)} )}
<DropdownMenuItem <DropdownMenuItem

View File

@ -48,11 +48,9 @@ const TableSatker = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const [dataTable, setDataTable] = React.useState<any[]>([]); const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[] []
@ -60,15 +58,10 @@ const TableSatker = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("10"); const [showData, setShowData] = React.useState("10");
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [search, setSearch] = React.useState(""); const [search, setSearch] = React.useState("");
// gunakan ref untuk debounce search
const searchTimeoutRef = React.useRef<NodeJS.Timeout | null>(null); const searchTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
// === FILTER STATES ===
const [categories, setCategories] = React.useState<any[]>([]); const [categories, setCategories] = React.useState<any[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<number[]>( const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[] []
@ -80,7 +73,7 @@ const TableSatker = () => {
const [filterByCreator, setFilterByCreator] = React.useState(""); const [filterByCreator, setFilterByCreator] = React.useState("");
const [filterBySource, setFilterBySource] = React.useState(""); const [filterBySource, setFilterBySource] = React.useState("");
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState(""); const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
const [typeId, setTypeId] = React.useState<string>(""); // 1=image, 2=video, 3=text, 4=audio const [typeId, setTypeId] = React.useState<string>("");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");

View File

@ -0,0 +1,15 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
const ImageDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormImageDetail />
</div>
</div>
);
};
export default ImageDetailPage;

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
const ImageUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormImageUpdate />
</div>
</div>
);
};
export default ImageUpdatePage;

View File

@ -59,15 +59,15 @@ const ReactTableVideoPage = () => {
<CardTitle> <CardTitle>
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900"> <div className="flex-1 text-xl font-medium text-default-900">
{t("video", { defaultValue: "Video" })} Satker
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/content/video/create"}> {/* <Link href={"/contributor/content/video/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" /> <UploadIcon size={18} className="mr-2" />
{t("create-video", { defaultValue: "Create Video" })} {t("create-video", { defaultValue: "Create Video" })}
</Button> </Button>
</Link> </Link> */}
{/* <Button color="primary" className="text-white ml-3"> {/* <Button color="primary" className="text-white ml-3">
<UploadIcon /> <UploadIcon />
Unggah Video Dengan AI Unggah Video Dengan AI

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormTeksDetail from "@/components/form/content/teks-detail-form";
const TeksDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTeksDetail />
</div>
</div>
);
};
export default TeksDetailPage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormTeksUpdate from "@/components/form/content/teks-update-form";
const TeksUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTeksUpdate />
</div>
</div>
);
};
export default TeksUpdatePage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormVideoUpdate from "@/components/form/content/video-update-form";
const VideoUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormVideoUpdate />
</div>
</div>
);
};
export default VideoUpdatePage;

View File

@ -205,21 +205,6 @@ const Navbar = () => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
useEffect(() => {
if (!pathname) return;
if (
pathname.startsWith("/_next") ||
pathname.startsWith("/api") ||
pathname.includes("favicon")
)
return;
if (!pathname.startsWith("/in")) {
router.replace("/in");
}
}, [pathname]);
return ( return (
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50"> <div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
<div className="flex items-center justify-between px-4 lg:px-16 py-2 gap-3"> <div className="flex items-center justify-between px-4 lg:px-16 py-2 gap-3">

View File

@ -100,6 +100,13 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
...(!hideForRole14 ...(!hideForRole14
? [ ? [
{
href: "/contributor/content/satker",
label: "satker",
active: pathname.includes("/content/satker"),
icon: "heroicons:credit-card",
children: [],
},
{ {
href: "/contributor/content/spit", href: "/contributor/content/spit",
label: "spit", label: "spit",

View File

@ -7,31 +7,34 @@ const intlMiddleware = createMiddleware(routing);
export default function middleware(request: NextRequest) { export default function middleware(request: NextRequest) {
const { pathname } = request.nextUrl; const { pathname } = request.nextUrl;
// Abaikan asset & API // Abaikan semua static file dan asset
if ( const isStaticAsset =
pathname.startsWith("/api") || pathname.startsWith("/api") ||
pathname.startsWith("/_next") || pathname.startsWith("/_next") ||
pathname.startsWith("/favicon") || pathname.startsWith("/favicon") ||
pathname.startsWith("/assets") pathname.startsWith("/assets") ||
) { pathname.startsWith("/static") ||
pathname.startsWith("/images") ||
pathname.startsWith("/icons") ||
pathname.match(/\.(png|jpg|jpeg|gif|webp|svg|ico)$/);
if (isStaticAsset) {
return NextResponse.next(); return NextResponse.next();
} }
// Jika sudah mengandung /in → pakai next-intl // Jika sudah dalam /in jalankan intl middleware
if (pathname.startsWith("/in")) { if (pathname.startsWith("/in")) {
return intlMiddleware(request); return intlMiddleware(request);
} }
// Jika TIDAK mengandung /in → selalu tambahkan /in // Redirect otomatis ke /in
// Contoh:
// /image/detail/... → /in/image/detail/...
const url = request.nextUrl.clone(); const url = request.nextUrl.clone();
url.pathname = `/in${pathname}`; url.pathname = `/in${pathname}`;
return NextResponse.redirect(url); return NextResponse.redirect(url);
} }
export const config = { export const config = {
matcher: ["/((?!_next|api|favicon.ico|assets).*)"], matcher: ["/((?!_next|api|favicon.ico|assets|static|images|icons).*)"],
}; };
// import createMiddleware from "next-intl/middleware"; // import createMiddleware from "next-intl/middleware";

View File

@ -62,7 +62,7 @@ export async function listDataSatker(
title: string = "" title: string = ""
) { ) {
return await httpGetInterceptor( return await httpGetInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&isAllSatker=1` + `media/list?enablePage=1&sortBy=createdAt&sort=desc&isAllSatker=true` +
`&size=${limit}` + `&size=${limit}` +
`&page=${page}` + `&page=${page}` +
`&isForSelf=${isForSelf}` + `&isForSelf=${isForSelf}` +