This commit is contained in:
Rama Priyanto 2025-01-07 20:03:20 +07:00
commit cd3d4b1570
53 changed files with 4098 additions and 1754 deletions

View File

@ -32,15 +32,15 @@ const INITIAL_MONTH = dayjs().format("M");
export interface AgendaSettingsAPIResponse { export interface AgendaSettingsAPIResponse {
id: number; id: number;
title: string; title: string;
createdByName: string;
description: string; description: string;
agendaType: string; agendaType: string;
startDate: string; // API mengembalikan tanggal dalam bentuk string startDate: string;
endDate: string; endDate: string;
isActive: boolean; isActive: boolean;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
createdById: number | null; createdById: number | null;
createdByName: string | null;
} }
interface APIResponse { interface APIResponse {
@ -89,12 +89,15 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const events = res?.data?.data.map((event: any) => ({ const events = res?.data?.data.map((event: any) => ({
id: event.id.toString(), id: event.id.toString(),
title: event.title, title: event.title,
createBy: "Mabes Polri - Approver",
createdByName: event.createdByName,
start: new Date(event.startDate), start: new Date(event.startDate),
end: new Date(event.endDate), end: new Date(event.endDate),
allDay: true, // Assuming all events are all-day by default allDay: true,
extendedProps: { extendedProps: {
calendar: event.agendaType, // Map agendaType to the calendar category calendar: event.agendaType,
description: event.description, description: event.description,
createdByName: event.createdByName,
}, },
})); }));
@ -130,25 +133,25 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const month = (selectedMonth.getMonth() + 1).toString(); const month = (selectedMonth.getMonth() + 1).toString();
const typeFilter = ""; // Replace with your type filter logic if needed const typeFilter = ""; // Replace with your type filter logic if needed
const response = await getAgendaSettingsList( const response = await getAgendaSettingsList(year, month, typeFilter);
year,
month,
typeFilter
);
if (response?.data && Array.isArray(response?.data)) { if (response?.data && Array.isArray(response?.data)) {
// Transform API data to match CalendarEvent type // Transform API data to match CalendarEvent type
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({ const eventsFromAPI: CalendarEvent[] = response?.data?.map(
id: item.id.toString(), (item) => ({
title: item.title, id: item.id.toString(),
start: new Date(item.startDate), title: item.title,
end: new Date(item.endDate), createBy: "Mabes Polri - Approver",
allDay: true, // Sesuaikan jika memang ada event sepanjang hari createdByName: item.createdByName,
extendedProps: { start: new Date(item.startDate),
calendar: item.agendaType, end: new Date(item.endDate),
description: item.description, allDay: true, // Sesuaikan jika memang ada event sepanjang hari
}, extendedProps: {
})); calendar: item.agendaType,
description: item.description,
},
})
);
setApiEvents(eventsFromAPI); setApiEvents(eventsFromAPI);
} else { } else {
@ -235,6 +238,18 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
} }
}; };
const renderEventContent = (eventInfo: any) => {
const { title } = eventInfo.event;
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps
return (
<>
<p>{title}</p>
<p className=" text-xs text-start mt-2">Create By: {createdByName}</p>
</>
);
};
const handleClassName = (arg: EventContentArg) => { const handleClassName = (arg: EventContentArg) => {
if (arg.event.extendedProps.calendar === "mabes") { if (arg.event.extendedProps.calendar === "mabes") {
return "primary"; return "primary";
@ -342,6 +357,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
dateClick={handleDateClick} dateClick={handleDateClick}
eventClick={handleEventClick} eventClick={handleEventClick}
initialView="dayGridMonth" initialView="dayGridMonth"
eventContent={renderEventContent}
/> />
</CardContent> </CardContent>
</Card> </Card>
@ -364,6 +380,8 @@ export type CalendarEvent = {
title: string; title: string;
start: Date; start: Date;
end: Date; end: Date;
createBy: string;
createdByName: string;
allDay: boolean; allDay: boolean;
extendedProps: { extendedProps: {
calendar: string; calendar: string;

View File

@ -120,20 +120,20 @@ export const categories = [
label: "Nasional", label: "Nasional",
value: "mabes", value: "mabes",
className: className:
"data-[state=checked]:bg-primary data-[state=checked]:ring-primary", "data-[state=checked]:bg-yellow-500 data-[state=checked]:ring-yellow-500",
}, },
{ {
label: "Polda", label: "Polda",
value: "polda", value: "polda",
className: className:
"data-[state=checked]:bg-success data-[state=checked]:ring-success", "data-[state=checked]:bg-blue-400 data-[state=checked]:ring-blue-400",
}, },
{ {
label: "Polres", label: "Polres",
value: "polres", value: "polres",
className: className:
"data-[state=checked]:bg-destructive data-[state=checked]:ring-destructive ", "data-[state=checked]:bg-slate-400 data-[state=checked]:ring-slate-400 ",
}, },
{ {
label: "Satker", label: "Satker",
@ -144,7 +144,8 @@ export const categories = [
{ {
label: "Internasional", label: "Internasional",
value: "international", value: "international",
className: "data-[state=checked]:bg-info data-[state=checked]:ring-info ", className:
"data-[state=checked]:bg-green-500 data-[state=checked]:ring-green-500 ",
}, },
]; ];

View File

@ -1,5 +1,10 @@
// "use client"; // "use client";
import React, { useState, useEffect } from "react"; import React, {
useState,
useEffect,
useRef,
Fragment,
} from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -32,7 +37,7 @@ import {
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { error, loading } from "@/lib/swal"; import { error, loading, success } from "@/lib/swal";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
@ -42,12 +47,18 @@ import { saveAgendaSettings } from "@/service/agenda-setting/agenda-setting";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { getUserLevelForAssignments } from "@/service/task"; import { getUserLevelForAssignments } from "@/service/task";
import { AudioRecorder } from "react-audio-voice-recorder"; import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client";
const schema = z.object({ const schema = z.object({
title: z.string().min(3, { message: "Required" }), title: z.string().min(3, { message: "Required" }),
description: z.string().min(3, { message: "Required" }), description: z.string().min(3, { message: "Required" }),
}); });
interface FileWithPreview extends File {
preview: string;
}
const EventModal = ({ const EventModal = ({
open, open,
onClose, onClose,
@ -64,7 +75,7 @@ const EventModal = ({
const [startDate, setStartDate] = useState<Date>(new Date()); const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date()); const [endDate, setEndDate] = useState<Date>(new Date());
const [isPending, startTransition] = React.useTransition(); const [isPending, startTransition] = React.useTransition();
const [calendarProps, setCalendarProps] = React.useState<any>( const [agendaType, setAgendaType] = React.useState<any>(
categories[0].value categories[0].value
); );
const [listDest, setListDest] = useState([]); const [listDest, setListDest] = useState([]);
@ -78,6 +89,14 @@ const EventModal = ({
const [audioFile, setAudioFile] = useState<File | null>(null); const [audioFile, setAudioFile] = useState<File | null>(null);
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const { const {
register, register,
@ -96,8 +115,17 @@ const EventModal = ({
setIsLoading(true); setIsLoading(true);
try { try {
const response = await getUserLevelForAssignments(); const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list); const levelList = response?.data?.data.list;
const initialExpandedState = response?.data?.data.list.reduce( let listFiltered = [];
if (agendaType == "polda") {
listFiltered = levelList.filter((level: any) => level.name != 'SATKER POLRI');
} else if (agendaType == "polres") {
listFiltered = levelList.filter((level: any) => level.name != 'SATKER POLRI');
} else if (agendaType == "satker") {
listFiltered = levelList.filter((level: any) => level.name == 'SATKER POLRI');
}
setListDest(listFiltered);
const initialExpandedState = listFiltered.reduce(
(acc: any, polda: any) => { (acc: any, polda: any) => {
acc[polda.id] = false; acc[polda.id] = false;
return acc; return acc;
@ -105,7 +133,6 @@ const EventModal = ({
{} {}
); );
setExpandedPolda(initialExpandedState); setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState);
} catch (error) { } catch (error) {
console.error("Error fetching Polda/Polres data:", error); console.error("Error fetching Polda/Polres data:", error);
} finally { } finally {
@ -113,7 +140,8 @@ const EventModal = ({
} }
} }
fetchPoldaPolres(); fetchPoldaPolres();
}, []); console.log("Event", event);
}, [agendaType]);
const handleCheckboxChange = (levelId: number) => { const handleCheckboxChange = (levelId: number) => {
setCheckedLevels((prev) => { setCheckedLevels((prev) => {
@ -128,18 +156,15 @@ const EventModal = ({
}; };
const save = async (data: any) => { const save = async (data: any) => {
if (!audioFile) return; // const formData = new FormData();
// formData.append("voiceNote", audioFile);
const formData = new FormData();
formData.append("voiceNote", audioFile);
const reqData = { const reqData = {
title: data.title, title: data.title,
description: data.description, description: data.description,
agendaType: calendarProps, agendaType: agendaType,
startDate: format(startDate, "yyyy-MM-dd"), startDate: format(startDate, "yyyy-MM-dd"),
endDate: format(endDate, "yyyy-MM-dd"), endDate: format(endDate, "yyyy-MM-dd"),
voiceNote: formData,
}; };
console.log("Submitted Data:", reqData); console.log("Submitted Data:", reqData);
@ -150,20 +175,70 @@ const EventModal = ({
return false; return false;
} }
Cookies.set("AgendaSetting", response?.data?.data.id, { const id = response?.data?.data.id;
expires: 1,
if (imageFiles?.length == 0) {
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"1",
"0"
);
});
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"2",
"0"
);
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"3",
"0"
);
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"4",
"0"
);
}); });
// Optional: Use Swal for success feedback // Optional: Use Swal for success feedback
MySwal.fire({ // MySwal.fire({
title: "Sukses", // title: "Sukses",
text: "Data berhasil disimpan.", // text: "Data berhasil disimpan.",
icon: "success", // icon: "success",
confirmButtonColor: "#3085d6", // confirmButtonColor: "#3085d6",
confirmButtonText: "OK", // confirmButtonText: "OK",
}).then(() => { // }).then(() => {
router.push("en/contributor/agenda-setting"); // router.push("en/contributor/agenda-setting");
}); // });
}; };
const onSubmit = (data: any) => { const onSubmit = (data: any) => {
@ -179,7 +254,7 @@ const EventModal = ({
setStartDate(event?.event?.start); setStartDate(event?.event?.start);
setEndDate(event?.event?.end); setEndDate(event?.event?.end);
const eventCalendar = event?.event?.extendedProps?.calendar; const eventCalendar = event?.event?.extendedProps?.calendar;
setCalendarProps(eventCalendar || categories[0].value); setAgendaType(eventCalendar || categories[0].value);
} }
setValue("title", event?.event?.title || ""); setValue("title", event?.event?.title || "");
setValue("description", event?.event?.description || ""); setValue("description", event?.event?.description || "");
@ -197,6 +272,7 @@ const EventModal = ({
}; };
const toggleExpand = (poldaId: any) => { const toggleExpand = (poldaId: any) => {
console.log("Toogle : ", expandedPolda);
setExpandedPolda((prev: any) => ({ setExpandedPolda((prev: any) => ({
...prev, ...prev,
[poldaId]: !prev[poldaId], [poldaId]: !prev[poldaId],
@ -248,6 +324,89 @@ const EventModal = ({
audioElements.forEach((audio) => audio.remove()); audioElements.forEach((audio) => audio.remove());
}; };
async function uploadResumableFile(
idx: number,
id: string,
file: any,
fileTypeId: string,
duration: string
) {
console.log(idx, id, file, fileTypeId, duration);
// const placements = getPlacement(file.placements);
// console.log("Placementttt: : ", placements);
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/agenda-settings/file/upload`,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
agendaSettingId: id,
filename: file.name,
filetype: file.type,
fileTypeId: fileTypeId,
duration: "",
isWatermark: "true", // hardcode
},
onError: async (e: any) => {
console.log("Error upload :", e);
error(e);
},
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// progressInfo[idx].percentage = uploadPersen;
// counterUpdateProgress++;
// console.log(counterUpdateProgress);
// setProgressList(progressInfo);
// setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
// uploadPersen = 100;
// progressInfo[idx].percentage = 100;
// counterUpdateProgress++;
// setCounterProgress(counterUpdateProgress);
successTodo();
if (fileTypeId == '1'){
setIsImageUploadFinish(true);
} else if (fileTypeId == '2'){
setIsVideoUploadFinish(true);
} if (fileTypeId == '3'){
setIsTextUploadFinish(true);
} if (fileTypeId == '4'){
setIsAudioUploadFinish(true);
}
},
});
upload.start();
}
useEffect(() => {
successTodo();
}, [isImageUploadFinish, isVideoUploadFinish, isAudioUploadFinish, isTextUploadFinish])
function successTodo() {
if (isImageUploadFinish && isVideoUploadFinish && isAudioUploadFinish && isTextUploadFinish) {
successSubmit("/in/contributor/agenda-setting");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
return ( return (
<> <>
<DeleteConfirmationDialog <DeleteConfirmationDialog
@ -263,7 +422,7 @@ const EventModal = ({
> >
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{event ? "Edit Agenda Setting" : "Create Agenda Setting"}{" "} {event?.length > 1 ? "Edit Agenda Setting" : "Create Agenda Setting"}{" "}
{event?.title} {event?.title}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
@ -366,14 +525,14 @@ const EventModal = ({
</Popover> </Popover>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label htmlFor="calendarProps">Jenis Agenda </Label> <Label htmlFor="agendaType">Jenis Agenda </Label>
<Controller <Controller
name="calendarProps" name="agendaType"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<Select <Select
value={calendarProps} value={agendaType}
onValueChange={(data) => setCalendarProps(data)} onValueChange={(data) => setAgendaType(data)}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Label" /> <SelectValue placeholder="Label" />
@ -392,12 +551,12 @@ const EventModal = ({
)} )}
/> />
</div> </div>
{calendarProps === "polda" && ( {(agendaType === "polda" || agendaType === "polres" || agendaType === "satker" )&& (
<div> <div>
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary"> <Button variant="soft" size="sm" color="primary">
[Kustom] [Pilih {agendaType}]
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -429,7 +588,7 @@ const EventModal = ({
)} )}
</button> </button>
</Label> </Label>
{expandedPolda[polda.id] && ( {(agendaType == "polres" || agendaType == "satker") && expandedPolda[polda.id] && (
<div className="ml-6 mt-2"> <div className="ml-6 mt-2">
<Label className="block"> <Label className="block">
<Checkbox <Checkbox
@ -506,29 +665,36 @@ const EventModal = ({
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label>Video</Label> <Label>Video</Label>
<Input <FileUploader
type="file" accept={{
accept="video/*" "mp4/*": [],
multiple "mov/*": [],
{...register("attachments.video")} }}
maxSize={100}
label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setImageFiles(files)}
/> />
</div> </div>
<div> <div>
<Label>Foto</Label> <Label>Foto</Label>
<Input <FileUploader
type="file" accept={{
accept="image/*" "image/*": [],
multiple }}
{...register("attachments.photo")} maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/> />
</div> </div>
<div> <div>
<Label>Teks</Label> <Label>Teks</Label>
<Input <FileUploader
type="file" accept={{
accept="text/plain" "pdf/*": [],
multiple }}
{...register("attachments.text")} maxSize={100}
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)}
/> />
</div> </div>
<div> <div>
@ -542,6 +708,16 @@ const EventModal = ({
downloadOnSavePress={true} downloadOnSavePress={true}
downloadFileExtension="webm" downloadFileExtension="webm"
/> />
<FileUploader
accept={{
"mp3/*": [],
"wav/*": [],
}}
maxSize={100}
label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) => setAudioFiles(files)}
className="mt-2"
/>
</div> </div>
{audioFile && ( {audioFile && (
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
@ -568,15 +744,15 @@ const EventModal = ({
{isPending ? ( {isPending ? (
<> <>
<Loader2 className="me-2 h-4 w-4 animate-spin" /> <Loader2 className="me-2 h-4 w-4 animate-spin" />
{event ? "Updating..." : "Adding..."} {event?.length > 1 ? "Updating..." : "Adding..."}
</> </>
) : event ? ( ) : event?.length > 1 ? (
"Update Agenda Setting" "Update Agenda Setting"
) : ( ) : (
"Simpan Agenda Setting" "Simpan Agenda Setting"
)} )}
</Button> </Button>
{event && ( {event?.length > 1 && (
<Button <Button
type="button" type="button"
color="destructive" color="destructive"

View File

@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -99,25 +100,32 @@ const columns: ColumnDef<any>[] = [
); );
}, },
}, },
{ {
accessorKey: "isDone", accessorKey: "statusName",
header: "Status", header: "Status",
cell: ({ row }) => { cell: ({ row }) => {
const isDone = row.getValue<boolean>("isDone"); const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
};
// Mengambil `statusName` dari data API
const status = row.getValue("statusName") as string;
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
// Gunakan `statusName` untuk pencocokan
const statusStyles =
statusColors[statusName] || "bg-gray-100 text-gray-600";
return ( return (
<div> <Badge
<Button className={cn(
size="sm" "rounded-full px-5 w-full whitespace-nowrap",
color="success" statusStyles
variant="outline" )}
className={` btn btn-sm ${ >
isDone ? "btn-outline-success" : "btn-outline-primary" {status} {/* Tetap tampilkan nilai asli */}
} pill-btn ml-1`} </Badge>
>
{isDone ? "Selesai" : "Aktif"}
</Button>
</div>
); );
}, },
}, },
@ -139,12 +147,18 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<a href="/contributor/task/detail/[id]"> <Link href={`/contributor/content/audio/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>
</a> </Link>
<Link href={`/contributor/content/Audio/update/${row.original.id}`}>
<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 className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
<Trash2 className="w-4 h-4 me-1.5" /> <Trash2 className="w-4 h-4 me-1.5" />
Delete Delete

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

@ -100,7 +100,6 @@ const columns: ColumnDef<any>[] = [
); );
}, },
}, },
{ {
accessorKey: "statusName", accessorKey: "statusName",
header: "Status", header: "Status",
@ -109,12 +108,8 @@ const columns: ColumnDef<any>[] = [
diterima: "bg-green-100 text-green-600", diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600", "menunggu review": "bg-orange-100 text-orange-600",
}; };
// Mengambil `statusName` dari data API
const status = row.getValue("statusName") as string; const status = row.getValue("statusName") as string;
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil const statusName = status?.toLocaleLowerCase();
// Gunakan `statusName` untuk pencocokan
const statusStyles = const statusStyles =
statusColors[statusName] || "bg-gray-100 text-gray-600"; statusColors[statusName] || "bg-gray-100 text-gray-600";
@ -125,7 +120,7 @@ const columns: ColumnDef<any>[] = [
statusStyles statusStyles
)} )}
> >
{status} {/* Tetap tampilkan nilai asli */} {status}
</Badge> </Badge>
); );
}, },

View File

@ -102,23 +102,31 @@ const columns: ColumnDef<any>[] = [
}, },
{ {
accessorKey: "isDone", accessorKey: "statusName",
header: "Status", header: "Status",
cell: ({ row }) => { cell: ({ row }) => {
const isDone = row.getValue<boolean>("isDone"); const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
};
// Mengambil `statusName` dari data API
const status = row.getValue("statusName") as string;
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
// Gunakan `statusName` untuk pencocokan
const statusStyles =
statusColors[statusName] || "bg-gray-100 text-gray-600";
return ( return (
<div> <Badge
<Button className={cn(
size="sm" "rounded-full px-5 w-full whitespace-nowrap",
color="success" statusStyles
variant="outline" )}
className={` btn btn-sm ${ >
isDone ? "btn-outline-success" : "btn-outline-primary" {status} {/* Tetap tampilkan nilai asli */}
} pill-btn ml-1`} </Badge>
>
{isDone ? "Selesai" : "Aktif"}
</Button>
</div>
); );
}, },
}, },

View File

@ -102,23 +102,31 @@ const columns: ColumnDef<any>[] = [
}, },
{ {
accessorKey: "isDone", accessorKey: "statusName",
header: "Status", header: "Status",
cell: ({ row }) => { cell: ({ row }) => {
const isDone = row.getValue<boolean>("isDone"); const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
};
// Mengambil `statusName` dari data API
const status = row.getValue("statusName") as string;
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
// Gunakan `statusName` untuk pencocokan
const statusStyles =
statusColors[statusName] || "bg-gray-100 text-gray-600";
return ( return (
<div> <Badge
<Button className={cn(
size="sm" "rounded-full px-5 w-full whitespace-nowrap",
color="success" statusStyles
variant="outline" )}
className={` btn btn-sm ${ >
isDone ? "btn-outline-success" : "btn-outline-primary" {status} {/* Tetap tampilkan nilai asli */}
} pill-btn ml-1`} </Badge>
>
{isDone ? "Selesai" : "Aktif"}
</Button>
</div>
); );
}, },
}, },

View File

@ -66,7 +66,7 @@ const columns: ColumnDef<any>[] = [
return ( return (
<div> <div>
{isActive ? ( {isActive ? (
<b className="text-info">Terkirim</b> <b className="text-blue-500">Terkirim</b>
) : ( ) : (
<b className="text-danger">Belum Terkirim</b> <b className="text-danger">Belum Terkirim</b>
)} )}

View File

@ -66,7 +66,7 @@ const columns: ColumnDef<any>[] = [
return ( return (
<div> <div>
{isActive ? ( {isActive ? (
<b className="text-info">Terkirim</b> <b className="text-blue-500">Terkirim</b>
) : ( ) : (
<b className="text-danger">Belum Terkirim</b> <b className="text-danger">Belum Terkirim</b>
)} )}

View File

@ -84,10 +84,17 @@ const columns: ColumnDef<any>[] = [
accessorKey: "isPublishForAll", accessorKey: "isPublishForAll",
header: "Status", header: "Status",
cell: ({ row }) => { cell: ({ row }) => {
const isPublishForAll = row.getValue("isPublishForAll");
return ( return (
<span className="whitespace-nowrap text-blue-600"> <Badge
{row.getValue("isPublishForAll")} className={`whitespace-nowrap px-2 py-1 rounded-full ${
</span> isPublishForAll
? "bg-green-100 text-green-600" // Warna hijau untuk status "Publish"
: "bg-orange-100 text-orange-600" // Warna kuning untuk status "Pending"
}`}
>
{isPublishForAll ? "Publish" : "Pending"}
</Badge>
); );
}, },
}, },

View File

@ -5,25 +5,8 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals"; import { getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
import { useParams, usePathname, useSearchParams } from "next/navigation"; import { useParams, usePathname, useSearchParams } from "next/navigation";
import { import { getUserLevelListByParent, listCategory, listData, listDataAll, listDataRegional } from "@/service/landing/landing";
getUserLevelListByParent, import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
listCategory,
listData,
listDataAll,
listDataRegional,
} from "@/service/landing/landing";
import {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Reveal } from "@/components/landing-page/Reveal"; import { Reveal } from "@/components/landing-page/Reveal";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -45,11 +28,8 @@ export default function FilterPage() {
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState<number>(1); const [totalPage, setTotalPage] = React.useState<number>(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>([]);
[] const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@ -71,9 +51,7 @@ export default function FilterPage() {
const [categoryFilter, setCategoryFilter] = useState<any>([]); const [categoryFilter, setCategoryFilter] = useState<any>([]);
const [monthYearFilter, setMonthYearFilter] = useState<any>(); const [monthYearFilter, setMonthYearFilter] = useState<any>();
const [searchTitle, setSearchTitle] = useState<string>(""); const [searchTitle, setSearchTitle] = useState<string>("");
const [sortByOpt, setSortByOpt] = useState<any>( const [sortByOpt, setSortByOpt] = useState<any>(sortBy === "popular" ? "clickCount" : "createdAt");
sortBy === "popular" ? "clickCount" : "createdAt"
);
const isRegional = asPath?.includes("regional"); const isRegional = asPath?.includes("regional");
const isSatker = asPath?.includes("satker"); const isSatker = asPath?.includes("satker");
const [formatFilter, setFormatFilter] = useState<any>([]); const [formatFilter, setFormatFilter] = useState<any>([]);
@ -110,14 +88,8 @@ export default function FilterPage() {
useEffect(() => { useEffect(() => {
if (categorie) { if (categorie) {
setCategoryFilter( setCategoryFilter(categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie] console.log("Kategori", categorie, categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
);
console.log(
"Kategori",
categorie,
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
} }
}, [categorie]); }, [categorie]);
@ -135,17 +107,7 @@ export default function FilterPage() {
} }
console.log(monthYearFilter, "monthFilter"); console.log(monthYearFilter, "monthFilter");
initState(); initState();
}, [ }, [change, monthYearFilter, sortBy, sortByOpt, title, startDateString, endDateString, categorie, formatFilter]);
change,
monthYearFilter,
sortBy,
sortByOpt,
title,
startDateString,
endDateString,
categorie,
formatFilter,
]);
async function getCategories() { async function getCategories() {
const category = await listCategory("1"); const category = await listCategory("1");
@ -168,10 +130,7 @@ export default function FilterPage() {
async function getData() { async function getData() {
if (asPath?.includes("/polda/") == true) { if (asPath?.includes("/polda/") == true) {
if (asPath?.split("/")[2] !== "[polda_name]") { if (asPath?.split("/")[2] !== "[polda_name]") {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -186,14 +145,8 @@ export default function FilterPage() {
filterGroup, filterGroup,
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("0", "") : "",
? getOnlyMonthAndYear(monthYearFilter) monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
?.split("/")[0]
?.replace("0", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
// setGetTotalPage(response?.data?.data?.totalPages); // setGetTotalPage(response?.data?.data?.totalPages);
@ -208,10 +161,7 @@ export default function FilterPage() {
setTotalContent(response?.data?.data?.totalElements); setTotalContent(response?.data?.data?.totalElements);
} }
} else { } else {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -225,14 +175,8 @@ export default function FilterPage() {
tag, tag,
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("0", "") : "",
? getOnlyMonthAndYear(monthYearFilter) monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
?.split("/")[0]
?.replace("0", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
// setGetTotalPage(response?.data?.data?.totalPages); // setGetTotalPage(response?.data?.data?.totalPages);
@ -283,10 +227,7 @@ export default function FilterPage() {
}; };
async function getDataRegional() { async function getDataRegional() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -299,12 +240,8 @@ export default function FilterPage() {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
12, 12,
pages, pages,
sortByOpt sortByOpt
@ -410,10 +347,7 @@ export default function FilterPage() {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label <label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
htmlFor="search"
className="block text-sm font-medium text-gray-700 dark:text-white"
>
Pencarian Pencarian
</label> </label>
<Input <Input
@ -429,9 +363,7 @@ export default function FilterPage() {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-white">Tahun & Bulan</label>
Tahun & Bulan
</label>
<ReactDatePicker <ReactDatePicker
selected={monthYearFilter} selected={monthYearFilter}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
@ -443,9 +375,7 @@ export default function FilterPage() {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-white">Tanggal</label>
Tanggal
</label>
<div className="flex flex-row justify justify-between gap-2"> <div className="flex flex-row justify justify-between gap-2">
<ReactDatePicker <ReactDatePicker
selectsRange selectsRange
@ -458,44 +388,18 @@ export default function FilterPage() {
placeholderText="Pilih Tanggal" placeholderText="Pilih Tanggal"
onCalendarClose={() => setCalenderState(!calenderState)} onCalendarClose={() => setCalenderState(!calenderState)}
/> />
<div className="flex items-center"> <div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
{handleClose ? (
<Icon
icon="carbon:close-filled"
onClick={handleDeleteDate}
width="20"
inline
color="#216ba5"
/>
) : (
""
)}
</div>
</div> </div>
</div> </div>
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white"> <h3 className="text-sm font-medium text-gray-700 dark:text-white">Kategori</h3>
Kategori
</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{categories.map((category: any) => ( {categories.map((category: any) => (
<li key={category?.id}> <li key={category?.id}>
<label <label className="inline-flex items-center" htmlFor={`${category.id}`}>
className="inline-flex items-center" <Checkbox id={`${category.id}`} value={category.id} checked={categoryFilter.includes(String(category.id))} onCheckedChange={(e) => handleCategoryFilter(Boolean(e), category.id)} />
htmlFor={`${category.id}`} <span className="ml-2 text-gray-700 dark:text-white">{category?.name}</span>
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label> </label>
</li> </li>
))} ))}
@ -505,78 +409,37 @@ export default function FilterPage() {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */} {/* Garis */}
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white"> <h3 className="text-sm font-medium text-gray-700 dark:text-white">Format Konten</h3>
Format Konten
</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="png" value="png" checked={formatFilter.includes("image")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "image")} />
id="png" <span className="ml-2 text-gray-700 dark:text-white">Foto</span>
value="png"
checked={formatFilter.includes("image")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "image")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
Foto
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="jpeg" value="jpeg" checked={formatFilter.includes("video")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "video")} />
id="jpeg" <span className="ml-2 text-gray-700 dark:text-white">Audio Visual</span>
value="jpeg"
checked={formatFilter.includes("video")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "video")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
Audio Visual
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="jpg" value="jpg" checked={formatFilter.includes("document")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "document")} />
id="jpg" <span className="ml-2 text-gray-700 dark:text-white">Teks</span>
value="jpg"
checked={formatFilter.includes("document")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "document")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
Teks
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="jpg" value="jpg" checked={formatFilter.includes("audio")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "audio")} />
id="jpg" <span className="ml-2 text-gray-700 dark:text-white">Audio</span>
value="jpg"
checked={formatFilter.includes("audio")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "audio")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
Audio
</span>
</label> </label>
</li> </li>
</ul> </ul>
</div> </div>
<div className="border-t border-black dark:border-white my-4"></div> <div className="border-t border-black dark:border-white my-4"></div>
<div className="text-center"> <div className="text-center">
<a <a onClick={cleanCheckbox} className="text-[#bb3523] cursor-pointer">
onClick={cleanCheckbox}
className="text-[#bb3523] cursor-pointer"
>
<b>Reset Filter</b> <b>Reset Filter</b>
</a> </a>
</div> </div>
@ -584,51 +447,21 @@ export default function FilterPage() {
</div> </div>
{/* Konten Kanan */} {/* Konten Kanan */}
<Reveal> <div className="flex-1 w-[75%]">
<div className="flex-1 w-full"> <div className="flex flex-col items-end mb-4">
<div className="flex flex-col items-end mb-4"> <h2 className="text-lg font-semibold">Urutkan berdasarkan</h2>
<h2 className="text-lg font-semibold">Urutkan berdasarkan</h2> <select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
<select <option value="terbaru">Terbaru</option>
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} <option value="terpopuler">Terpopuler</option>
onChange={(e) => handleSorting(e)} </select>
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
>
<option value="terbaru">Terbaru</option>
<option value="terpopuler">Terpopuler</option>
</select>
</div>
<div className="flex flex-col gap-2 w-full">
<FilterImageComponent
categoryFilter={categoryFilter}
sortByOpt={sortByOpt}
startDateString={startDateString}
endDateString={endDateString}
monthYearFilter={monthYearFilter}
/>
<FilterVideoComponent
categoryFilter={categoryFilter}
sortByOpt={sortByOpt}
startDateString={startDateString}
endDateString={endDateString}
monthYearFilter={monthYearFilter}
/>
<FilterDocumentComponent
categoryFilter={categoryFilter}
sortByOpt={sortByOpt}
startDateString={startDateString}
endDateString={endDateString}
monthYearFilter={monthYearFilter}
/>
<FilterAudioComponent
categoryFilter={categoryFilter}
sortByOpt={sortByOpt}
startDateString={startDateString}
endDateString={endDateString}
monthYearFilter={monthYearFilter}
/>
</div>
</div> </div>
</Reveal> <div className="flex flex-col gap-2 w-full">
<FilterImageComponent categoryFilter={categoryFilter} sortByOpt={sortByOpt} startDateString={startDateString} endDateString={endDateString} monthYearFilter={monthYearFilter} />
<FilterVideoComponent categoryFilter={categoryFilter} sortByOpt={sortByOpt} startDateString={startDateString} endDateString={endDateString} monthYearFilter={monthYearFilter} />
<FilterDocumentComponent categoryFilter={categoryFilter} sortByOpt={sortByOpt} startDateString={startDateString} endDateString={endDateString} monthYearFilter={monthYearFilter} />
<FilterAudioComponent categoryFilter={categoryFilter} sortByOpt={sortByOpt} startDateString={startDateString} endDateString={endDateString} monthYearFilter={monthYearFilter} />
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@ -1,14 +1,16 @@
"use client"; "use client";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { getDetail } from "@/service/landing/landing";
import VideoPlayer from "@/utils/video-player";
import NewContent from "@/components/landing-page/new-content"; import NewContent from "@/components/landing-page/new-content";
import { Link } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { BarWave } from "react-cssfx-loading"; import { BarWave } from "react-cssfx-loading";
import { useToast } from "@/components/ui/use-toast";
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
import { getCookiesDecrypt } from "@/lib/utils";
import { close, error, loading } from "@/config/swal";
const DetailAudio = () => { const DetailAudio = () => {
const [selectedSize, setSelectedSize] = useState<string>("L"); const [selectedSize, setSelectedSize] = useState<string>("L");
@ -16,19 +18,196 @@ const DetailAudio = () => {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const params = useParams(); const params = useParams();
const slug = params?.slug; const slug = String(params?.slug);
const [detailDataAudio, setDetailDataAudio] = useState<any>(); const [detailDataAudio, setDetailDataAudio] = useState<any>();
const [isSaved, setIsSaved] = useState(false);
const [wishlistId, setWishlistId] = useState();
const { toast } = useToast();
const [isDownloadAll, setIsDownloadAll] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const [isFromSPIT, setIsFromSPIT] = useState(false);
const [main, setMain] = useState<any>();
const [resolutionSelected, setResolutionSelected] = useState("720");
const [imageSizeSelected, setImageSizeSelected] = useState("l");
const userId = getCookiesDecrypt("uie");
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
checkWishlist();
}, []); }, []);
const initFetch = async () => { const initFetch = async () => {
const response = await getDetail(String(slug)); const response = await getDetail(String(slug));
console.log("detailAudio", response); console.log("detailAudio", response);
setIsFromSPIT(response?.data?.data?.isFromSPIT);
setMain({
id: response?.data?.data?.files[0]?.id,
type: response?.data?.data?.fileType.name,
url:
Number(response?.data?.data?.fileType?.id) == 4
? response?.data?.data?.files[0]?.secondaryUrl
: Number(response?.data?.data?.fileType?.id) == 2
? `${process.env.NEXT_PUBLIC_API}/media/view?id=${response?.data?.data?.files[0]?.id}&operation=file&type=video`
: response?.data?.data?.files[0]?.url,
thumbnailFileUrl: response?.data?.data?.files[0]?.thumbnailFileUrl,
names: response?.data?.data?.files[0]?.fileName,
format: response?.data?.data?.files[0]?.format,
widthPixel: response?.data?.data?.files[0]?.widthPixel,
heightPixel: response?.data?.data?.files[0]?.heightPixel,
size: response?.data?.data?.files[0]?.size,
caption: response?.data?.data?.files[0]?.caption,
});
setDetailDataAudio(response?.data?.data); setDetailDataAudio(response?.data?.data);
}; };
const doBookmark = async () => {
if (userId) {
const data = {
mediaUploadId: slug?.split("-")?.[0],
};
loading();
const res = await saveWishlist(data);
if (res?.error) {
error(res.message);
return false;
}
close();
toast({
title: "Konten berhasil disimpan",
});
checkWishlist();
} else {
router.push("/auth");
}
};
async function checkWishlist() {
if (userId) {
const res = await checkWishlistStatus(slug.split("-")?.[0]);
console.log(res?.data?.data);
const isAlreadyOnWishlist = res?.data?.data !== "-1";
setWishlistId(res?.data?.data);
setIsSaved(isAlreadyOnWishlist);
}
}
const handleDeleteWishlist = async () => {
if (userId) {
loading();
const res = await deleteWishlist(wishlistId);
if (res?.error) {
error(res.message);
return false;
}
toast({
title: "Konten berhasil dihapus",
});
close();
checkWishlist();
} else {
router.push("/auth");
}
};
async function sendActivityLog(activityTypeId: number) {
const data = {
activityTypeId,
mediaId: slug.split("-")?.[0],
url: window.location.href,
};
// set activity
// const response = await postActivityLog(data, token);
// console.log(response);
}
const handleDownload = () => {
if (downloadProgress === 0) {
if (!userId) {
router.push("/auth/login");
} else {
sendActivityLog(2);
sendActivityLog(3);
if (isDownloadAll) {
let url: string;
const baseId = slug.split("-")?.[0];
// if (type === "1") {
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${resolutionSelected}`;
// } else if (type === "2") {
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${imageSizeSelected}`;
// } else {
url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}`;
// }
downloadFile(url, "FileDownload.zip");
} else {
if (isFromSPIT && main?.url?.includes("spit.humas")) {
downloadFile(`${main?.url}`, `${main.names}`);
} else {
downloadFile(`${main?.url}`, `${main.names}`);
}
}
// } else if (type === "1" && resolutionSelected?.length > 0) {
// if (isFromSPIT && main?.url?.includes("spit.humas")) {
// downloadFile(`${main?.url}`, `${main.names}`);
// } else {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
// downloadFile(url, `${main.names}`);
// }
// } else if (type === "2" && imageSizeSelected?.length > 0) {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=image&resolution=${imageSizeSelected}`;
// downloadFile(url, `${main.names}`);
// } else if (type === "3" || type === "4") {
// downloadFile(`${main?.url}`, `${main.names}`);
// }
}
}
};
const downloadFile = (fileUrl: string, name: string) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", fileUrl, true);
xhr.responseType = "blob";
xhr.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percentCompleted = Math.round((event.loaded / event.total) * 100);
setDownloadProgress(percentCompleted);
}
});
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`;
const blob = new Blob([xhr.response], {
type: contentType,
});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
}
});
xhr.onloadend = () => {
setDownloadProgress(0);
};
xhr.send();
};
const sizes = [ const sizes = [
{ label: "XL", value: "3198 x 1798 px" }, { label: "XL", value: "3198 x 1798 px" },
{ label: "L", value: "2399 x 1349 px" }, { label: "L", value: "2399 x 1349 px" },
@ -68,52 +247,64 @@ const DetailAudio = () => {
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 h-fit bg-gray-300 rounded-lg mx-4"> <div className="md:w-1/4 p-4 h-fit bg-gray-300 rounded-lg mx-4">
<div className="flex flex-col mb-3 items-center justify-center cursor-pointer"> {isSaved ? (
<svg xmlns="http://www.w3.org/2000/svg" width="2.5em" height="2.5em" viewBox="0 0 24 24"> <a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<path fill="black" d="m17 18l-5-2.18L7 18V5h10m0-2H7a2 2 0 0 0-2 2v16l7-3l7 3V5a2 2 0 0 0-2-2" /> <Icon icon="material-symbols:bookmark" width={40} />
</svg> <p className="text-base lg:text-lg">Hapus</p>
<p className="text-base lg:text-lg">Simpan</p> </a>
</div> ) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<Icon icon="material-symbols:bookmark-outline" width={40} />
<p className="text-base lg:text-lg">Simpan</p>
</a>
)}
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<Link href="" className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"> <Link href={`/all/filter?title=polda&category=${detailDataAudio?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded">
{detailDataAudio?.category?.name} {detailDataAudio?.category?.name}
</Link> </Link>
<div className="flex justify-center flex-wrap gap-2 mb-4"> <div className="flex justify-center flex-wrap gap-2 mb-4">
<p className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">poldajabar</p> {detailDataAudio?.tags?.split(",").map((tag: string) => (
<p className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">pilkadamai2024</p> <a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">
{tag}
</a>
))}
</div> </div>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
{/* Opsi Ukuran Foto */} {/* Opsi Ukuran Foto */}
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4> <h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Audio</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size) => ( {sizes.map((size: any) => (
<label key={size.label} className="flex items-center space-x-2 cursor-pointer"> <div className="flex flex-row justify-between">
<input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" /> <div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer">
<div className="text-sm"> <input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" />
{size.label} ----------------- {size.value} <div className="text-sm">{size.label}</div>
</div> </div>
</label> <div className="">
<div className="text-sm">{size.value}</div>
</div>
</div>
))} ))}
</div> </div>
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input type="checkbox" className="text-red-600 focus:ring-red-600" /> <input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
<span>Download Semua File?</span> <span>Download Semua File?</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"> <button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" /> <path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
</svg> </svg>

View File

@ -3,30 +3,10 @@ import React, { useEffect, useState } from "react";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { import { formatDateToIndonesian, getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
formatDateToIndonesian,
getOnlyDate,
getOnlyMonthAndYear,
} from "@/utils/globals";
import { useParams, usePathname, useSearchParams } from "next/navigation"; import { useParams, usePathname, useSearchParams } from "next/navigation";
import { import { getUserLevelListByParent, listCategory, listData, listDataRegional } from "@/service/landing/landing";
getUserLevelListByParent, import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
listCategory,
listData,
listDataRegional,
} from "@/service/landing/landing";
import {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import LandingPagination from "@/components/landing-page/pagination"; import LandingPagination from "@/components/landing-page/pagination";
import { Reveal } from "@/components/landing-page/Reveal"; import { Reveal } from "@/components/landing-page/Reveal";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
@ -34,13 +14,8 @@ import { Input } from "@/components/ui/input";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
Carousel, import { useTranslations } from "next-intl";
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -60,11 +35,8 @@ const FilterPage = () => {
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState<number>(1); const [totalPage, setTotalPage] = React.useState<number>(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>([]);
[] const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@ -85,9 +57,7 @@ const FilterPage = () => {
const [categoryFilter, setCategoryFilter] = useState<any>([]); const [categoryFilter, setCategoryFilter] = useState<any>([]);
const [monthYearFilter, setMonthYearFilter] = useState<any>(); const [monthYearFilter, setMonthYearFilter] = useState<any>();
const [searchTitle, setSearchTitle] = useState<string>(""); const [searchTitle, setSearchTitle] = useState<string>("");
const [sortByOpt, setSortByOpt] = useState<any>( const [sortByOpt, setSortByOpt] = useState<any>(sortBy === "popular" ? "clickCount" : "createdAt");
sortBy === "popular" ? "clickCount" : "createdAt"
);
const isRegional = asPath?.includes("regional"); const isRegional = asPath?.includes("regional");
const isSatker = asPath?.includes("satker"); const isSatker = asPath?.includes("satker");
const [formatFilter, setFormatFilter] = useState<any>([]); const [formatFilter, setFormatFilter] = useState<any>([]);
@ -99,6 +69,7 @@ const FilterPage = () => {
const [handleClose, setHandleClose] = useState(false); const [handleClose, setHandleClose] = useState(false);
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const [userLevels, setUserLevels] = useState([]); const [userLevels, setUserLevels] = useState([]);
const t = useTranslations("FilterPage");
// const [startDate, endDate] = dateRange; // const [startDate, endDate] = dateRange;
@ -123,14 +94,8 @@ const FilterPage = () => {
useEffect(() => { useEffect(() => {
if (categorie) { if (categorie) {
setCategoryFilter( setCategoryFilter(categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie] console.log("Kategori", categorie, categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
);
console.log(
"Kategori",
categorie,
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
} }
}, [categorie]); }, [categorie]);
@ -148,22 +113,10 @@ const FilterPage = () => {
} }
console.log(monthYearFilter, "monthFilter"); console.log(monthYearFilter, "monthFilter");
initState(); initState();
}, [ }, [change, asPath, monthYearFilter, page, sortBy, sortByOpt, title, startDateString, endDateString, categorie, formatFilter]);
change,
asPath,
monthYearFilter,
page,
sortBy,
sortByOpt,
title,
startDateString,
endDateString,
categorie,
formatFilter,
]);
async function getCategories() { async function getCategories() {
const category = await listCategory("1"); const category = await listCategory("4");
const resCategory = category?.data?.data?.content; const resCategory = category?.data?.data?.content;
setCategories(resCategory); setCategories(resCategory);
} }
@ -183,17 +136,14 @@ const FilterPage = () => {
async function getDataAll() { async function getDataAll() {
if (asPath?.includes("/polda/") == true) { if (asPath?.includes("/polda/") == true) {
if (asPath?.split("/")[2] !== "[polda_name]") { if (asPath?.split("/")[2] !== "[polda_name]") {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
const filterGroup = group == undefined ? asPath.split("/")[2] : group; const filterGroup = group == undefined ? asPath.split("/")[2] : group;
loading(); loading();
const response = await listData( const response = await listData(
"1", "4",
name, name,
filter, filter,
12, 12,
@ -204,14 +154,8 @@ const FilterPage = () => {
filterGroup, filterGroup,
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter) monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
?.split("/")[0]
?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
// setGetTotalPage(response?.data?.data?.totalPages); // setGetTotalPage(response?.data?.data?.totalPages);
@ -225,16 +169,13 @@ const FilterPage = () => {
setTotalContent(response?.data?.data?.totalElements); setTotalContent(response?.data?.data?.totalElements);
} }
} else { } else {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
loading(); loading();
const response = await listData( const response = await listData(
"1", "4",
name, name,
filter, filter,
12, 12,
@ -245,12 +186,8 @@ const FilterPage = () => {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
// setGetTotalPage(response?.data?.data?.totalPages); // setGetTotalPage(response?.data?.data?.totalPages);
@ -300,10 +237,7 @@ const FilterPage = () => {
}; };
async function getDataRegional() { async function getDataRegional() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -316,12 +250,8 @@ const FilterPage = () => {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
12, 12,
pages, pages,
sortByOpt sortByOpt
@ -431,10 +361,10 @@ const FilterPage = () => {
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black"> <div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black">
<p> <p>
{" "} {" "}
Audio {">"} <span className="font-bold">Semua Audio</span> Audio {">"} <span className="font-bold">{t("allAudio")}</span>
</p> </p>
<p className="font-bold">|</p> <p className="font-bold">|</p>
<p>{`Terdapat ${totalContent} artikel berisi Audio yang dapat diunduh`}</p> <p>{`${t("thereIs")} ${totalContent} ${t("downloadableAudio")}`}</p>
</div> </div>
{/* Left */} {/* Left */}
@ -447,11 +377,8 @@ const FilterPage = () => {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label <label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
htmlFor="search" {t("search")}
className="block text-sm font-medium text-gray-700 dark:text-white"
>
Pencarian
</label> </label>
<Input <Input
value={searchTitle} value={searchTitle}
@ -460,79 +387,49 @@ const FilterPage = () => {
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
type="text" type="text"
id="search" id="search"
placeholder="Cari judul..." placeholder={t("searchTitle")}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full text-sm border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label>
Tahun & Bulan
</label>
<ReactDatePicker <ReactDatePicker
selected={monthYearFilter} selected={monthYearFilter}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
onChange={(date) => setMonthYearFilter(date)} onChange={(date) => setMonthYearFilter(date)}
dateFormat="MM | yyyy" dateFormat="MM | yyyy"
placeholderText="Pilih Tahun dan Bulan" placeholderText={t("selectYear")}
showMonthYearPicker showMonthYearPicker
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("date")}</label>
Tanggal
</label>
<div className="flex flex-row justify justify-between gap-2"> <div className="flex flex-row justify justify-between gap-2">
<ReactDatePicker <ReactDatePicker
selectsRange selectsRange
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
startDate={dateRange[0]} startDate={dateRange[0]}
endDate={dateRange[1]} endDate={dateRange[1]}
onChange={(update) => { onChange={(update) => {
setDateRange(update); setDateRange(update);
}} }}
placeholderText="Pilih Tanggal" placeholderText={t("searchDate")}
onCalendarClose={() => setCalenderState(!calenderState)} onCalendarClose={() => setCalenderState(!calenderState)}
/> />
<div className="flex items-center"> <div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
{handleClose ? (
<Icon
icon="carbon:close-filled"
onClick={handleDeleteDate}
width="20"
inline
color="#216ba5"
/>
) : (
""
)}
</div>
</div> </div>
</div> </div>
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white"> <h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3>
Kategori
</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{categories.map((category: any) => ( {categories.map((category: any) => (
<li key={category?.id}> <li key={category?.id}>
<label <label className="inline-flex items-center" htmlFor={`${category.id}`}>
className="inline-flex items-center" <Checkbox id={`${category.id}`} value={category.id} checked={categoryFilter.includes(String(category.id))} onCheckedChange={(e) => handleCategoryFilter(Boolean(e), category.id)} />
htmlFor={`${category.id}`} <span className="ml-2 text-gray-700 dark:text-white">{category?.name}</span>
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label> </label>
</li> </li>
))} ))}
@ -542,48 +439,25 @@ const FilterPage = () => {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */} {/* Garis */}
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white"> <h3 className="text-sm font-medium text-gray-700 dark:text-white">Format</h3>
Format Audio
</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="png" value="png" checked={formatFilter.includes("wav")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "wav")} />
id="png" <span className="ml-2 text-gray-700 dark:text-white">WAV</span>
value="png"
checked={formatFilter.includes("wav")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "wav")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
WAV
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="jpeg" value="jpeg" checked={formatFilter.includes("mp3")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "mp3")} />
id="jpeg" <span className="ml-2 text-gray-700 dark:text-white">MP3</span>
value="jpeg"
checked={formatFilter.includes("mp3")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "mp3")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
MP3
</span>
</label> </label>
</li> </li>
</ul> </ul>
</div> </div>
<div className="border-t border-black dark:border-white my-4"></div> <div className="border-t border-black dark:border-white my-4"></div>
<div className="text-center"> <div className="text-center">
<a <a onClick={cleanCheckbox} className="text-[#bb3523] cursor-pointer">
onClick={cleanCheckbox}
className="text-[#bb3523] cursor-pointer"
>
<b>Reset Filter</b> <b>Reset Filter</b>
</a> </a>
</div> </div>
@ -594,14 +468,10 @@ const FilterPage = () => {
<Reveal> <Reveal>
<div className="flex-1"> <div className="flex-1">
<div className="flex flex-col items-end mb-4"> <div className="flex flex-col items-end mb-4">
<h2 className="text-lg font-semibold">Urutkan berdasarkan</h2> <h2 className="text-lg font-semibold">{t("sortBy")} </h2>
<select <select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} <option value="latest">{t("latest")}</option>
onChange={(e) => handleSorting(e)} <option value="popular">{t("mostPopular")}</option>
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
>
<option value="terbaru">Terbaru</option>
<option value="terpopuler">Terpopuler</option>
</select> </select>
</div> </div>
@ -611,23 +481,11 @@ const FilterPage = () => {
<Carousel className="w-full max-w-7xl mx-auto"> <Carousel className="w-full max-w-7xl mx-auto">
<CarouselContent> <CarouselContent>
{audioData?.map((audio: any) => ( {audioData?.map((audio: any) => (
<CarouselItem <CarouselItem key={audio?.id} className="md:basis-1/2 lg:basis-1/3">
key={audio?.id}
className="md:basis-1/2 lg:basis-1/3"
>
<div className="flex flex-row gap-6"> <div className="flex flex-row gap-6">
<Link <Link href={`/audio/detail/${audio?.slug}`} className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full">
href={`/audio/detail/${audio?.slug}`}
className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16"> <div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
<svg <svg width="32" height="34" viewBox="0 0 32 34" fill="null" xmlns="http://www.w3.org/2000/svg">
width="32"
height="34"
viewBox="0 0 32 34"
fill="null"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z" d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
fill="white" fill="white"
@ -637,20 +495,9 @@ const FilterPage = () => {
<div className="flex flex-col flex-1"> <div className="flex flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm"> <div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
{formatDateToIndonesian( {formatDateToIndonesian(new Date(audio?.createdAt))} {audio?.timezone ? audio?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {audio?.clickCount}{" "}
new Date(audio?.createdAt)
)}{" "}
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
<Icon
icon="formkit:eye"
width="15"
height="15"
/>{" "}
{audio?.clickCount}{" "}
</div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
{audio?.title}
</div> </div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{audio?.title}</div>
</div> </div>
</Link> </Link>
</div> </div>
@ -664,19 +511,11 @@ const FilterPage = () => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center text-black"> <p className="flex items-center justify-center text-black">
<img <img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
src="/assets/empty-data.png"
alt="empty"
className="h-60 w-60 my-4"
/>
</p> </p>
)} )}
<LandingPagination <LandingPagination table={table} totalData={totalData} totalPage={totalPage} />
table={table}
totalData={totalData}
totalPage={totalPage}
/>
</div> </div>
</Reveal> </Reveal>
</div> </div>

View File

@ -1,12 +1,15 @@
"use client"; "use client";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { getDetail } from "@/service/landing/landing";
import NewContent from "@/components/landing-page/new-content"; import NewContent from "@/components/landing-page/new-content";
import { Link } from "@/i18n/routing";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { getCookiesDecrypt } from "@/lib/utils";
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
import { close, error, loading } from "@/config/swal";
import { useToast } from "@/components/ui/use-toast";
import { Link, useRouter } from "@/i18n/routing";
const DetailDocument = () => { const DetailDocument = () => {
const [selectedSize, setSelectedSize] = useState<string>("L"); const [selectedSize, setSelectedSize] = useState<string>("L");
@ -14,20 +17,101 @@ const DetailDocument = () => {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const params = useParams(); const params = useParams();
const slug = params?.slug; const slug = String(params?.slug);
const [detailDataDocument, setDetailDataDocument] = useState<any>(); const [detailDataDocument, setDetailDataDocument] = useState<any>();
const [selectedDocument, setSelectedDocument] = useState(0); const [selectedDocument, setSelectedDocument] = useState(0);
const [isSaved, setIsSaved] = useState(false);
const [wishlistId, setWishlistId] = useState();
const { toast } = useToast();
const [isDownloadAll, setIsDownloadAll] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const [isFromSPIT, setIsFromSPIT] = useState(false);
const [main, setMain] = useState<any>();
const [resolutionSelected, setResolutionSelected] = useState("720");
const [imageSizeSelected, setImageSizeSelected] = useState("l");
const userId = getCookiesDecrypt("uie");
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
checkWishlist();
}, []); }, []);
const initFetch = async () => { const initFetch = async () => {
const response = await getDetail(String(slug)); const response = await getDetail(String(slug));
console.log("detailAudio", response); console.log("detailDocument", response);
setIsFromSPIT(response?.data?.data?.isFromSPIT);
setMain({
id: response?.data?.data?.files[0]?.id,
type: response?.data?.data?.fileType.name,
url:
Number(response?.data?.data?.fileType?.id) == 4
? response?.data?.data?.files[0]?.secondaryUrl
: Number(response?.data?.data?.fileType?.id) == 2
? `${process.env.NEXT_PUBLIC_API}/media/view?id=${response?.data?.data?.files[0]?.id}&operation=file&type=video`
: response?.data?.data?.files[0]?.url,
thumbnailFileUrl: response?.data?.data?.files[0]?.thumbnailFileUrl,
names: response?.data?.data?.files[0]?.fileName,
format: response?.data?.data?.files[0]?.format,
widthPixel: response?.data?.data?.files[0]?.widthPixel,
heightPixel: response?.data?.data?.files[0]?.heightPixel,
size: response?.data?.data?.files[0]?.size,
caption: response?.data?.data?.files[0]?.caption,
});
setDetailDataDocument(response?.data?.data); setDetailDataDocument(response?.data?.data);
}; };
const doBookmark = async () => {
if (userId) {
const data = {
mediaUploadId: slug?.split("-")?.[0],
};
loading();
const res = await saveWishlist(data);
if (res?.error) {
error(res.message);
return false;
}
close();
toast({
title: "Konten berhasil disimpan",
});
checkWishlist();
} else {
router.push("/auth");
}
};
async function checkWishlist() {
if (userId) {
const res = await checkWishlistStatus(slug.split("-")?.[0]);
console.log(res?.data?.data);
const isAlreadyOnWishlist = res?.data?.data !== "-1";
setWishlistId(res?.data?.data);
setIsSaved(isAlreadyOnWishlist);
}
}
const handleDeleteWishlist = async () => {
if (userId) {
loading();
const res = await deleteWishlist(wishlistId);
if (res?.error) {
error(res.message);
return false;
}
toast({
title: "Konten berhasil dihapus",
});
close();
checkWishlist();
} else {
router.push("/auth");
}
};
const sizes = [ const sizes = [
{ label: "XL", value: "3198 x 1798 px" }, { label: "XL", value: "3198 x 1798 px" },
{ label: "L", value: "2399 x 1349 px" }, { label: "L", value: "2399 x 1349 px" },
@ -36,6 +120,102 @@ const DetailDocument = () => {
{ label: "XS", value: "800 x 450 px" }, { label: "XS", value: "800 x 450 px" },
]; ];
async function sendActivityLog(activityTypeId: number) {
const data = {
activityTypeId,
mediaId: slug.split("-")?.[0],
url: window.location.href,
};
// set activity
// const response = await postActivityLog(data, token);
// console.log(response);
}
const handleDownload = () => {
if (downloadProgress === 0) {
if (!userId) {
router.push("/auth/login");
} else {
sendActivityLog(2);
sendActivityLog(3);
if (isDownloadAll) {
let url: string;
const baseId = slug.split("-")?.[0];
// if (type === "1") {
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${resolutionSelected}`;
// } else if (type === "2") {
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${imageSizeSelected}`;
// } else {
url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}`;
// }
downloadFile(url, "FileDownload.zip");
} else {
if (isFromSPIT && main?.url?.includes("spit.humas")) {
downloadFile(`${main?.url}`, `${main.names}`);
} else {
downloadFile(`${main?.url}`, `${main.names}`);
}
}
// } else if (type === "1" && resolutionSelected?.length > 0) {
// if (isFromSPIT && main?.url?.includes("spit.humas")) {
// downloadFile(`${main?.url}`, `${main.names}`);
// } else {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
// downloadFile(url, `${main.names}`);
// }
// } else if (type === "2" && imageSizeSelected?.length > 0) {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=image&resolution=${imageSizeSelected}`;
// downloadFile(url, `${main.names}`);
// } else if (type === "3" || type === "4") {
// downloadFile(`${main?.url}`, `${main.names}`);
// }
}
}
};
const downloadFile = (fileUrl: string, name: string) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", fileUrl, true);
xhr.responseType = "blob";
xhr.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percentCompleted = Math.round((event.loaded / event.total) * 100);
setDownloadProgress(percentCompleted);
}
});
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`;
const blob = new Blob([xhr.response], {
type: contentType,
});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
}
});
xhr.onloadend = () => {
setDownloadProgress(0);
};
xhr.send();
};
return ( return (
<> <>
<div className="px-4 md:px-24 py-4"> <div className="px-4 md:px-24 py-4">
@ -66,22 +246,31 @@ const DetailDocument = () => {
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] h-fit rounded-lg mx-4"> <div className="md:w-1/4 p-4 bg-[#f7f7f7] h-fit rounded-lg mx-4">
<div className="flex flex-col mb-3 items-center justify-center cursor-pointer"> {isSaved ? (
<svg xmlns="http://www.w3.org/2000/svg" width="2.5em" height="2.5em" viewBox="0 0 24 24"> <a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<path fill="black" d="m17 18l-5-2.18L7 18V5h10m0-2H7a2 2 0 0 0-2 2v16l7-3l7 3V5a2 2 0 0 0-2-2" /> <Icon icon="material-symbols:bookmark" width={40} />
</svg> <p className="text-base lg:text-lg">Hapus</p>
<p className="text-base lg:text-lg">Simpan</p> </a>
</div> ) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<Icon icon="material-symbols:bookmark-outline" width={40} />
<p className="text-base lg:text-lg">Simpan</p>
</a>
)}
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<Link href="" className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"> <Link href={`/all/filter?title=polda&category=${detailDataDocument?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded">
{detailDataDocument?.category?.name} {detailDataDocument?.category?.name}
</Link> </Link>
<div className="flex justify-center flex-wrap gap-2 mb-4"> <div className="flex justify-center flex-wrap gap-2 mb-4">
<p className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">poldajabar</p> {detailDataDocument?.tags?.split(",").map((tag: string) => (
<p className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">pilkadamai2024</p> <a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">
{tag}
</a>
))}
</div> </div>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
@ -90,26 +279,29 @@ const DetailDocument = () => {
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4> <h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size) => ( {sizes.map((size: any) => (
<label key={size.label} className="flex items-center space-x-2 cursor-pointer"> <div className="flex flex-row justify-between">
<input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" /> <div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer">
<div className="text-sm"> <input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" />
{size.label} ----------------- {size.value} <div className="text-sm">{size.label}</div>
</div> </div>
</label> <div className="">
<div className="text-sm">{size.value}</div>
</div>
</div>
))} ))}
</div> </div>
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input type="checkbox" className="text-red-600 focus:ring-red-600" /> <input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
<span>Download Semua File?</span> <span>Download Semua File?</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"> <button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" /> <path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
</svg> </svg>
@ -118,6 +310,7 @@ const DetailDocument = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="w-full mb-8"> <div className="w-full mb-8">
{/* Comment */} {/* Comment */}
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]"> <div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">

View File

@ -14,6 +14,7 @@ import { Input } from "@/components/ui/input";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { useTranslations } from "next-intl";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -48,6 +49,7 @@ const FilterPage = () => {
const categorie = searchParams?.get("category"); const categorie = searchParams?.get("category");
const group = searchParams?.get("group"); const group = searchParams?.get("group");
const [, setGetTotalPage] = useState(); const [, setGetTotalPage] = useState();
const t = useTranslations("FilterPage");
let typingTimer: any; let typingTimer: any;
const doneTypingInterval = 1500; const doneTypingInterval = 1500;
const [contentDocument, setContentDocument] = useState([]); const [contentDocument, setContentDocument] = useState([]);
@ -371,15 +373,15 @@ const FilterPage = () => {
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black"> <div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black">
<p> <p>
{" "} {" "}
Teks {">"} <span className="font-bold">Semua Teks</span> {t("text")} {">"} <span className="font-bold">{t("allText")}</span>
</p> </p>
<p className="font-bold">|</p> <p className="font-bold">|</p>
<p>{`Terdapat ${totalContent} artikel berisi Teks yang dapat diunduh`}</p> <p>{`${t("thereIs")} ${totalContent} ${t("downloadableText")}`}</p>
</div> </div>
{/* Left */} {/* Left */}
<div className="flex flex-col lg:flex-row gap-6 p-4"> <div className="flex flex-col lg:flex-row gap-6 p-4">
<div className="lg:w-[40%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md"> <div className="lg:w-[25%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1"> <h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
<Icon icon="stash:filter-light" fontSize={30} /> <Icon icon="stash:filter-light" fontSize={30} />
Filter Filter
@ -388,7 +390,7 @@ const FilterPage = () => {
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white"> <label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
Pencarian {t("search")}
</label> </label>
<Input <Input
value={searchTitle} value={searchTitle}
@ -397,35 +399,35 @@ const FilterPage = () => {
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
type="text" type="text"
id="search" id="search"
placeholder="Cari judul..." placeholder={t("searchTitle")}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">Tahun & Bulan</label> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label>
<ReactDatePicker <ReactDatePicker
selected={monthYearFilter} selected={monthYearFilter}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
onChange={(date) => setMonthYearFilter(date)} onChange={(date) => setMonthYearFilter(date)}
dateFormat="MM | yyyy" dateFormat="MM | yyyy"
placeholderText="Pilih Tahun dan Bulan" placeholderText={t("selectYear")}
showMonthYearPicker showMonthYearPicker
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">Tanggal</label> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("date")}</label>
<div className="flex flex-row justify justify-between gap-2"> <div className="flex flex-row justify justify-between gap-2">
<ReactDatePicker <ReactDatePicker
selectsRange selectsRange
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
startDate={dateRange[0]} startDate={dateRange[0]}
endDate={dateRange[1]} endDate={dateRange[1]}
onChange={(update) => { onChange={(update) => {
setDateRange(update); setDateRange(update);
}} }}
placeholderText="Pilih Tanggal" placeholderText={t("selectDate")}
onCalendarClose={() => setCalenderState(!calenderState)} onCalendarClose={() => setCalenderState(!calenderState)}
/> />
<div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div> <div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
@ -433,7 +435,7 @@ const FilterPage = () => {
</div> </div>
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">Kategori</h3> <h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{categories.map((category: any) => ( {categories.map((category: any) => (
<li key={category?.id}> <li key={category?.id}>
@ -449,7 +451,7 @@ const FilterPage = () => {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */} {/* Garis */}
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">Format Teks</h3> <h3 className="text-sm font-medium text-gray-700 dark:text-white">Format</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
@ -496,10 +498,10 @@ const FilterPage = () => {
<Reveal> <Reveal>
<div className="flex-1"> <div className="flex-1">
<div className="flex flex-col items-end mb-4"> <div className="flex flex-col items-end mb-4">
<h2 className="text-lg font-semibold">Urutkan berdasarkan</h2> <h2 className="text-lg font-semibold">{t("sortBy")}</h2>
<select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"> <select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
<option value="terbaru">Terbaru</option> <option value="latest">{t("latest")}</option>
<option value="terpopuler">Terpopuler</option> <option value="popular">{t("mostPopular")}</option>
</select> </select>
</div> </div>

View File

@ -1,12 +1,15 @@
"use client"; "use client";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import Link from "next/link"; import { useParams, usePathname } from "next/navigation";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { getDetail } from "@/service/landing/landing";
import NewContent from "@/components/landing-page/new-content"; import NewContent from "@/components/landing-page/new-content";
import { useToast } from "@/components/ui/use-toast";
import { getCookiesDecrypt } from "@/lib/utils";
import { close, error, loading } from "@/config/swal";
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
import { Link, useRouter } from "@/i18n/routing";
const DetailInfo = () => { const DetailInfo = () => {
const [selectedSize, setSelectedSize] = useState<string>("L"); const [selectedSize, setSelectedSize] = useState<string>("L");
@ -14,20 +17,101 @@ const DetailInfo = () => {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const params = useParams(); const params = useParams();
const slug = params?.slug; const slug = String(params?.slug);
const [detailDataImage, setDetailDataImage] = useState<any>(); const [detailDataImage, setDetailDataImage] = useState<any>();
const [selectedImage, setSelectedImage] = useState(0); const [selectedImage, setSelectedImage] = useState(0);
const [isSaved, setIsSaved] = useState(false);
const [wishlistId, setWishlistId] = useState();
const { toast } = useToast();
const [isDownloadAll, setIsDownloadAll] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const [isFromSPIT, setIsFromSPIT] = useState(false);
const [main, setMain] = useState<any>();
const [resolutionSelected, setResolutionSelected] = useState("720");
const [imageSizeSelected, setImageSizeSelected] = useState("l");
const userId = getCookiesDecrypt("uie");
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
checkWishlist();
}, []); }, []);
const initFetch = async () => { const initFetch = async () => {
const response = await getDetail(String(slug)); const response = await getDetail(String(slug));
console.log("detailImage", response); console.log("detailImage", response);
setIsFromSPIT(response?.data?.data?.isFromSPIT);
setMain({
id: response?.data?.data?.files[0]?.id,
type: response?.data?.data?.fileType.name,
url:
Number(response?.data?.data?.fileType?.id) == 4
? response?.data?.data?.files[0]?.secondaryUrl
: Number(response?.data?.data?.fileType?.id) == 2
? `${process.env.NEXT_PUBLIC_API}/media/view?id=${response?.data?.data?.files[0]?.id}&operation=file&type=video`
: response?.data?.data?.files[0]?.url,
thumbnailFileUrl: response?.data?.data?.files[0]?.thumbnailFileUrl,
names: response?.data?.data?.files[0]?.fileName,
format: response?.data?.data?.files[0]?.format,
widthPixel: response?.data?.data?.files[0]?.widthPixel,
heightPixel: response?.data?.data?.files[0]?.heightPixel,
size: response?.data?.data?.files[0]?.size,
caption: response?.data?.data?.files[0]?.caption,
});
setDetailDataImage(response?.data?.data); setDetailDataImage(response?.data?.data);
}; };
const doBookmark = async () => {
if (userId) {
const data = {
mediaUploadId: slug?.split("-")?.[0],
};
loading();
const res = await saveWishlist(data);
if (res?.error) {
error(res.message);
return false;
}
close();
toast({
title: "Konten berhasil disimpan",
});
checkWishlist();
} else {
router.push("/auth");
}
};
async function checkWishlist() {
if (userId) {
const res = await checkWishlistStatus(slug.split("-")?.[0]);
console.log(res?.data?.data);
const isAlreadyOnWishlist = res?.data?.data !== "-1";
setWishlistId(res?.data?.data);
setIsSaved(isAlreadyOnWishlist);
}
}
const handleDeleteWishlist = async () => {
if (userId) {
loading();
const res = await deleteWishlist(wishlistId);
if (res?.error) {
error(res.message);
return false;
}
toast({
title: "Konten berhasil dihapus",
});
close();
checkWishlist();
} else {
router.push("/auth");
}
};
const sizes = [ const sizes = [
{ label: "XL", value: "3198 x 1798 px" }, { label: "XL", value: "3198 x 1798 px" },
{ label: "L", value: "2399 x 1349 px" }, { label: "L", value: "2399 x 1349 px" },
@ -36,6 +120,105 @@ const DetailInfo = () => {
{ label: "XS", value: "800 x 450 px" }, { label: "XS", value: "800 x 450 px" },
]; ];
async function sendActivityLog(activityTypeId: number) {
const data = {
activityTypeId,
mediaId: slug.split("-")?.[0],
url: window.location.href,
};
// set activity
// const response = await postActivityLog(data, token);
// console.log(response);
}
const handleDownload = () => {
if (downloadProgress === 0) {
if (!userId) {
router.push("/auth/login");
} else {
sendActivityLog(2);
sendActivityLog(3);
if (isDownloadAll) {
let url: string;
const baseId = slug.split("-")?.[0];
// if (type === "1") {
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${resolutionSelected}`;
// } else if (type === "2") {
url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}&resolution=${imageSizeSelected}`;
// } else {
// url = `${process.env.NEXT_PUBLIC_API}/media/file/download-zip?id=${baseId}`;
// }
downloadFile(url, "FileDownload.zip");
} else {
if (isFromSPIT && main?.url?.includes("spit.humas")) {
downloadFile(`${main?.url}`, `${main.names}`);
} else {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
// downloadFile(url, `${main.names}`);
const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=image&resolution=${imageSizeSelected}`;
downloadFile(url, `${main.names}`);
}
}
// } else if (type === "1" && resolutionSelected?.length > 0) {
// if (isFromSPIT && main?.url?.includes("spit.humas")) {
// downloadFile(`${main?.url}`, `${main.names}`);
// } else {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=video&resolution=${resolutionSelected}p`;
// downloadFile(url, `${main.names}`);
// }
// } else if (type === "2" && imageSizeSelected?.length > 0) {
// const url = `${process.env.NEXT_PUBLIC_API}/media/view?id=${main?.id}&operation=file&type=image&resolution=${imageSizeSelected}`;
// downloadFile(url, `${main.names}`);
// } else if (type === "3" || type === "4") {
// downloadFile(`${main?.url}`, `${main.names}`);
// }
}
}
};
const downloadFile = (fileUrl: string, name: string) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", fileUrl, true);
xhr.responseType = "blob";
xhr.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percentCompleted = Math.round((event.loaded / event.total) * 100);
setDownloadProgress(percentCompleted);
}
});
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`;
const blob = new Blob([xhr.response], {
type: contentType,
});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
}
});
xhr.onloadend = () => {
setDownloadProgress(0);
};
xhr.send();
};
return ( return (
<> <>
<div className="min-h-screen px-4 md:px-24 py-4"> <div className="min-h-screen px-4 md:px-24 py-4">
@ -76,22 +259,30 @@ const DetailInfo = () => {
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] h-fit rounded-lg mx-4"> <div className="md:w-1/4 p-4 bg-[#f7f7f7] h-fit rounded-lg mx-4">
<div className="flex flex-col mb-3 items-center justify-center cursor-pointer"> {isSaved ? (
<svg xmlns="http://www.w3.org/2000/svg" width="2.5em" height="2.5em" viewBox="0 0 24 24"> <a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<path fill="black" d="m17 18l-5-2.18L7 18V5h10m0-2H7a2 2 0 0 0-2 2v16l7-3l7 3V5a2 2 0 0 0-2-2" /> <Icon icon="material-symbols:bookmark" width={40} />
</svg> <p className="text-base lg:text-lg">Hapus</p>
<button className="text-base lg:text-lg">Simpan</button> </a>
</div> ) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<Icon icon="material-symbols:bookmark-outline" width={40} />
<p className="text-base lg:text-lg">Simpan</p>
</a>
)}
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<Link href="" className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"> <Link href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded">
{detailDataImage?.category?.name} {detailDataImage?.category?.name}
</Link> </Link>
<div className="flex justify-center flex-wrap gap-2 mb-4"> <div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataImage?.tags?.split(",").map((tag: string, index: number) => ( {detailDataImage?.tags?.split(",").map((tag: string) => (
<p key={index} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">{tag}</p> <a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">
{tag}
</a>
))} ))}
</div> </div>
@ -99,30 +290,32 @@ const DetailInfo = () => {
{/* Opsi Ukuran Foto */} {/* Opsi Ukuran Foto */}
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4> <h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size) => ( {sizes.map((size: any) => (
<label key={size?.label} className="flex items-center space-x-2 cursor-pointer"> <div className="flex flex-row justify-between">
<input type="radio" name="size" value={size?.label} checked={selectedSize === size?.label} onChange={() => setSelectedSize(size?.label)} className="text-red-600 focus:ring-red-600" /> <div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer">
<div className="text-sm"> <input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" />
{size?.label}&nbsp;{size?.value} <div className="text-sm">{size.label}</div>
</div> </div>
</label> <div className="">
<div className="text-sm">{size.value}</div>
</div>
</div>
))} ))}
</div> </div>
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input type="checkbox" className="text-red-600 focus:ring-red-600" /> <input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
<span>Download Semua File?</span> <span>Download Semua File?</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"> <button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" /> <path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
</svg> </svg>
@ -131,6 +324,7 @@ const DetailInfo = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="w-full mb-8"> <div className="w-full mb-8">
{/* Comment */} {/* Comment */}
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]"> <div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">

View File

@ -14,6 +14,7 @@ import { Input } from "@/components/ui/input";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { useTranslations } from "next-intl";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -67,6 +68,7 @@ const FilterPage = () => {
const [handleClose, setHandleClose] = useState(false); const [handleClose, setHandleClose] = useState(false);
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const [userLevels, setUserLevels] = useState([]); const [userLevels, setUserLevels] = useState([]);
const t = useTranslations("FilterPage");
// const [startDate, endDate] = dateRange; // const [startDate, endDate] = dateRange;
@ -358,15 +360,15 @@ const FilterPage = () => {
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black"> <div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black">
<p> <p>
{" "} {" "}
Foto {">"} <span className="font-bold">Semua Foto</span> {t("image")} {">"} <span className="font-bold">{t("allImage")}</span>
</p> </p>
<p className="font-bold">|</p> <p className="font-bold">|</p>
<p>{`Terdapat ${totalContent} artikel berisi Foto yang dapat diunduh`}</p> <p>{`${t("thereIs")} ${totalContent} ${t("downloadableImage")}`}</p>
</div> </div>
{/* Left */} {/* Left */}
<div className="flex flex-col lg:flex-row gap-6 p-4"> <div className="flex flex-col lg:flex-row gap-6 p-4">
<div className="lg:w-[40%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md"> <div className="lg:w-[25%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1"> <h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
<Icon icon="stash:filter-light" fontSize={30} /> <Icon icon="stash:filter-light" fontSize={30} />
Filter Filter
@ -375,7 +377,7 @@ const FilterPage = () => {
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white"> <label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
Pencarian {t("search")}
</label> </label>
<Input <Input
value={searchTitle} value={searchTitle}
@ -384,35 +386,35 @@ const FilterPage = () => {
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
type="text" type="text"
id="search" id="search"
placeholder="Cari judul..." placeholder={t("searchTitle")}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">Tahun & Bulan</label> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label>
<ReactDatePicker <ReactDatePicker
selected={monthYearFilter} selected={monthYearFilter}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
onChange={(date) => setMonthYearFilter(date)} onChange={(date) => setMonthYearFilter(date)}
dateFormat="MM | yyyy" dateFormat="MM | yyyy"
placeholderText="Pilih Tahun dan Bulan" placeholderText={t("selectYear")}
showMonthYearPicker showMonthYearPicker
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">Tanggal</label> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("date")}</label>
<div className="flex flex-row justify justify-between gap-2"> <div className="flex flex-row justify justify-between gap-2">
<ReactDatePicker <ReactDatePicker
selectsRange selectsRange
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
startDate={dateRange[0]} startDate={dateRange[0]}
endDate={dateRange[1]} endDate={dateRange[1]}
onChange={(update) => { onChange={(update) => {
setDateRange(update); setDateRange(update);
}} }}
placeholderText="Pilih Tanggal" placeholderText={t("selectDate")}
onCalendarClose={() => setCalenderState(!calenderState)} onCalendarClose={() => setCalenderState(!calenderState)}
/> />
<div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div> <div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
@ -420,7 +422,7 @@ const FilterPage = () => {
</div> </div>
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">Kategori</h3> <h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{categories.map((category: any) => ( {categories.map((category: any) => (
<li key={category?.id}> <li key={category?.id}>
@ -436,7 +438,7 @@ const FilterPage = () => {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */} {/* Garis */}
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">Format Foto</h3> <h3 className="text-sm font-medium text-gray-700 dark:text-white">Format</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
@ -471,10 +473,10 @@ const FilterPage = () => {
<Reveal> <Reveal>
<div className="flex-1"> <div className="flex-1">
<div className="flex flex-col items-end mb-4"> <div className="flex flex-col items-end mb-4">
<h2 className="text-lg font-semibold">Urutkan berdasarkan</h2> <h2 className="text-lg font-semibold">{t("sortBy")}</h2>
<select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"> <select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
<option value="terbaru">Terbaru</option> <option value="latest">{t("latest")}</option>
<option value="terpopuler">Terpopuler</option> <option value="popular">{t("mostPopular")}</option>
</select> </select>
</div> </div>

View File

@ -66,7 +66,7 @@ const Indeks: React.FC = () => {
(indeksRight: any, index: number) => (indeksRight: any, index: number) =>
(index == count + 1 || index == count + 2) && ( (index == count + 1 || index == count + 2) && (
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]"> <div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]">
<img src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg" /> <img src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " />
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg"> <div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{indeksRight?.categoryName}</span> <span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{indeksRight?.categoryName}</span>
<Link href={`/indeks/detail/${indeksRight?.slug}`}> <Link href={`/indeks/detail/${indeksRight?.slug}`}>
@ -96,7 +96,7 @@ const Indeks: React.FC = () => {
(indeksBottom: any, index: number) => (indeksBottom: any, index: number) =>
index < 3 && ( index < 3 && (
<div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4"> <div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4">
<img src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-[500px] lg:h-[300px]" /> <img src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
<div className="flex flex-col justify-between w-full"> <div className="flex flex-col justify-between w-full">
<p className="text-sm">{indeksBottom?.date}</p> <p className="text-sm">{indeksBottom?.date}</p>
<Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800"> <Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800">

View File

@ -3,12 +3,7 @@
import { useParams, usePathname } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
checkWishlistStatus,
deleteWishlist,
getDetail,
saveWishlist,
} from "@/service/landing/landing";
import VideoPlayer from "@/utils/video-player"; import VideoPlayer from "@/utils/video-player";
import NewContent from "@/components/landing-page/new-content"; import NewContent from "@/components/landing-page/new-content";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
@ -196,8 +191,7 @@ const DetailVideo = () => {
xhr.addEventListener("readystatechange", () => { xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream";
xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1]; const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`; const filename = `${name}.${extension}`;
@ -238,11 +232,8 @@ const DetailVideo = () => {
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4"> <div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
<p className="flex flex-row items-center mt-3"> <p className="flex flex-row items-center mt-3">
oleh&nbsp; oleh&nbsp;
<span className="font-semibold text-black"> <span className="font-semibold text-black">{detailDataVideo?.uploadedBy?.userLevel?.name}</span>
{detailDataVideo?.uploadedBy?.userLevel?.name} &nbsp;|&nbsp;Diupdate pada {detailDataVideo?.updatedAt} WIB&nbsp;|&nbsp;
</span>
&nbsp;|&nbsp;Diupdate pada {detailDataVideo?.updatedAt}{" "}
WIB&nbsp;|&nbsp;
<Icon icon="formkit:eye" width="15" height="15" /> <Icon icon="formkit:eye" width="15" height="15" />
&nbsp; &nbsp;
{detailDataVideo?.clickCount} {detailDataVideo?.clickCount}
@ -252,9 +243,7 @@ const DetailVideo = () => {
{/* Keterangan */} {/* Keterangan */}
<div className="w-full"> <div className="w-full">
<h1 className="flex flex-row font-bold text-2xl my-8"> <h1 className="flex flex-row font-bold text-2xl my-8">{detailDataVideo?.title}</h1>
{detailDataVideo?.title}
</h1>
<div <div
className="font-light text-justify" className="font-light text-justify"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -267,18 +256,12 @@ const DetailVideo = () => {
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] rounded-lg mx-4 h-fit"> <div className="md:w-1/4 p-4 bg-[#f7f7f7] rounded-lg mx-4 h-fit">
{isSaved ? ( {isSaved ? (
<a <a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
onClick={() => handleDeleteWishlist()}
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark" width={40} /> <Icon icon="material-symbols:bookmark" width={40} />
<p className="text-base lg:text-lg">Hapus</p> <p className="text-base lg:text-lg">Hapus</p>
</a> </a>
) : ( ) : (
<a <a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
onClick={() => doBookmark()}
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark-outline" width={40} /> <Icon icon="material-symbols:bookmark-outline" width={40} />
<p className="text-base lg:text-lg">Simpan</p> <p className="text-base lg:text-lg">Simpan</p>
</a> </a>
@ -287,20 +270,13 @@ const DetailVideo = () => {
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<Link <Link href={`/all/filter?title=polda&category=${detailDataVideo?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded">
href={`/all/filter?title=polda&category=${detailDataVideo?.category.id}`}
className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"
>
{detailDataVideo?.categoryName} {detailDataVideo?.categoryName}
</Link> </Link>
<div className="flex justify-center flex-wrap gap-2 mb-4"> <div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataVideo?.tags.split(",").map((tag: string) => ( {detailDataVideo?.tags.split(",").map((tag: string) => (
<a <a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500 hover:text-white">
onClick={() => router.push(`/all/filter?tag=${tag}`)}
key={tag}
className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500 hover:text-white"
>
{tag} {tag}
</a> </a>
))} ))}
@ -309,60 +285,36 @@ const DetailVideo = () => {
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
{/* Opsi Ukuran Foto */} {/* Opsi Ukuran Foto */}
<h4 className="flex text-lg justify-center items-center font-semibold my-3"> <h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Audio Visual</h4>
Opsi Ukuran Foto
</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size) => ( {sizes.map((size: any) => (
<label <div className="flex flex-row justify-between">
key={size.label} <div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer">
className="flex items-center space-x-2 cursor-pointer" <input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" />
> <div className="text-sm">{size.label}</div>
<input
type="radio"
name="size"
value={size.label}
checked={selectedSize === size.label}
onChange={() => setSelectedSize(size.label)}
className="text-red-600 focus:ring-red-600"
/>
<div className="text-sm">
{size.label} ----------------- {size.value}
</div> </div>
</label> <div className="">
<div className="text-sm">{size.value}</div>
</div>
</div>
))} ))}
</div> </div>
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input <input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
type="checkbox"
className="text-red-600 focus:ring-red-600"
onChange={() => setIsDownloadAll(!isDownloadAll)}
/>
<span>Download Semua File?</span> <span>Download Semua File?</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button <button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700">
onClick={handleDownload} <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700" <path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="white"
d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"
/>
</svg> </svg>
Download Download
</button> </button>
@ -374,13 +326,8 @@ const DetailVideo = () => {
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]"> <div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
<div className="gap-5 flex flex-col px-4 lg:px-14"> <div className="gap-5 flex flex-col px-4 lg:px-14">
<p className="flex items-start text-lg">Berikan Komentar</p> <p className="flex items-start text-lg">Berikan Komentar</p>
<Textarea <Textarea placeholder="Type your comments here." className="flex w-full" />
placeholder="Type your comments here." <button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">Kirim</button>
className="flex w-full"
/>
<button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">
Kirim
</button>
</div> </div>
</div> </div>

View File

@ -3,31 +3,10 @@ import React, { useEffect, useState } from "react";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { import { formatDateToIndonesian, getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
formatDateToIndonesian,
getOnlyDate,
getOnlyMonthAndYear,
} from "@/utils/globals";
import { useParams, usePathname, useSearchParams } from "next/navigation"; import { useParams, usePathname, useSearchParams } from "next/navigation";
import { import { getListContent, getUserLevelListByParent, listCategory, listData, listDataRegional } from "@/service/landing/landing";
getListContent, import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
getUserLevelListByParent,
listCategory,
listData,
listDataRegional,
} from "@/service/landing/landing";
import {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import LandingPagination from "@/components/landing-page/pagination"; import LandingPagination from "@/components/landing-page/pagination";
import { Reveal } from "@/components/landing-page/Reveal"; import { Reveal } from "@/components/landing-page/Reveal";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
@ -35,6 +14,7 @@ import { Input } from "@/components/ui/input";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { useTranslations } from "next-intl";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -54,11 +34,8 @@ const FilterPage = () => {
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState<number>(1); const [totalPage, setTotalPage] = React.useState<number>(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>([]);
[] const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@ -76,9 +53,7 @@ const FilterPage = () => {
const [categoryFilter, setCategoryFilter] = useState<any>([]); const [categoryFilter, setCategoryFilter] = useState<any>([]);
const [monthYearFilter, setMonthYearFilter] = useState<any>(); const [monthYearFilter, setMonthYearFilter] = useState<any>();
const [searchTitle, setSearchTitle] = useState<string>(""); const [searchTitle, setSearchTitle] = useState<string>("");
const [sortByOpt, setSortByOpt] = useState<any>( const [sortByOpt, setSortByOpt] = useState<any>(sortBy === "popular" ? "clickCount" : "createdAt");
sortBy === "popular" ? "clickCount" : "createdAt"
);
const isRegional = asPath?.includes("regional"); const isRegional = asPath?.includes("regional");
const isSatker = asPath?.includes("satker"); const isSatker = asPath?.includes("satker");
const [formatFilter, setFormatFilter] = useState<any>([]); const [formatFilter, setFormatFilter] = useState<any>([]);
@ -90,6 +65,7 @@ const FilterPage = () => {
const [handleClose, setHandleClose] = useState(false); const [handleClose, setHandleClose] = useState(false);
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const [userLevels, setUserLevels] = useState([]); const [userLevels, setUserLevels] = useState([]);
const t = useTranslations("FilterPage");
// const [startDate, endDate] = dateRange; // const [startDate, endDate] = dateRange;
@ -114,14 +90,8 @@ const FilterPage = () => {
useEffect(() => { useEffect(() => {
if (categorie) { if (categorie) {
setCategoryFilter( setCategoryFilter(categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie] console.log("Kategori", categorie, categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
);
console.log(
"Kategori",
categorie,
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
} }
}, [categorie]); }, [categorie]);
@ -139,19 +109,7 @@ const FilterPage = () => {
} }
console.log(monthYearFilter, "monthFilter"); console.log(monthYearFilter, "monthFilter");
initState(); initState();
}, [ }, [change, asPath, monthYearFilter, page, sortBy, sortByOpt, title, startDateString, endDateString, categorie, formatFilter]);
change,
asPath,
monthYearFilter,
page,
sortBy,
sortByOpt,
title,
startDateString,
endDateString,
categorie,
formatFilter,
]);
async function getCategories() { async function getCategories() {
const category = await listCategory("2"); const category = await listCategory("2");
@ -174,10 +132,7 @@ const FilterPage = () => {
async function getDataAll() { async function getDataAll() {
if (asPath?.includes("/polda/") == true) { if (asPath?.includes("/polda/") == true) {
if (asPath?.split("/")[2] !== "[polda_name]") { if (asPath?.split("/")[2] !== "[polda_name]") {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -195,14 +150,8 @@ const FilterPage = () => {
filterGroup, filterGroup,
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter) monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
?.split("/")[0]
?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
// setGetTotalPage(response?.data?.data?.totalPages); // setGetTotalPage(response?.data?.data?.totalPages);
@ -216,10 +165,7 @@ const FilterPage = () => {
setTotalContent(response?.data?.data?.totalElements); setTotalContent(response?.data?.data?.totalElements);
} }
} else { } else {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -236,12 +182,8 @@ const FilterPage = () => {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: ""
); );
close(); close();
// setGetTotalPage(response?.data?.data?.totalPages); // setGetTotalPage(response?.data?.data?.totalPages);
@ -291,10 +233,7 @@ const FilterPage = () => {
}; };
async function getDataRegional() { async function getDataRegional() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -307,12 +246,8 @@ const FilterPage = () => {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
12, 12,
pages, pages,
sortByOpt sortByOpt
@ -443,16 +378,16 @@ const FilterPage = () => {
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black"> <div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black">
<p> <p>
{" "} {" "}
Audio Visual {">"}{" "} {t("video")}&nbsp;
<span className="font-bold">Semua Audio Visual</span> {">"} <span className="font-bold">{t("allVideo")}</span>
</p> </p>
<p className="font-bold">|</p> <p className="font-bold">|</p>
<p>{`Terdapat ${totalContent} artikel berisi Audio Visual yang dapat diunduh`}</p> <p>{`${t("thereIs")} ${totalContent} ${t("downloadableVideo")}`}</p>
</div> </div>
{/* Left */} {/* Left */}
<div className="flex flex-col lg:flex-row gap-6 p-4"> <div className="flex flex-col lg:flex-row gap-6 p-4">
<div className="lg:w-[40%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md"> <div className="lg:w-[25%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1"> <h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
<Icon icon="stash:filter-light" fontSize={30} /> <Icon icon="stash:filter-light" fontSize={30} />
Filter Filter
@ -460,11 +395,8 @@ const FilterPage = () => {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label <label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
htmlFor="search" {t("search")}
className="block text-sm font-medium text-gray-700 dark:text-white"
>
Pencarian
</label> </label>
<Input <Input
value={searchTitle} value={searchTitle}
@ -473,79 +405,49 @@ const FilterPage = () => {
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
type="text" type="text"
id="search" id="search"
placeholder="Cari judul..." placeholder={t("searchTitle")}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label>
Tahun & Bulan
</label>
<ReactDatePicker <ReactDatePicker
selected={monthYearFilter} selected={monthYearFilter}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
onChange={(date) => setMonthYearFilter(date)} onChange={(date) => setMonthYearFilter(date)}
dateFormat="MM | yyyy" dateFormat="MM | yyyy"
placeholderText="Pilih Tahun dan Bulan" placeholderText={t("selectYear")}
showMonthYearPicker showMonthYearPicker
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-white">{t("date")}</label>
Tanggal
</label>
<div className="flex flex-row justify justify-between gap-2"> <div className="flex flex-row justify justify-between gap-2">
<ReactDatePicker <ReactDatePicker
selectsRange selectsRange
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" className="mt-1 w-full text-sm border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
startDate={dateRange[0]} startDate={dateRange[0]}
endDate={dateRange[1]} endDate={dateRange[1]}
onChange={(update) => { onChange={(update) => {
setDateRange(update); setDateRange(update);
}} }}
placeholderText="Pilih Tanggal" placeholderText={t("selectDate")}
onCalendarClose={() => setCalenderState(!calenderState)} onCalendarClose={() => setCalenderState(!calenderState)}
/> />
<div className="flex items-center"> <div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
{handleClose ? (
<Icon
icon="carbon:close-filled"
onClick={handleDeleteDate}
width="20"
inline
color="#216ba5"
/>
) : (
""
)}
</div>
</div> </div>
</div> </div>
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white"> <h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3>
Kategori
</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{categories.map((category: any) => ( {categories.map((category: any) => (
<li key={category?.id}> <li key={category?.id}>
<label <label className="inline-flex items-center" htmlFor={`${category.id}`}>
className="inline-flex items-center" <Checkbox id={`${category.id}`} value={category.id} checked={categoryFilter.includes(String(category.id))} onCheckedChange={(e) => handleCategoryFilter(Boolean(e), category.id)} />
htmlFor={`${category.id}`} <span className="ml-2 text-gray-700 dark:text-white">{category?.name}</span>
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label> </label>
</li> </li>
))} ))}
@ -555,93 +457,43 @@ const FilterPage = () => {
<div className="border-t border-black my-4 dark:border-white"></div> <div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */} {/* Garis */}
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white"> <h3 className="text-sm font-medium text-gray-700 dark:text-white">Format</h3>
Format Foto
</h3>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="mk4" value="mk4" checked={formatFilter.includes("mk4")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "mk4")} />
id="mk4" <span className="ml-2 text-gray-700 dark:text-white">MK4</span>
value="mk4"
checked={formatFilter.includes("mk4")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "mk4")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
MK4
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="mov" value="mov" checked={formatFilter.includes("mov")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "mov")} />
id="mov" <span className="ml-2 text-gray-700 dark:text-white">MOV</span>
value="mov"
checked={formatFilter.includes("mov")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "mov")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
MOV
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="mp4" value="mp4" checked={formatFilter.includes("mp4")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "mp4")} />
id="mp4" <span className="ml-2 text-gray-700 dark:text-white">MP4</span>
value="mp4"
checked={formatFilter.includes("mp4")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "mp4")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
MP4
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="avi" value="avi" checked={formatFilter.includes("avi")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "avi")} />
id="avi" <span className="ml-2 text-gray-700 dark:text-white">AVI</span>
value="avi"
checked={formatFilter.includes("avi")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "avi")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
AVI
</span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox <Checkbox id="wmv" value="wmv" checked={formatFilter.includes("wmv")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "wmv")} />
id="wmv" <span className="ml-2 text-gray-700 dark:text-white">WMV</span>
value="wmv"
checked={formatFilter.includes("wmv")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "wmv")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
WMV
</span>
</label> </label>
</li> </li>
</ul> </ul>
</div> </div>
<div className="border-t border-black dark:border-white my-4"></div> <div className="border-t border-black dark:border-white my-4"></div>
<div className="text-center"> <div className="text-center">
<a <a onClick={cleanCheckbox} className="text-[#bb3523] cursor-pointer">
onClick={cleanCheckbox}
className="text-[#bb3523] cursor-pointer"
>
<b>Reset Filter</b> <b>Reset Filter</b>
</a> </a>
</div> </div>
@ -652,50 +504,31 @@ const FilterPage = () => {
<Reveal> <Reveal>
<div className="flex-1"> <div className="flex-1">
<div className="flex flex-col items-end mb-4"> <div className="flex flex-col items-end mb-4">
<h2 className="text-lg font-semibold">Urutkan berdasarkan</h2> <h2 className="text-lg font-semibold">{t("sortBy")}</h2>
<select <select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} <option value="latest">{t("latest")}</option>
onChange={(e) => handleSorting(e)} <option value="popular">{t("mostPopular")}</option>
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
>
<option value="terbaru">Terbaru</option>
<option value="terpopuler">Terpopuler</option>
</select> </select>
</div> </div>
{videoData?.length > 0 ? ( {videoData?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{videoData?.map((video: any) => ( {videoData?.map((video: any) => (
<Card <Card key={video?.id} className="hover:scale-110 transition-transform duration-300">
key={video?.id}
className="hover:scale-110 transition-transform duration-300"
>
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0"> <CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<Link href={`/video/detail/${video?.slug}`}> <Link href={`/video/detail/${video?.slug}`}>
<img <img src={video?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center" />
src={video?.thumbnailLink}
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center"
/>
<div className="flex flex-row items-center gap-2 text-[10px] mx-2"> <div className="flex flex-row items-center gap-2 text-[10px] mx-2">
{formatDateToIndonesian(new Date(video?.createdAt))}{" "} {formatDateToIndonesian(new Date(video?.createdAt))} {video?.timezone ? video?.timezone : "WIB"}| <Icon icon="formkit:eye" width="15" height="15" />
{video?.timezone ? video?.timezone : "WIB"}|{" "}
<Icon icon="formkit:eye" width="15" height="15" />
{video?.clickCount}{" "} {video?.clickCount}{" "}
<svg <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 20 20">
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 20 20"
>
<path <path
fill="#f00" fill="#f00"
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z" d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
/> />
</svg>{" "} </svg>{" "}
</div> </div>
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full"> <div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">{video?.title}</div>
{video?.title}
</div>
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>
@ -703,19 +536,11 @@ const FilterPage = () => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<img <img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
src="/assets/empty-data.png"
alt="empty"
className="h-60 w-60 my-4"
/>
</p> </p>
)} )}
<LandingPagination <LandingPagination table={table} totalData={totalData} totalPage={totalPage} />
table={table}
totalData={totalData}
totalPage={totalPage}
/>
</div> </div>
</Reveal> </Reveal>
</div> </div>

View File

@ -0,0 +1,755 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
rejectFiles,
submitApproval,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import {
DialogHeader,
DialogFooter,
Dialog,
DialogContent,
DialogTitle,
} from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";
import { loading } from "@/config/swal";
import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
name: string;
};
type FileType = {
id: number;
url: string;
thumbnailFileUrl: string;
fileName: string;
};
type Detail = {
id: string;
title: string;
description: string;
slug: string;
category: {
id: number;
name: string;
};
categoryName: string;
creatorName: string;
thumbnailLink: string;
tags: string;
statusName: string;
isPublish: boolean;
needApprovalFromLevel: number;
files: FileType[];
uploadedById: number;
};
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function FormAudioDetail() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const [modalOpen, setModalOpen] = useState(false);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [status, setStatus] = useState("");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [detail, setDetail] = useState<any>();
const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [description, setDescription] = useState("");
const [main, setMain] = useState<any>([]);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [isMabesApprover, setIsMabesApprover] = useState(false);
let fileTypeId = "4";
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<ImageSchema>({
resolver: zodResolver(imageSchema),
});
// const handleKeyDown = (e: any) => {
// const newTag = e.target.value.trim(); // Ambil nilai input
// if (e.key === "Enter" && newTag) {
// e.preventDefault(); // Hentikan submit form
// if (!tags.includes(newTag)) {
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
// setValue("tags", ""); // Kosongkan input
// }
// }
// };
useEffect(() => {
if (
userLevelId != undefined &&
roleId != undefined &&
userLevelId == "216" &&
roleId == "3"
) {
setIsMabesApprover(true);
}
}, [userLevelId, roleId]);
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
useEffect(() => {
async function initState() {
getCategories();
}
initState();
}, []);
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category?.data.data.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response?.data?.data;
console.log("detail", details);
setFiles(details?.files);
setDetail(details);
setMain({
type: details?.fileType.name,
url: details?.files[0]?.url,
names: details?.files[0]?.fileName,
format: details?.files[0]?.format,
});
if (details.publishedForObject) {
const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id
);
setSelectedPublishers(publisherIds);
}
const matchingCategory = categories.find(
(category) => category.id === details.categoryId
);
if (matchingCategory) {
setSelectedTarget(matchingCategory.name);
}
setSelectedTarget(details.categoryId); // Untuk dropdown
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrls);
}
}
initState();
}, [refresh, setValue]);
const actionApproval = (e: string) => {
setStatus(e);
setModalOpen(true);
setDescription("");
};
const submit = async () => {
if (
(description?.length > 1 && Number(status) == 3) ||
Number(status) == 2 ||
Number(status) == 4
) {
MySwal.fire({
title: "Simpan Approval",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save();
}
});
}
};
async function save() {
const data = {
mediaUploadId: id,
statusId: status,
message: description,
files: [],
// files: isMabesApprover ? getPlacement() : [],
};
loading();
const response = await submitApproval(data);
if (response?.error) {
error(response.message);
return false;
}
const dataReject = {
listFiles: rejectedFiles,
};
const resReject = await rejectFiles(dataReject);
if (resReject?.error) {
error(resReject.message);
return false;
}
close();
return false;
}
function handleDeleteFileApproval(id: number) {
const selectedFiles = files.filter((file) => file.id != id);
setFiles(selectedFiles);
const rejects = rejectedFiles;
rejects.push(id);
setRejectedFiles(rejects);
}
const handleMain = (
type: string,
url: string,
names: string,
format: string
) => {
console.log("Test 3 :", type, url, names, format);
setMain({
type,
url,
names,
format,
});
return false;
};
const submitApprovalSuccesss = () => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/in/contributor/content/image");
});
};
return (
<form>
{detail !== undefined ? (
<div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Select
value={detail?.category.name} // Nilai default berdasarkan detail
onValueChange={(id) => {
console.log("Selected Category:", id);
setSelectedTarget(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.name}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="py-3">
<Label>Deskripsi</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<ViewEditor initialData={detail?.htmlDescription} />
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<Label className="text-xl text-black">File Media</Label>
<div className="w-full ">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-fill h-full w-full rounded-md"
src={data}
alt={` ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
<div className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-cover h-[60px] w-[80px]"
src={data}
alt={` ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[800px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Kreator</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.creatorName}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags
?.split(",")
.map((tag: string, index: number) => (
<Badge
key={index}
className="border rounded-md px-2 py-2"
>
{tag.trim()}
</Badge>
))}
</div>
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
<Checkbox
id="5"
checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)}
/>
<Label htmlFor="5">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="6"
checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)}
/>
<Label htmlFor="6">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
</div>
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">Kotak Saran (0)</p>
</div>
<div className="px-3 py-3 border mx-3">
<p>Keterangan:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p>
</div>
{/* {detail?.isPublish == false ? (
<div className="p-3">
<Button className="bg-blue-600">Publish</Button>
</div>
) : (
""
)} */}
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
Number(detail?.uploadedById) == Number(userId) ? (
""
) : (
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Setujui
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" />
Tolak
</Button>
</div>
)
) : (
""
)}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle>
</DialogHeader>
{status == "2"
? files?.map((file) => (
<div
key={file.id}
className="flex flex-row gap-2 items-center"
>
<img src={file.url} className="w-[200px]" />
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between text-sm">
{file.fileName}
<a>
<Icon icon="humbleicons:times" color="red" />
</a>
</div>
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Semua
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Nasional
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Wilayah
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Internasional
</label>
</div>
</div>
</div>
</div>
))
: ""}
<div className="flex flex-col gap-4">
<Textarea
placeholder="Type your message here."
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
{status == "3" || status == "4" ? (
<div className="flex flex-row gap-2">
<Badge
color={
description === "Kualitas media kurang baik"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() =>
setDescription("Kualitas media kurang baik")
}
>
Kualitas media kurang baik
</Badge>
<Badge
color={
description === "Deskripsi kurang lengkap"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() =>
setDescription("Deskripsi kurang lengkap")
}
>
Deskripsi kurang lengkap
</Badge>
<Badge
color={
description === "Judul kurang tepat"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() => setDescription("Judul kurang tepat")}
>
Judul kurang tepat
</Badge>
</div>
) : (
<div className="flex flex-row gap-2">
<Badge
color={
description === "Konten sangat bagus"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() => setDescription("Konten sangat bagus")}
>
Konten sangat bagus
</Badge>
<Badge
color={
description === "Konten menarik"
? "primary"
: "default"
}
className="cursor-pointer"
onClick={() => setDescription("Konten menarik")}
>
Konten menarik
</Badge>
</div>
)}
<DialogFooter>
<Button
type="button"
color="primary"
onClick={submitApprovalSuccesss}
>
Submit
</Button>
<Button
type="button"
color="destructive"
onClick={() => setModalOpen(false)}
>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card>
</div>
</div>
) : (
""
)}
</form>
);
}

View File

@ -51,6 +51,7 @@ import { CloudUpload } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Item } from "@radix-ui/react-dropdown-menu"; import { Item } from "@radix-ui/react-dropdown-menu";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -70,6 +71,18 @@ type Category = {
name: string; name: string;
}; };
type Option = {
id: string;
label: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormAudio() { export default function FormAudio() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -94,6 +107,7 @@ export default function FormAudio() {
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
@ -125,6 +139,15 @@ export default function FormAudio() {
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
{ id: "6", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
];
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => { onDrop: (acceptedFiles) => {
@ -268,11 +291,11 @@ export default function FormAudio() {
setIsGeneratedArticle(true); setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => { setArticleIds((prevIds: string[]) => {
if (prevIds.length < 5) { if (prevIds.length < 3) {
return [...prevIds, newArticleId]; return [...prevIds, newArticleId];
} else { } else {
const updatedIds = [...prevIds]; const updatedIds = [...prevIds];
updatedIds[4] = newArticleId; updatedIds[2] = newArticleId;
return updatedIds; return updatedIds;
} }
}); });
@ -281,19 +304,41 @@ export default function FormAudio() {
}; };
const handleArticleIdClick = async (id: string) => { const handleArticleIdClick = async (id: string) => {
const res = await getDetailArticle(id); setIsLoadingData(true);
const articleData = res?.data?.data; let retryCount = 0;
const maxRetries = 20;
const cleanArticleBody = articleData?.articleBody?.replace( try {
/<img[^>]*>/g, const waitForStatusUpdate = async () => {
"" while (retryCount < maxRetries) {
); const res = await getDetailArticle(id);
const articleImagesData = articleData?.imagesUrl?.split(","); const articleData = res?.data?.data;
setArticleBody(cleanArticleBody || ""); if (articleData?.status === 2) {
setDetailData(articleData); return articleData;
setSelectedArticleId(id); }
setArticleImages(articleImagesData || []);
retryCount++;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Timeout: Artikel belum selesai diproses.");
};
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
} catch (error) {
console.error("Error fetching article details:", error);
} finally {
setIsLoadingData(false);
}
}; };
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -350,6 +395,33 @@ export default function FormAudio() {
} }
}; };
const handleCheckboxChange = (id: string): void => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
}
};
const save = async (data: ImageSchema) => { const save = async (data: ImageSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
@ -364,7 +436,7 @@ export default function FormAudio() {
subCategoryId: selectedCategory, subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1", statusId: "1",
publishedFor: "6", publishedFor: publishedFor.join(","),
creatorName: data.creatorName, creatorName: data.creatorName,
tags: finalTags, tags: finalTags,
isYoutube: false, isYoutube: false,
@ -509,7 +581,7 @@ export default function FormAudio() {
setIsStartUpload(false); setIsStartUpload(false);
// hideProgress(); // hideProgress();
Cookies.remove("idCreate"); Cookies.remove("idCreate");
successSubmit("/in/contributor/content/audio/"); successSubmit("/in/contributor/content/audio");
} }
} }
@ -810,17 +882,17 @@ export default function FormAudio() {
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <button
key={index} key={index}
className={`btn mr-3 ${ className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id selectedArticleId === id
? "bg-green-500 text-white px-3 py-2 rounded-md" ? "bg-green-500 text-white"
: "border-2 border-green-500 text-green-500 px-3 py-2 rounded-md" : "border-2 border-green-500 text-green-500"
}`} }`}
onClick={() => handleArticleIdClick(id)} onClick={() => handleArticleIdClick(id)}
> >
{id} {id}
</p> </button>
))} ))}
</div> </div>
)} )}
@ -861,14 +933,18 @@ export default function FormAudio() {
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) =>
<JoditEditor isLoadingData ? (
ref={editor} <div className="flex justify-center items-center h-40">
value={articleBody || value} <p className="text-gray-500">Loading Proses Data...</p>
onChange={onChange} </div>
className="dark:text-black" ) : (
/> <CustomEditor
)} onChange={onChange}
initialData={articleBody || value}
/>
)
}
/> />
{errors.description?.message && ( {errors.description?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">
@ -977,28 +1053,24 @@ export default function FormAudio() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-3">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option) => (
<Checkbox id="all" /> <div key={option.id} className="flex gap-2 items-center">
<Label htmlFor="all">SEMUA</Label> <Checkbox
</div> id={option.id}
<div className="flex gap-2 items-center"> checked={
<Checkbox id="umum" /> option.id === "all"
<Label htmlFor="umum">UMUM</Label> ? publishedFor.length ===
</div> options.filter((opt: any) => opt.id !== "all")
<div className="flex gap-2 items-center"> .length
<Checkbox id="journalist" /> : publishedFor.includes(option.id)
<Label htmlFor="journalist">JOURNALIS</Label> }
</div> onCheckedChange={() => handleCheckboxChange(option.id)}
<div className="flex gap-2 items-center"> />
<Checkbox id="polri" /> <Label htmlFor={option.id}>{option.label}</Label>
<Label htmlFor="polri">POLRI</Label> </div>
</div> ))}
<div className="flex gap-2 items-center">
<Checkbox id="ksp" />
<Label htmlFor="ksp">KSP</Label>
</div>
</div> </div>
</div> </div>
</Card> </Card>

View File

@ -0,0 +1,430 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
name: string;
};
type Detail = {
id: string;
title: string;
description: string;
slug: string;
categoryId: {
id: number;
name: string;
};
creatorName: string;
categoryName: string;
thumbnailLink: string;
tags: string;
};
export default function FormAudioUpdate() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let fileTypeId = "4";
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<ImageSchema>({
resolver: zodResolver(imageSchema),
});
// const handleKeyDown = (e: any) => {
// const newTag = e.target.value.trim(); // Ambil nilai input
// if (e.key === "Enter" && newTag) {
// e.preventDefault(); // Hentikan submit form
// if (!tags.includes(newTag)) {
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
// setValue("tags", ""); // Kosongkan input
// }
// }
// };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const files = Array.from(event.target.files);
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
console.log("DATAFILE::", selectedFiles);
}
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
useEffect(() => {
async function initState() {
getCategories();
}
initState();
}, []);
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category.data.data.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
setDetail(details);
if (details.publishedForObject) {
const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id
);
setSelectedPublishers(publisherIds);
}
const matchingCategory = categories.find(
(category) => category.id === details.categoryId
);
if (matchingCategory) {
setSelectedTarget(matchingCategory.name);
}
setSelectedTarget(details.categoryId); // Untuk dropdown
}
}
initState();
}, [refresh, setValue]);
const save = async (data: ImageSchema) => {
const requestData = {
...data,
id: detail?.id,
title: data.title,
description: data.description,
htmlDescription: data.description,
fileTypeId,
categoryId: selectedTarget,
subCategoryId: selectedTarget,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1",
publishedFor: "6",
creatorName: data.creatorName,
tags: "siap",
isYoutube: false,
isInternationalMedia: false,
};
const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/audio");
});
};
const onSubmit = (data: ImageSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
<div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Select
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
onValueChange={(id) => {
console.log("Selected Category:", id);
setSelectedTarget(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="py-3">
<Label>Deskripsi</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[800px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Kreator</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.creatorName}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
key={index}
className="border rounded-md px-2 py-2"
>
{tag.trim()}
</Badge>
))}
</div>
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
<Checkbox
id="5"
checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)}
/>
<Label htmlFor="5">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="6"
checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)}
/>
<Label htmlFor="6">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
</div>
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">Kotak Saran (0)</p>
</div>
<div className="px-3 py-3">
<p>Keterangan:</p>
{/* <p>{detail?.status}</p> */}
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
<div className="mt-4">
<Button type="submit" color="primary" variant="outline">
Cancel
</Button>
</div>
</div>
</div>
</div>
) : (
""
)}
</form>
);
}

View File

@ -150,17 +150,6 @@ export default function FormImageDetail() {
resolver: zodResolver(imageSchema), resolver: zodResolver(imageSchema),
}); });
// const handleKeyDown = (e: any) => {
// const newTag = e.target.value.trim(); // Ambil nilai input
// if (e.key === "Enter" && newTag) {
// e.preventDefault(); // Hentikan submit form
// if (!tags.includes(newTag)) {
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
// setValue("tags", ""); // Kosongkan input
// }
// }
// };
useEffect(() => { useEffect(() => {
if ( if (
userLevelId != undefined && userLevelId != undefined &&

View File

@ -109,6 +109,7 @@ export default function FormImage() {
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
@ -295,11 +296,11 @@ export default function FormImage() {
setIsGeneratedArticle(true); setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => { setArticleIds((prevIds: string[]) => {
if (prevIds.length < 5) { if (prevIds.length < 3) {
return [...prevIds, newArticleId]; return [...prevIds, newArticleId];
} else { } else {
const updatedIds = [...prevIds]; const updatedIds = [...prevIds];
updatedIds[4] = newArticleId; updatedIds[2] = newArticleId;
return updatedIds; return updatedIds;
} }
}); });
@ -308,19 +309,41 @@ export default function FormImage() {
}; };
const handleArticleIdClick = async (id: string) => { const handleArticleIdClick = async (id: string) => {
const res = await getDetailArticle(id); setIsLoadingData(true);
const articleData = res?.data?.data; let retryCount = 0;
const maxRetries = 20;
const cleanArticleBody = articleData?.articleBody?.replace( try {
/<img[^>]*>/g, const waitForStatusUpdate = async () => {
"" while (retryCount < maxRetries) {
); const res = await getDetailArticle(id);
const articleImagesData = articleData?.imagesUrl?.split(","); const articleData = res?.data?.data;
setArticleBody(cleanArticleBody || ""); if (articleData?.status === 2) {
setDetailData(articleData); return articleData;
setSelectedArticleId(id); }
setArticleImages(articleImagesData || []);
retryCount++;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Timeout: Artikel belum selesai diproses.");
};
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
} catch (error) {
console.error("Error fetching article details:", error);
} finally {
setIsLoadingData(false);
}
}; };
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -862,19 +885,19 @@ export default function FormImage() {
</div> </div>
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <button
key={index} key={index}
className={`btn mr-3 ${ className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id selectedArticleId === id
? "bg-green-500 text-white px-3 py-2 rounded-md" ? "bg-green-500 text-white"
: "border-2 border-green-500 text-green-500 px-3 py-2 rounded-md" : "border-2 border-green-500 text-green-500"
}`} }`}
onClick={() => handleArticleIdClick(id)} onClick={() => handleArticleIdClick(id)}
> >
{id} {id}
</p> </button>
))} ))}
</div> </div>
)} )}
@ -915,15 +938,18 @@ export default function FormImage() {
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) =>
// <JoditEditor isLoadingData ? (
// ref={editor} <div className="flex justify-center items-center h-40">
// value={articleBody || value} <p className="text-gray-500">Loading Proses Data...</p>
// onChange={onChange} </div>
// className="dark:text-black" ) : (
// /> <CustomEditor
<CustomEditor onChange={onChange} /> onChange={onChange}
)} initialData={articleBody || value}
/>
)
}
/> />
{errors.description?.message && ( {errors.description?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">

View File

@ -31,6 +31,7 @@ import {
import { detailMedia } from "@/service/curated-content/curated-content"; import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react"; import { MailIcon } from "lucide-react";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -61,6 +62,13 @@ type Detail = {
tags: string; tags: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormImageUpdate() { export default function FormImageUpdate() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -81,6 +89,7 @@ export default function FormImageUpdate() {
const [detail, setDetail] = useState<Detail>(); const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false); const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]); const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [articleBody, setArticleBody] = useState<string>("");
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
@ -305,11 +314,9 @@ export default function FormImageUpdate() {
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.description}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />

View File

@ -46,6 +46,7 @@ import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { request } from "http"; import { request } from "http";
import { generateDataArticle, getDetailArticle } from "@/service/content/ai"; import { generateDataArticle, getDetailArticle } from "@/service/content/ai";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const imageSchema = z.object({
contentTitle: z.string().min(1, { message: "Judul diperlukan" }), contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
@ -79,6 +80,18 @@ type Detail = {
contentTag: string; contentTag: string;
}; };
type Option = {
id: string;
label: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormConvertSPIT() { export default function FormConvertSPIT() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -114,6 +127,7 @@ export default function FormConvertSPIT() {
); );
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [selectedFileType, setSelectedFileType] = useState("original"); const [selectedFileType, setSelectedFileType] = useState("original");
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
@ -122,6 +136,15 @@ export default function FormConvertSPIT() {
polda: false, polda: false,
polres: false, polres: false,
}); });
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
{ id: "6", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
];
let fileTypeId = "1"; let fileTypeId = "1";
@ -150,11 +173,11 @@ export default function FormConvertSPIT() {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
}; };
const handleCheckboxChange = (id: number) => { // const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => // setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id] // prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
); // );
}; // };
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
@ -246,6 +269,33 @@ export default function FormConvertSPIT() {
})); }));
}; };
const handleCheckboxChange = (id: string): void => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
}
};
const save = async (data: any) => { const save = async (data: any) => {
const description = const description =
selectedFileType === "original" selectedFileType === "original"
@ -258,7 +308,7 @@ export default function FormConvertSPIT() {
htmlDescription: description, htmlDescription: description,
tags: "siap", tags: "siap",
categoryId: selectedCategoryId, categoryId: selectedCategoryId,
publishedFor: "6", publishedFor: publishedFor.join(","),
creator: data.contentCreator, creator: data.contentCreator,
files: getPlacement(), files: getPlacement(),
}; };
@ -330,11 +380,11 @@ export default function FormConvertSPIT() {
setIsGeneratedArticle(true); setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => { setArticleIds((prevIds: string[]) => {
if (prevIds.length < 5) { if (prevIds.length < 3) {
return [...prevIds, newArticleId]; return [...prevIds, newArticleId];
} else { } else {
const updatedIds = [...prevIds]; const updatedIds = [...prevIds];
updatedIds[4] = newArticleId; updatedIds[2] = newArticleId;
return updatedIds; return updatedIds;
} }
}); });
@ -344,19 +394,41 @@ export default function FormConvertSPIT() {
}; };
const handleArticleIdClick = async (id: string) => { const handleArticleIdClick = async (id: string) => {
const res = await getDetailArticle(id); setIsLoadingData(true);
const articleData = res?.data?.data; let retryCount = 0;
const maxRetries = 20;
const cleanArticleBody = articleData?.articleBody?.replace( try {
/<img[^>]*>/g, const waitForStatusUpdate = async () => {
"" while (retryCount < maxRetries) {
); const res = await getDetailArticle(id);
const articleImagesData = articleData?.imagesUrl?.split(","); const articleData = res?.data?.data;
setArticleBody(cleanArticleBody || ""); if (articleData?.status === 2) {
setDetailData(articleData); return articleData;
setSelectedArticleId(id); }
// setArticleImages(articleImagesData || []);
retryCount++;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Timeout: Artikel belum selesai diproses.");
};
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
// setArticleImages(articleImagesData || []);
} catch (error) {
console.error("Error fetching article details:", error);
} finally {
setIsLoadingData(false);
}
}; };
return ( return (
@ -434,11 +506,15 @@ export default function FormConvertSPIT() {
control={control} control={control}
name="contentDescription" name="contentDescription"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor // <JoditEditor
ref={editor} // ref={editor}
value={detail?.contentDescription} // value={detail?.contentDescription}
// onChange={onChange}
// className="dark:text-black"
// />
<CustomEditor
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.contentDescription}
/> />
)} )}
/> />
@ -462,30 +538,17 @@ export default function FormConvertSPIT() {
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
// <Button <button
// key={index}
// className={`btn mr-2 ${
// selectedArticleId === id
// ? "btn-warning"
// : "btn-success"
// }`}
// onClick={() => handleArticleIdClick(id)}
// variant={"outline"}
// color="success"
// >
// {id}
// </Button>
<p
key={index} key={index}
className={`btn mr-3 ${ className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id selectedArticleId === id
? "bg-green-500 text-white px-3 py-2 rounded-md" ? "bg-green-500 text-white"
: "border-2 border-green-500 text-green-500 px-3 py-2 rounded-md" : "border-2 border-green-500 text-green-500"
}`} }`}
onClick={() => handleArticleIdClick(id)} onClick={() => handleArticleIdClick(id)}
> >
{id} {id}
</p> </button>
))} ))}
</div> </div>
)} )}
@ -500,14 +563,20 @@ export default function FormConvertSPIT() {
<Controller <Controller
control={control} control={control}
name="contentRewriteDescription" name="contentRewriteDescription"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) =>
<JoditEditor isLoadingData ? (
ref={editor} <div className="flex justify-center items-center h-40">
value={articleBody || value} <p className="text-gray-500">
onChange={onChange} Loading Proses Data...
className="dark:text-black" </p>
/> </div>
)} ) : (
<CustomEditor
onChange={onChange}
initialData={articleBody || value}
/>
)
}
/> />
{errors.contentRewriteDescription?.message && ( {errors.contentRewriteDescription?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">
@ -654,40 +723,24 @@ export default function FormConvertSPIT() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-3">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option) => (
<Checkbox <div key={option.id} className="flex gap-2 items-center">
id="5" <Checkbox
checked={selectedPublishers.includes(5)} id={option.id}
onChange={() => handleCheckboxChange(5)} checked={
/> option.id === "all"
<Label htmlFor="5">UMUM</Label> ? publishedFor.length ===
</div> options.filter((opt: any) => opt.id !== "all")
<div className="flex gap-2 items-center"> .length
<Checkbox : publishedFor.includes(option.id)
id="6" }
checked={selectedPublishers.includes(6)} onCheckedChange={() => handleCheckboxChange(option.id)}
onChange={() => handleCheckboxChange(6)} />
/> <Label htmlFor={option.id}>{option.label}</Label>
<Label htmlFor="6">JOURNALIS</Label> </div>
</div> ))}
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
</div>
</div> </div>
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">

View File

@ -54,6 +54,7 @@ import { loading } from "@/config/swal";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -96,6 +97,13 @@ type Detail = {
uploadedById: number; uploadedById: number;
}; };
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function FormTeksDetail() { export default function FormTeksDetail() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -336,7 +344,7 @@ export default function FormTeksDetail() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/in/contributor/content/image"); router.push("/in/contributor/content/teks");
}); });
}; };
@ -346,7 +354,9 @@ export default function FormTeksDetail() {
<div className="flex lg:flex-row gap-10"> <div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">
Form detail Konten Teks
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
@ -400,12 +410,7 @@ export default function FormTeksDetail() {
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <ViewEditor initialData={detail?.htmlDescription} />
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)} )}
/> />
{errors.description?.message && ( {errors.description?.message && (

View File

@ -51,8 +51,9 @@ import { CloudUpload } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Item } from "@radix-ui/react-dropdown-menu"; import { Item } from "@radix-ui/react-dropdown-menu";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const teksSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z
.string() .string()
@ -70,11 +71,23 @@ type Category = {
name: string; name: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
type Option = {
id: string;
label: string;
};
export default function FormTeks() { export default function FormTeks() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const editor = useRef(null); const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type TeksSchema = z.infer<typeof teksSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
@ -94,6 +107,7 @@ export default function FormTeks() {
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
@ -125,6 +139,14 @@ export default function FormTeks() {
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
{ id: "6", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
];
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => { onDrop: (acceptedFiles) => {
@ -137,8 +159,8 @@ export default function FormTeks() {
handleSubmit, handleSubmit,
setValue, setValue,
formState: { errors }, formState: { errors },
} = useForm<ImageSchema>({ } = useForm<TeksSchema>({
resolver: zodResolver(imageSchema), resolver: zodResolver(teksSchema),
}); });
const doGenerateMainKeyword = async () => { const doGenerateMainKeyword = async () => {
@ -268,11 +290,11 @@ export default function FormTeks() {
setIsGeneratedArticle(true); setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => { setArticleIds((prevIds: string[]) => {
if (prevIds.length < 5) { if (prevIds.length < 3) {
return [...prevIds, newArticleId]; return [...prevIds, newArticleId];
} else { } else {
const updatedIds = [...prevIds]; const updatedIds = [...prevIds];
updatedIds[4] = newArticleId; updatedIds[2] = newArticleId;
return updatedIds; return updatedIds;
} }
}); });
@ -281,19 +303,41 @@ export default function FormTeks() {
}; };
const handleArticleIdClick = async (id: string) => { const handleArticleIdClick = async (id: string) => {
const res = await getDetailArticle(id); setIsLoadingData(true);
const articleData = res?.data?.data; let retryCount = 0;
const maxRetries = 20;
const cleanArticleBody = articleData?.articleBody?.replace( try {
/<img[^>]*>/g, const waitForStatusUpdate = async () => {
"" while (retryCount < maxRetries) {
); const res = await getDetailArticle(id);
const articleImagesData = articleData?.imagesUrl?.split(","); const articleData = res?.data?.data;
setArticleBody(cleanArticleBody || ""); if (articleData?.status === 2) {
setDetailData(articleData); return articleData;
setSelectedArticleId(id); }
setArticleImages(articleImagesData || []);
retryCount++;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Timeout: Artikel belum selesai diproses.");
};
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
} catch (error) {
console.error("Error fetching article details:", error);
} finally {
setIsLoadingData(false);
}
}; };
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -350,7 +394,34 @@ export default function FormTeks() {
} }
}; };
const save = async (data: ImageSchema) => { const handleCheckboxChange = (id: string): void => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
}
};
const save = async (data: TeksSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
@ -364,7 +435,7 @@ export default function FormTeks() {
subCategoryId: selectedCategory, subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1", statusId: "1",
publishedFor: "6", publishedFor: publishedFor.join(","),
creatorName: data.creatorName, creatorName: data.creatorName,
tags: finalTags, tags: finalTags,
isYoutube: false, isYoutube: false,
@ -420,7 +491,7 @@ export default function FormTeks() {
// MySwal.fire("Sukses", "Data berhasil disimpan.", "success"); // MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
}; };
const onSubmit = (data: ImageSchema) => { const onSubmit = (data: TeksSchema) => {
MySwal.fire({ MySwal.fire({
title: "Simpan Data", title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?", text: "Apakah Anda yakin ingin menyimpan data ini?",
@ -509,7 +580,7 @@ export default function FormTeks() {
setIsStartUpload(false); setIsStartUpload(false);
// hideProgress(); // hideProgress();
Cookies.remove("idCreate"); Cookies.remove("idCreate");
successSubmit("/in/contributor/content/image/"); successSubmit("/in/contributor/content/teks");
} }
} }
@ -810,17 +881,17 @@ export default function FormTeks() {
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <button
key={index} key={index}
className={`btn mr-3 ${ className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id selectedArticleId === id
? "bg-green-500 text-white px-3 py-2 rounded-md" ? "bg-green-500 text-white"
: "border-2 border-green-500 text-green-500 px-3 py-2 rounded-md" : "border-2 border-green-500 text-green-500"
}`} }`}
onClick={() => handleArticleIdClick(id)} onClick={() => handleArticleIdClick(id)}
> >
{id} {id}
</p> </button>
))} ))}
</div> </div>
)} )}
@ -861,14 +932,18 @@ export default function FormTeks() {
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) =>
<JoditEditor isLoadingData ? (
ref={editor} <div className="flex justify-center items-center h-40">
value={articleBody || value} <p className="text-gray-500">Loading Proses Data...</p>
onChange={onChange} </div>
className="dark:text-black" ) : (
/> <CustomEditor
)} onChange={onChange}
initialData={articleBody || value}
/>
)
}
/> />
{errors.description?.message && ( {errors.description?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">
@ -977,28 +1052,24 @@ export default function FormTeks() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-3">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option) => (
<Checkbox id="all" /> <div key={option.id} className="flex gap-2 items-center">
<Label htmlFor="all">SEMUA</Label> <Checkbox
</div> id={option.id}
<div className="flex gap-2 items-center"> checked={
<Checkbox id="umum" /> option.id === "all"
<Label htmlFor="umum">UMUM</Label> ? publishedFor.length ===
</div> options.filter((opt: any) => opt.id !== "all")
<div className="flex gap-2 items-center"> .length
<Checkbox id="journalist" /> : publishedFor.includes(option.id)
<Label htmlFor="journalist">JOURNALIS</Label> }
</div> onCheckedChange={() => handleCheckboxChange(option.id)}
<div className="flex gap-2 items-center"> />
<Checkbox id="polri" /> <Label htmlFor={option.id}>{option.label}</Label>
<Label htmlFor="polri">POLRI</Label> </div>
</div> ))}
<div className="flex gap-2 items-center">
<Checkbox id="ksp" />
<Label htmlFor="ksp">KSP</Label>
</div>
</div> </div>
</div> </div>
</Card> </Card>

View File

@ -225,7 +225,7 @@ export default function FormTeksUpdate() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/en/contributor/content/image"); router.push("/en/contributor/content/teks");
}); });
}; };
@ -251,7 +251,9 @@ export default function FormTeksUpdate() {
<div className="flex lg:flex-row gap-10"> <div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">
Form Update Konten Teks
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">

View File

@ -54,6 +54,7 @@ import { loading } from "@/config/swal";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -97,6 +98,13 @@ type Detail = {
uploadedById: number; uploadedById: number;
}; };
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function FormVideoDetail() { export default function FormVideoDetail() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -118,7 +126,7 @@ export default function FormVideoDetail() {
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
const [detail, setDetail] = useState<Detail>(); const [detail, setDetail] = useState<any>();
const [refresh, setRefresh] = useState(false); const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]); const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
@ -337,7 +345,7 @@ export default function FormVideoDetail() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/in/contributor/content/image"); router.push("/in/contributor/content/video");
}); });
}; };
@ -347,7 +355,9 @@ export default function FormVideoDetail() {
<div className="flex lg:flex-row gap-10"> <div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">
Form Detail Konten Video
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
@ -401,12 +411,7 @@ export default function FormVideoDetail() {
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <ViewEditor initialData={detail?.htmlDescription} />
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)} )}
/> />
{errors.description?.message && ( {errors.description?.message && (

View File

@ -51,8 +51,16 @@ import { CloudUpload } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Item } from "@radix-ui/react-dropdown-menu"; import { Item } from "@radix-ui/react-dropdown-menu";
import dynamic from "next/dynamic";
const imageSchema = z.object({ const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
const teksSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z
.string() .string()
@ -70,11 +78,16 @@ type Category = {
name: string; name: string;
}; };
type Option = {
id: string;
label: string;
};
export default function FormVideo() { export default function FormVideo() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const editor = useRef(null); const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type TeksSchema = z.infer<typeof teksSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
@ -94,6 +107,7 @@ export default function FormVideo() {
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
@ -123,9 +137,17 @@ export default function FormVideo() {
let uploadPersen = 0; let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
{ id: "6", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
];
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => { onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file))); setFiles(acceptedFiles.map((file) => Object.assign(file)));
@ -137,8 +159,8 @@ export default function FormVideo() {
handleSubmit, handleSubmit,
setValue, setValue,
formState: { errors }, formState: { errors },
} = useForm<ImageSchema>({ } = useForm<TeksSchema>({
resolver: zodResolver(imageSchema), resolver: zodResolver(teksSchema),
}); });
const doGenerateMainKeyword = async () => { const doGenerateMainKeyword = async () => {
@ -268,11 +290,11 @@ export default function FormVideo() {
setIsGeneratedArticle(true); setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => { setArticleIds((prevIds: string[]) => {
if (prevIds.length < 5) { if (prevIds.length < 3) {
return [...prevIds, newArticleId]; return [...prevIds, newArticleId];
} else { } else {
const updatedIds = [...prevIds]; const updatedIds = [...prevIds];
updatedIds[4] = newArticleId; updatedIds[2] = newArticleId;
return updatedIds; return updatedIds;
} }
}); });
@ -281,19 +303,41 @@ export default function FormVideo() {
}; };
const handleArticleIdClick = async (id: string) => { const handleArticleIdClick = async (id: string) => {
const res = await getDetailArticle(id); setIsLoadingData(true);
const articleData = res?.data?.data; let retryCount = 0;
const maxRetries = 20;
const cleanArticleBody = articleData?.articleBody?.replace( try {
/<img[^>]*>/g, const waitForStatusUpdate = async () => {
"" while (retryCount < maxRetries) {
); const res = await getDetailArticle(id);
const articleImagesData = articleData?.imagesUrl?.split(","); const articleData = res?.data?.data;
setArticleBody(cleanArticleBody || ""); if (articleData?.status === 2) {
setDetailData(articleData); return articleData;
setSelectedArticleId(id); }
setArticleImages(articleImagesData || []);
retryCount++;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Timeout: Artikel belum selesai diproses.");
};
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
setDetailData(articleData);
setSelectedArticleId(id);
setArticleImages(articleImagesData || []);
} catch (error) {
console.error("Error fetching article details:", error);
} finally {
setIsLoadingData(false);
}
}; };
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -350,7 +394,34 @@ export default function FormVideo() {
} }
}; };
const save = async (data: ImageSchema) => { const handleCheckboxChange = (id: string): void => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
}
};
const save = async (data: TeksSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
@ -364,7 +435,7 @@ export default function FormVideo() {
subCategoryId: selectedCategory, subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1", statusId: "1",
publishedFor: "6", publishedFor: publishedFor.join(","),
creatorName: data.creatorName, creatorName: data.creatorName,
tags: finalTags, tags: finalTags,
isYoutube: false, isYoutube: false,
@ -386,26 +457,13 @@ export default function FormVideo() {
// Upload Thumbnail // Upload Thumbnail
const formMedia = new FormData(); const formMedia = new FormData();
console.log("Thumbnail : ", files[0]); const thumbnail = files[0];
formMedia.append("file", files[0]); formMedia.append("file", thumbnail);
const responseThumbnail = await uploadThumbnail(id, formMedia); const responseThumbnail = await uploadThumbnail(id, formMedia);
if (responseThumbnail?.error == true) { if (responseThumbnail?.error == true) {
error(responseThumbnail?.message); error(responseThumbnail?.message);
return false; return false;
} }
const videoId = response?.data.data.id;
if (videoId && thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail);
const responseThumbnail = await uploadThumbnail(videoId, formMedia);
if (responseThumbnail?.error) {
MySwal.fire("Error", responseThumbnail?.message, "error");
return;
}
}
} }
// Upload File // Upload File
@ -433,7 +491,7 @@ export default function FormVideo() {
// MySwal.fire("Sukses", "Data berhasil disimpan.", "success"); // MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
}; };
const onSubmit = (data: ImageSchema) => { const onSubmit = (data: TeksSchema) => {
MySwal.fire({ MySwal.fire({
title: "Simpan Data", title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?", text: "Apakah Anda yakin ingin menyimpan data ini?",
@ -522,7 +580,7 @@ export default function FormVideo() {
setIsStartUpload(false); setIsStartUpload(false);
// hideProgress(); // hideProgress();
Cookies.remove("idCreate"); Cookies.remove("idCreate");
successSubmit("/in/contributor/content/image/"); successSubmit("/in/contributor/content/video");
} }
} }
@ -823,17 +881,17 @@ export default function FormVideo() {
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <button
key={index} key={index}
className={`btn mr-3 ${ className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id selectedArticleId === id
? "bg-green-500 text-white px-3 py-2 rounded-md" ? "bg-green-500 text-white"
: "border-2 border-green-500 text-green-500 px-3 py-2 rounded-md" : "border-2 border-green-500 text-green-500"
}`} }`}
onClick={() => handleArticleIdClick(id)} onClick={() => handleArticleIdClick(id)}
> >
{id} {id}
</p> </button>
))} ))}
</div> </div>
)} )}
@ -874,14 +932,18 @@ export default function FormVideo() {
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) =>
<JoditEditor isLoadingData ? (
ref={editor} <div className="flex justify-center items-center h-40">
value={articleBody || value} <p className="text-gray-500">Loading Proses Data...</p>
onChange={onChange} </div>
className="dark:text-black" ) : (
/> <CustomEditor
)} onChange={onChange}
initialData={articleBody || value}
/>
)
}
/> />
{errors.description?.message && ( {errors.description?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">
@ -1003,28 +1065,24 @@ export default function FormVideo() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-3">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option) => (
<Checkbox id="all" /> <div key={option.id} className="flex gap-2 items-center">
<Label htmlFor="all">SEMUA</Label> <Checkbox
</div> id={option.id}
<div className="flex gap-2 items-center"> checked={
<Checkbox id="umum" /> option.id === "all"
<Label htmlFor="umum">UMUM</Label> ? publishedFor.length ===
</div> options.filter((opt: any) => opt.id !== "all")
<div className="flex gap-2 items-center"> .length
<Checkbox id="journalist" /> : publishedFor.includes(option.id)
<Label htmlFor="journalist">JOURNALIS</Label> }
</div> onCheckedChange={() => handleCheckboxChange(option.id)}
<div className="flex gap-2 items-center"> />
<Checkbox id="polri" /> <Label htmlFor={option.id}>{option.label}</Label>
<Label htmlFor="polri">POLRI</Label> </div>
</div> ))}
<div className="flex gap-2 items-center">
<Checkbox id="ksp" />
<Label htmlFor="ksp">KSP</Label>
</div>
</div> </div>
</div> </div>
</Card> </Card>

View File

@ -242,7 +242,7 @@ export default function FormVideoUpdate() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/en/contributor/content/image"); router.push("/en/contributor/content/video");
}); });
}; };
@ -268,7 +268,9 @@ export default function FormVideoUpdate() {
<div className="flex lg:flex-row gap-10"> <div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">
Form Update Konten Video
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">

View File

@ -20,8 +20,12 @@ import {
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react"; import JoditEditor from "jodit-react";
import { createTask, getTask } from "@/service/task"; import {
import { getContestById } from "@/service/contest/contest"; createTask,
getTask,
getUserLevelForAssignments,
} from "@/service/task";
import { getContestById, postCreateContest } from "@/service/contest/contest";
import page from "@/app/[locale]/page"; import page from "@/app/[locale]/page";
import { import {
Popover, Popover,
@ -29,10 +33,19 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { CalendarIcon } from "lucide-react"; import { CalendarIcon, ChevronDown, ChevronUp } from "lucide-react";
import { format, parseISO } from "date-fns"; import { format, parseISO } from "date-fns";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { DateRange } from "react-day-picker"; import { DateRange } from "react-day-picker";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import dynamic from "next/dynamic";
import Cookies from "js-cookie";
const contestSchema = z.object({ const contestSchema = z.object({
theme: z.string().min(1, { message: "Judul diperlukan" }), theme: z.string().min(1, { message: "Judul diperlukan" }),
@ -40,9 +53,12 @@ const contestSchema = z.object({
description: z.string().min(2, { description: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.", message: "Narasi Penugasan harus lebih dari 2 karakter.",
}), }),
scoringFormula: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
}); });
export type taskDetail = { export type contestDetail = {
id: number; id: number;
theme: string; theme: string;
hastagCode: string; hastagCode: string;
@ -56,12 +72,22 @@ export type taskDetail = {
name: string; name: string;
}; };
createdAt: string; createdAt: string;
platformType: string | null;
assignmentTypeId: string;
targetOutput: string; targetOutput: string;
targetParticipantTopLevel: string; targetParticipantTopLevel: string;
description: string; description: string;
fileTypeOutput: any;
is_active: string; is_active: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormContestDetail() { export default function FormContestDetail() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -69,8 +95,19 @@ export default function FormContestDetail() {
type ContestSchema = z.infer<typeof contestSchema>; type ContestSchema = z.infer<typeof contestSchema>;
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
console.log(id); console.log(id);
const [mainType, setMainType] = useState<string>("1");
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
const [selectedTarget, setSelectedTarget] = useState("all");
const [detail, setDetail] = useState<any>();
const [refresh] = useState(false);
const [date, setDate] = useState<DateRange | undefined>();
const [listDest, setListDest] = useState([]);
const [checkedLevels, setCheckedLevels] = useState(new Set());
const [expandedPolda, setExpandedPolda] = useState([{}]);
const [isLoading, setIsLoading] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
// State for various form fields
const [taskOutput, setTaskOutput] = useState({ const [taskOutput, setTaskOutput] = useState({
all: false, all: false,
video: false, video: false,
@ -79,18 +116,6 @@ export default function FormContestDetail() {
text: false, text: false,
}); });
// const [assignmentType, setAssignmentType] = useState("mediahub");
// const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<string>("1");
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const [date, setDate] = useState<DateRange | undefined>();
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
mabes: false, mabes: false,
@ -113,6 +138,30 @@ export default function FormContestDetail() {
// setPlatformTypeVisible(selectedValue === 2); // setPlatformTypeVisible(selectedValue === 2);
// }; // };
useEffect(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
try {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
const initialExpandedState = response?.data?.data.list.reduce(
(acc: any, polda: any) => {
acc[polda.id] = false;
return acc;
},
{}
);
setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
fetchPoldaPolres();
}, []);
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
if (id) { if (id) {
@ -142,19 +191,42 @@ export default function FormContestDetail() {
} }
}, [detail?.targetOutput]); }, [detail?.targetOutput]);
useEffect(() => { // useEffect(() => {
if (detail?.targetParticipantTopLevel) { // if (detail?.targetParticipantTopLevel) {
const outputSet = new Set( // const outputSet = new Set(
detail.targetParticipantTopLevel.split(",").map(Number) // detail.targetParticipantTopLevel.split(",").map(Number)
); // );
setUnitSelection({ // setUnitSelection({
allUnit: outputSet.has(0), // allUnit: outputSet.has(0),
mabes: outputSet.has(1), // mabes: outputSet.has(1),
polda: outputSet.has(2), // polda: outputSet.has(2),
polres: outputSet.has(3), // polres: outputSet.has(3),
}); // });
} // }
}, [detail?.targetParticipantTopLevel]); // }, [detail?.targetParticipantTopLevel]);
const handleCheckboxChange = (levelId: number) => {
setCheckedLevels((prev) => {
const updatedLevels = new Set(prev);
if (updatedLevels.has(levelId)) {
updatedLevels.delete(levelId);
} else {
updatedLevels.add(levelId);
}
return updatedLevels;
});
};
const handlePoldaPolresChange = () => {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
};
const toggleExpand = (poldaId: any) => {
setExpandedPolda((prev: any) => ({
...prev,
[poldaId]: !prev[poldaId],
}));
};
const save = async (data: ContestSchema) => { const save = async (data: ContestSchema) => {
const fileTypeMapping = { const fileTypeMapping = {
@ -165,34 +237,53 @@ export default function FormContestDetail() {
text: "5", text: "5",
}; };
const unitMapping = {
allUnit: "0",
mabes: "1",
polda: "2",
polres: "3",
};
const assignmentPurposeString = Object.keys(unitSelection)
.filter((key) => unitSelection[key as keyof typeof unitSelection])
.map((key) => unitMapping[key as keyof typeof unitMapping])
.join(",");
const selectedOutputs = Object.keys(taskOutput) const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true` .filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string .map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(","); .join(",");
const requestData = { const requestData: {
id?: any;
theme: string;
assignedToLevel: any;
assignmentPurpose: any;
hastagCode: string;
description: string;
assignmentMainTypeId: any;
scoringFormula: string;
fileTypeOutput: any;
} = {
...data, ...data,
// assignmentType, hastagCode: data.hastagCode,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
description: data.description,
platformType: "",
theme: data.theme, theme: data.theme,
description: data.description,
scoringFormula: data.scoringFormula,
assignmentMainTypeId: mainType,
assignedToLevel: handlePoldaPolresChange(),
assignmentPurpose: assignmentPurposeString,
fileTypeOutput: selectedOutputs,
}; };
// const response = await createTask(requestData); if (id != undefined) {
requestData.id = id;
}
const response = await postCreateContest(requestData);
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
// console.log("response", response); console.log("response", response);
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
@ -225,7 +316,6 @@ export default function FormContestDetail() {
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Contest</p> <p className="text-lg font-semibold mb-3">Form Contest</p>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2"> <div className="space-y-2">
@ -233,12 +323,12 @@ export default function FormContestDetail() {
<Controller <Controller
control={control} control={control}
name="hastagCode" name="hastagCode"
render={({ field }) => ( render={({ field: { onChange, value } }) => (
<Input <Input
size="md" size="md"
type="text" type="text"
value={field.value} value={detail?.hastagCode || value}
onChange={field.onChange} onChange={onChange}
placeholder="Enter hastagCode" placeholder="Enter hastagCode"
/> />
)} )}
@ -255,12 +345,12 @@ export default function FormContestDetail() {
<Controller <Controller
control={control} control={control}
name="theme" name="theme"
render={({ field }) => ( render={({ field: { onChange, value } }) => (
<Input <Input
size="md" size="md"
type="text" type="text"
value={field.value} value={detail?.theme || value}
onChange={field.onChange} onChange={onChange}
placeholder="Enter theme" placeholder="Enter theme"
/> />
)} )}
@ -345,19 +435,101 @@ export default function FormContestDetail() {
</Label> </Label>
</div> </div>
))} ))}
<div className=" pl-1">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>
Daftar Wilayah Polda dan Polres
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listDest.map((polda: any) => (
<div key={polda.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
checked={checkedLevels.has(polda.id)}
onCheckedChange={() =>
handleCheckboxChange(polda.id)
}
className="mr-3"
/>
{polda.name}
<button
onClick={() => toggleExpand(polda.id)}
className="ml-2 focus:outline-none"
>
{expandedPolda[polda.id] ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</button>
</Label>
{expandedPolda[polda.id] && (
<div className="ml-6 mt-2">
<Label className="block">
<Checkbox
checked={polda?.subDestination?.every(
(polres: any) =>
checkedLevels.has(polres.id)
)}
onCheckedChange={(isChecked) => {
const updatedLevels = new Set(
checkedLevels
);
polda?.subDestination?.forEach(
(polres: any) => {
if (isChecked) {
updatedLevels.add(polres.id);
} else {
updatedLevels.delete(polres.id);
}
}
);
setCheckedLevels(updatedLevels);
}}
className="mr-2"
/>
Pilih Semua Polres
</Label>
{polda?.subDestination?.map((polres: any) => (
<Label key={polres.id} className="block mt-1">
<Checkbox
checked={checkedLevels.has(polres.id)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
className="mr-2"
/>
{polres.name}
</Label>
))}
</div>
)}
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
</div> </div>
</div> </div>
<div className="mt-7"> <div className="mt-7">
<Label>Narasi Penugasan</Label> <Label>Narasi Penugasan</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={value}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />
@ -367,6 +539,24 @@ export default function FormContestDetail() {
</p> </p>
)} )}
</div> </div>
<div className="mt-7">
<Label>Rumus Penilaian</Label>
<Controller
control={control}
name="scoringFormula"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={onChange}
initialData={detail?.scoringFormula || value}
/>
)}
/>
{errors.scoringFormula?.message && (
<p className="text-red-400 text-sm">
{errors.scoringFormula.message}
</p>
)}
</div>
</div> </div>
<div className="mt-4"> <div className="mt-4">

View File

@ -0,0 +1,128 @@
"use client";
import { Fragment, useState } from "react";
import { Icon } from "@iconify/react";
import { Accept, useDropzone } from "react-dropzone";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import { CloudUpload } from "lucide-react";
interface FileWithPreview extends File {
preview: string;
}
interface FileUploaderProps {
onDrop: (files: FileWithPreview[]) => void;
accept: Accept;
maxSize: number;
label: string;
className?: string;
isMultiple?: boolean;
}
const FileUploader = ({ onDrop, accept, maxSize, label, className = "", isMultiple = true }: FileUploaderProps) => {
const [files, setFiles] = useState<FileWithPreview[]>([]);
const { getRootProps, getInputProps } = useDropzone({
accept,
maxSize: maxSize * 1024 * 1024,
onDrop: (acceptedFiles) => {
const mappedFiles = acceptedFiles.map((file) => Object.assign(file));
setFiles((prevFiles) => [...prevFiles, ...mappedFiles]);
onDrop(mappedFiles);
},
});
const renderFilePreview = (file: FileWithPreview) => {
if (file.type.startsWith("image")) {
return (
<Image
width={48}
height={48}
alt={file.name}
src={URL.createObjectURL(file)}
className=" rounded border p-0.5"
/>
);
} else {
return <Icon icon="tabler:file-description" />;
}
};
const handleRemoveFile = (file: FileWithPreview) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
const handleRemoveAllFiles = () => {
setFiles([]);
onDrop([]);
};
const fileList = files.map((file) => (
<div
key={file.name}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file)}</div>
<div>
<div className=" text-sm text-card-foreground">{file.name}</div>
<div className=" text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
</div>
<Button
size="icon"
color="destructive"
variant="outline"
className=" border-none rounded-full"
onClick={() => handleRemoveFile(file)}
>
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
));
return (
<Fragment>
<div {...getRootProps({ className: "dropzone" })} className={className}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( {label}
Ukuran maksimal {maxSize} MB.)
</div>
</div>
</div>
{files.length ? (
<Fragment>
<div>{fileList}</div>
<div className=" flex justify-between gap-2">
<Button
color="destructive"
onClick={handleRemoveAllFiles}
>
Remove All
</Button>
</div>
</Fragment>
) : null}
</Fragment>
);
}
export default FileUploader;

View File

@ -34,6 +34,9 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { ChevronDown, ChevronUp } from "lucide-react"; import { ChevronDown, ChevronUp } from "lucide-react";
import { AudioRecorder } from "react-audio-voice-recorder"; import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client";
import { error } from "@/config/swal";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -42,6 +45,10 @@ const taskSchema = z.object({
}), }),
}); });
interface FileWithPreview extends File {
preview: string;
}
export type taskDetail = { export type taskDetail = {
id: number; id: number;
title: string; title: string;
@ -96,6 +103,16 @@ export default function FormTask() {
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [voiceNoteLink, setVoiceNoteLink] = useState("");
const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -222,15 +239,45 @@ export default function FormTask() {
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
console.log("response", response); console.log("response", response);
MySwal.fire({ const id = response?.data?.data.id;
title: "Sukses",
text: "Data berhasil disimpan.", if (imageFiles?.length == 0) {
icon: "success", setIsImageUploadFinish(true);
confirmButtonColor: "#3085d6", }
confirmButtonText: "OK", imageFiles?.map(async (item: any, index: number) => {
}).then(() => { await uploadResumableFile(index, String(id), item, "1", "0");
router.push("/en/contributor/task");
}); });
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "4", "0");
});
// MySwal.fire({
// title: "Sukses",
// text: "Data berhasil disimpan.",
// icon: "success",
// confirmButtonColor: "#3085d6",
// confirmButtonText: "OK",
// }).then(() => {
// router.push("/en/contributor/task");
// });
}; };
const onSubmit = (data: TaskSchema) => { const onSubmit = (data: TaskSchema) => {
@ -301,6 +348,101 @@ export default function FormTask() {
audioElements.forEach((audio) => audio.remove()); audioElements.forEach((audio) => audio.remove());
}; };
async function uploadResumableFile(
idx: number,
id: string,
file: any,
fileTypeId: string,
duration: string
) {
console.log(idx, id, file, fileTypeId, duration);
// const placements = getPlacement(file.placements);
// console.log("Placementttt: : ", placements);
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
assignmentid: id,
filename: file.name,
filetype: file.type,
fileTypeId: fileTypeId,
duration: "",
isWatermark: "true", // hardcode
},
onError: async (e: any) => {
console.log("Error upload :", e);
error(e);
},
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// progressInfo[idx].percentage = uploadPersen;
// counterUpdateProgress++;
// console.log(counterUpdateProgress);
// setProgressList(progressInfo);
// setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
// uploadPersen = 100;
// progressInfo[idx].percentage = 100;
// counterUpdateProgress++;
// setCounterProgress(counterUpdateProgress);
successTodo();
if (fileTypeId == "1") {
setIsImageUploadFinish(true);
} else if (fileTypeId == "2") {
setIsVideoUploadFinish(true);
}
if (fileTypeId == "3") {
setIsTextUploadFinish(true);
}
if (fileTypeId == "4") {
setIsAudioUploadFinish(true);
}
},
});
upload.start();
}
useEffect(() => {
successTodo();
}, [
isImageUploadFinish,
isVideoUploadFinish,
isAudioUploadFinish,
isTextUploadFinish,
]);
function successTodo() {
if (
isImageUploadFinish &&
isVideoUploadFinish &&
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/agenda-setting");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
@ -550,34 +692,41 @@ export default function FormTask() {
</p> </p>
)} )}
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5 mt-5">
<Label htmlFor="attachments">Lampiran</Label> <Label htmlFor="attachments">Lampiran</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label>Video</Label> <Label>Video</Label>
<Input <FileUploader
type="file" accept={{
accept="video/*" "mp4/*": [],
multiple "mov/*": [],
// {...register("attachments.video")} }}
maxSize={100}
label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setImageFiles(files)}
/> />
</div> </div>
<div> <div>
<Label>Foto</Label> <Label>Foto</Label>
<Input <FileUploader
type="file" accept={{
accept="image/*" "image/*": [],
multiple }}
// {...register("attachments.photo")} maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/> />
</div> </div>
<div> <div>
<Label>Teks</Label> <Label>Teks</Label>
<Input <FileUploader
type="file" accept={{
accept="text/plain" "pdf/*": [],
multiple }}
// {...register("attachments.text")} maxSize={100}
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)}
/> />
</div> </div>
<div> <div>
@ -591,6 +740,16 @@ export default function FormTask() {
downloadOnSavePress={true} downloadOnSavePress={true}
downloadFileExtension="webm" downloadFileExtension="webm"
/> />
<FileUploader
accept={{
"mp3/*": [],
"wav/*": [],
}}
maxSize={100}
label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) => setAudioFiles(files)}
className="mt-2"
/>
</div> </div>
{audioFile && ( {audioFile && (
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
@ -607,6 +766,16 @@ export default function FormTask() {
)} )}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4">
<Label htmlFor="voiceNoteLink">Link Berita</Label>
<Input
id="voiceNoteLink"
type="url"
placeholder="Masukkan link voice note"
value={voiceNoteLink}
onChange={(e) => setVoiceNoteLink(e.target.value)}
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,9 +3,13 @@ import Link from "next/link";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Reveal } from "./Reveal"; import { Reveal } from "./Reveal";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
const ContentCategory = () => { const ContentCategory = () => {
const [categories, setCategories] = useState<any>(); const [categories, setCategories] = useState<any>();
const t = useTranslations("LandingPage");
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
}, []); }, []);
@ -17,12 +21,23 @@ const ContentCategory = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [seeAllValue, setSeeAllValue] = useState(false); const [seeAllValue, setSeeAllValue] = useState(false);
const pathname = usePathname();
return ( return (
<div className="mx-auto px-4 lg:px-20 py-10 max-w-screen-2xl "> <div className="mx-auto px-4 lg:px-20 py-10 max-w-screen-2xl ">
<Reveal> <Reveal>
<h2 className="text-center text-xl lg:text-2xl font-bold text-[#bb3523] mb-4"> <h2 className="text-center text-xl lg:text-2xl font-bold text-[#bb3523] mb-4">
Kategori <span className="text-black dark:text-white">Konten</span> {pathname?.split("/")[1] == "in" ? (
<>
<span className="text-black dark:text-white">{t("category")}&nbsp;</span>
{t("content")}
</>
) : (
<>
<span className="text-black dark:text-white">{t("content")}&nbsp;</span>
{t("category")}
</>
)}
</h2> </h2>
<div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div> <div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div>
@ -51,7 +66,7 @@ const ContentCategory = () => {
</div> </div>
<div className="flex items-center flex-row justify-center"> <div className="flex items-center flex-row justify-center">
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"> <Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]">
Lihat Lebih {seeAllValue ? "Sedikit" : "Banyak"} {seeAllValue ? t("seeLess") : t("seeMore")}
</Button> </Button>
</div> </div>
</Reveal> </Reveal>

View File

@ -3,6 +3,8 @@ import { Button } from "../ui/button";
import { Reveal } from "./Reveal"; import { Reveal } from "./Reveal";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
const regions = [ const regions = [
{ name: "Polda Metro Jaya", slug: "metro-jaya", logo: "/assets/polda/polda-metro.png" }, { name: "Polda Metro Jaya", slug: "metro-jaya", logo: "/assets/polda/polda-metro.png" },
@ -47,6 +49,8 @@ const Coverage: React.FC = () => {
const [seeAllValue, setSeeAllValue] = useState(false); const [seeAllValue, setSeeAllValue] = useState(false);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions); const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions);
const pathname = usePathname();
const t = useTranslations("LandingPage");
const handleSearch = () => { const handleSearch = () => {
const value = searchTerm.toLowerCase(); const value = searchTerm.toLowerCase();
@ -60,18 +64,30 @@ const Coverage: React.FC = () => {
<Reveal> <Reveal>
{/* Header */} {/* Header */}
<h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4"> <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
Liputan <span className="text-[#bb3523]">Wilayah</span> {pathname?.split("/")[1] == "in" ? (
<>
{t("coverageOnly")}&nbsp;<span className="text-[#bb3523]">{t("area")}</span>{" "}
</>
) : (
<>
{t("area")}&nbsp;
<span className="text-[#bb3523]">{t("coverageOnly")}</span>
</>
)}
</h2> </h2>
<div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div>
{/* <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
Liputan <span className="text-[#bb3523]">Wilayah</span>
</h2> */}
<div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div>
{/* Pencarian */} {/* Pencarian */}
<div className="flex items-center justify-center gap-4 mb-6"> <div className="flex items-center justify-center gap-4 mb-6">
<input onChange={(e) => setSearchTerm(e.target.value)} type="text" placeholder="Pencarian" className="w-4/5 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#bb3523] focus:outline-none" /> <input onChange={(e) => setSearchTerm(e.target.value)} type="text" placeholder={t("search")} className="w-4/5 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#bb3523] focus:outline-none" />
<button onClick={handleSearch} className="px-2 w-1/5 lg:px-4 py-2 bg-[#bb3523] text-xs lg:text-base text-white flex justify-center items-center gap-2 rounded-md hover:bg-red-700"> <button onClick={handleSearch} className="px-2 w-1/5 lg:px-4 py-2 bg-[#bb3523] text-xs lg:text-base text-white flex justify-center items-center gap-2 rounded-md hover:bg-red-700">
Cari Polda <Icon icon="ri:arrow-right-s-line" fontSize={20} /> {t("searchRegional")}
<Icon icon="ri:arrow-right-s-line" fontSize={20} />
</button> </button>
</div> </div>
{/* Grid Wilayah */} {/* Grid Wilayah */}
<div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-9 gap-6"> <div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-9 gap-6">
{filteredList && filteredList.length > 0 ? ( {filteredList && filteredList.length > 0 ? (
@ -97,13 +113,13 @@ const Coverage: React.FC = () => {
) )
) )
) : ( ) : (
<p className="text-center text-[#bb3523] font-semibold">Polda Tidak Ditemukan</p> <p className="text-center text-[#bb3523] font-semibold">{t("notFound")}</p>
)} )}
</div> </div>
{filteredList && filteredList.length > 9 && ( {filteredList && filteredList.length > 9 && (
<div className="flex justify-center py-5"> <div className="flex justify-center py-5">
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"> <Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]">
Lihat Lebih {seeAllValue ? "Sedikit" : "Banyak"} {seeAllValue ? t("seeLess") : t("seeMore")}
</Button> </Button>
</div> </div>
)} )}

View File

@ -3,6 +3,8 @@ import { Button } from "../ui/button";
import { Reveal } from "./Reveal"; import { Reveal } from "./Reveal";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
const regions = [ const regions = [
{ name: "SIBER", slug: "siber", logo: "/assets/satker/siber.png" }, { name: "SIBER", slug: "siber", logo: "/assets/satker/siber.png" },
@ -49,6 +51,8 @@ const Division = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [seeAllValue, setSeeAllValue] = useState(false); const [seeAllValue, setSeeAllValue] = useState(false);
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions); const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions);
const pathname = usePathname();
const t = useTranslations("LandingPage");
const handleSearch = () => { const handleSearch = () => {
const value = searchTerm.toLowerCase(); const value = searchTerm.toLowerCase();
@ -62,15 +66,25 @@ const Division = () => {
{/* Header */} {/* Header */}
<Reveal> <Reveal>
<h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4"> <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
Liputan <span className="text-[#bb3523]">Satker</span> {pathname?.split("/")[1] == "in" ? (
<>
{t("coverageOnly")}&nbsp;<span className="text-[#bb3523]">{t("division")}</span>{" "}
</>
) : (
<>
{t("division")}&nbsp;
<span className="text-[#bb3523]">{t("coverageOnly")}</span>
</>
)}
</h2> </h2>
<div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div> <div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div>
{/* Pencarian */} {/* Pencarian */}
<div className="flex items-center justify-center gap-4 mb-6"> <div className="flex items-center justify-center gap-4 mb-6">
<input onChange={(e) => setSearchTerm(e.target.value)} type="text" placeholder="Pencarian" className="w-4/5 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#bb3523] focus:outline-none" /> <input onChange={(e) => setSearchTerm(e.target.value)} type="text" placeholder={t("search")} className="w-4/5 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#bb3523] focus:outline-none" />
<button onClick={handleSearch} className="px-2 w-1/5 lg:px-4 py-2 bg-[#bb3523] text-xs lg:text-base flex justify-center items-center gap-2 text-white rounded-md hover:bg-red-700"> <button onClick={handleSearch} className="px-2 w-1/5 lg:px-4 py-2 bg-[#bb3523] text-xs lg:text-base flex justify-center items-center gap-2 text-white rounded-md hover:bg-red-700">
Cari Satker <Icon icon="ri:arrow-right-s-line" fontSize={20} /> {t("searchDivision")}
<Icon icon="ri:arrow-right-s-line" fontSize={20} />
</button> </button>
</div> </div>
@ -105,7 +119,7 @@ const Division = () => {
{filteredList && filteredList.length > 9 && ( {filteredList && filteredList.length > 9 && (
<div className="flex justify-center py-5"> <div className="flex justify-center py-5">
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"> <Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]">
Lihat Lebih {seeAllValue ? "Sedikit" : "Banyak"} {seeAllValue ? t("seeLess") : t("seeMore")}
</Button> </Button>
</div> </div>
)} )}

View File

@ -135,7 +135,7 @@ export default function FilterImageComponent(props: {
} }
return newContent?.length > 0 ? ( return newContent?.length > 0 ? (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 w-full">
<p>{`Foto(${totalContent})`}</p> <p>{`Foto(${totalContent})`}</p>
<Carousel className="w-full max-w-7xl mx-auto"> <Carousel className="w-full max-w-7xl mx-auto">
<CarouselContent> <CarouselContent>

View File

@ -5,13 +5,7 @@ import "swiper/css/navigation";
import { getHeroData } from "@/service/landing/landing"; import { getHeroData } from "@/service/landing/landing";
import Link from "next/link"; import Link from "next/link";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname, useRouter } from "next/navigation";
import { import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
const Hero: React.FC = () => { const Hero: React.FC = () => {
const router = useRouter(); const router = useRouter();
@ -36,27 +30,15 @@ const Hero: React.FC = () => {
{heroData?.map((list: any) => ( {heroData?.map((list: any) => (
<CarouselItem key={list?.id}> <CarouselItem key={list?.id}>
<div className="relative h-[310px] lg:h-[420px]"> <div className="relative h-[310px] lg:h-[420px]">
<img <img src={list?.thumbnailLink} alt="Gambar Utama" className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover" />
src={list?.thumbnailLink}
alt="Gambar Utama"
className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg"> <div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1"> <span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{list?.categoryName}</span>
{list?.categoryName}
</span>
<Link href={`${locale}/image/detail/${list?.slug}`}> <Link href={`${locale}/image/detail/${list?.slug}`}>
<h2 className="text-lg font-bold mt-2">{list?.title}</h2> <h2 className="text-lg font-bold mt-2">{list?.title}</h2>
</Link> </Link>
<p className="text-xs flex flex-row items-center gap-1 mt-1"> <p className="text-xs flex flex-row items-center gap-1 mt-1">
{formatDateToIndonesian(new Date(list?.createdAt))}{" "} {formatDateToIndonesian(new Date(list?.createdAt))} {list?.timezone ? list?.timezone : "WIB"}|{" "}
{list?.timezone ? list?.timezone : "WIB"}|{" "} <svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path <path
fill="currentColor" fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9" d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
@ -79,30 +61,16 @@ const Hero: React.FC = () => {
{heroData?.map((item: any) => ( {heroData?.map((item: any) => (
<li key={item?.id} className="flex gap-4 flex-row lg:w-full "> <li key={item?.id} className="flex gap-4 flex-row lg:w-full ">
<div className="flex-shrink-0 w-24 rounded-lg"> <div className="flex-shrink-0 w-24 rounded-lg">
<img <img src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" />
src={item?.thumbnailLink}
alt={item?.title}
className="w-full max-h-14 object-cover rounded-lg"
/>
</div> </div>
<div className="w-[280px] lg:w-auto"> <div className="w-[280px] lg:w-auto">
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-xs font-bold uppercase w-fit"> <span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">{item?.categoryName}</span>
{item?.categoryName}
</span>
<Link href={`${locale}/image/detail/${item?.slug}`}> <Link href={`${locale}/image/detail/${item?.slug}`}>
<h3 className="text-sm font-bold mt-2"> <h3 className="text-base font-bold mt-2">{textEllipsis(item?.title, 30)}</h3>
{textEllipsis(item?.title, 30)}
</h3>
</Link> </Link>
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1"> <p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
{formatDateToIndonesian(new Date(item?.createdAt))}{" "} {formatDateToIndonesian(new Date(item?.createdAt))} {item?.timezone ? item?.timezone : "WIB"} |{" "}
{item?.timezone ? item?.timezone : "WIB"} |{" "} <svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path <path
fill="currentColor" fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9" d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"

View File

@ -3,7 +3,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import ThemeSwitcher from "@/components/partials/header/theme-switcher"; import ThemeSwitcher from "@/components/partials/header/theme-switcher";
import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi"; import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import { generateLocalizedPath } from "@/utils/globals"; import { generateLocalizedPath } from "@/utils/globals";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
@ -14,6 +14,8 @@ import { getCookiesDecrypt } from "@/lib/utils";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { getInfoProfile } from "@/service/auth"; import { getInfoProfile } from "@/service/auth";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useRouter } from "@/i18n/routing";
import LocalSwitcher from "../partials/header/locale-switcher";
type Detail = { type Detail = {
id: number; id: number;
@ -31,7 +33,6 @@ type Detail = {
const Navbar = () => { const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const router = useRouter(); const router = useRouter();
const pathname = usePathname();
const params = useParams(); const params = useParams();
const locale = params?.locale; const locale = params?.locale;
const [language, setLanguage] = useState<"id" | "en">("id"); const [language, setLanguage] = useState<"id" | "en">("id");
@ -40,7 +41,10 @@ const Navbar = () => {
const levelName = getCookiesDecrypt("ulnae"); const levelName = getCookiesDecrypt("ulnae");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [detail, setDetail] = useState<Detail>(); const [detail, setDetail] = useState<Detail>();
const t = useTranslations("Menu"); const t = useTranslations("LandingPage");
const [search, setSearch] = useState("");
const [onSearch, setOnSearch] = useState("");
const pathname = usePathname();
const onLogout = () => { const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => { Object.keys(Cookies.get()).forEach((cookieName) => {
@ -73,6 +77,16 @@ const Navbar = () => {
initState(); initState();
}, []); }, []);
const handleChange = (e: any) => {
setSearch(e.target.value);
};
// const handleKeypress = (e: any) => {
// if (e.which == 13) {
// handleSearch();
// }
// };
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-20 py-4 gap-3"> <div className="flex items-center justify-between px-4 lg:px-20 py-4 gap-3">
@ -100,7 +114,7 @@ const Navbar = () => {
<NavigationMenu> <NavigationMenu>
<NavigationMenuList> <NavigationMenuList>
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuTrigger> <NavigationMenuTrigger className="">
<a className="dark:text-white text-black flex flex-row justify-center items-center cursor-pointer"> <a className="dark:text-white text-black flex flex-row justify-center items-center cursor-pointer">
<svg className="mx-2 dark:" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg className="mx-2 dark:" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path
@ -111,29 +125,44 @@ const Navbar = () => {
{t("content")} {t("content")}
</a> </a>
</NavigationMenuTrigger> </NavigationMenuTrigger>
<NavigationMenuContent className="p-0 rounded-md overflow-hidden w-full"> <NavigationMenuContent className=" rounded-md overflow-hidden w-">
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 "> <NavigationMenuLink onClick={() => router.push("/image/filter")} className="flex place-items-start gap-1.5 p-2">
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiYoutube className="mr-2" />
Video
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 ">
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiMusic className="mr-2" />
Audio
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2">
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer"> <p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiImage className="mr-2" /> <FiImage className="mr-2" />
Foto {t("image")}
</p> </p>
</NavigationMenuLink> </NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2"> <NavigationMenuLink onClick={() => router.push("/video/filter")} className="flex items-start gap-1.5 p-2 ">
{pathname?.split("/")[1] == "in" ? (
<>
<p className="text-slate-600 text-sm dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-0 py-2 cursor-pointer">
<FiYoutube className="mr-2" />
{t("video")}
</p>
</>
) : (
<>
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiYoutube className="mr-2" />
{t("video")}
</p>
</>
)}
{/* <p className="text-slate-600 text-sm dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-0 py-2 cursor-pointer">
<FiYoutube className="mr-2" />
{t("video")}
</p> */}
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push("/document/filter")} className="flex place-items-start gap-1.5 p-2">
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer"> <p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiFile className="mr-2" /> <FiFile className="mr-2" />
Teks {t("text")}
</p>
</NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push("/audio/filter")} className="flex place-items-start gap-1.5 p-2 ">
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiMusic className="mr-2" />
{t("audio")}{" "}
</p> </p>
</NavigationMenuLink> </NavigationMenuLink>
</NavigationMenuContent> </NavigationMenuContent>
@ -149,7 +178,7 @@ const Navbar = () => {
/> />
</svg> </svg>
</span> </span>
Jadwal {t("schedule")}
</NavigationMenuLink> </NavigationMenuLink>
</Link> </Link>
</NavigationMenuItem> </NavigationMenuItem>
@ -166,7 +195,7 @@ const Navbar = () => {
/> />
</svg> </svg>
</span> </span>
Indeks {t("index")}
</NavigationMenuLink> </NavigationMenuLink>
</Link> </Link>
</NavigationMenuItem> </NavigationMenuItem>
@ -175,7 +204,7 @@ const Navbar = () => {
<Link href="#" className="flex items-center space-x-1 text-red-600"> <Link href="#" className="flex items-center space-x-1 text-red-600">
<span className="w-2 h-2 bg-red-500 rounded-full"></span> <span className="w-2 h-2 bg-red-500 rounded-full"></span>
<span className="font-medium">Live</span> <span className="font-medium">{t("live")}</span>
</Link> </Link>
<div className="flex items-center space-x-1 "> <div className="flex items-center space-x-1 ">
<a href="https://tvradio.polri.go.id/"> <a href="https://tvradio.polri.go.id/">
@ -183,39 +212,23 @@ const Navbar = () => {
</a> </a>
</div> </div>
{/* Languange */}
<div className="relative inline-block text-left"> <div className="relative inline-block text-left">
{/* Tombol Utama */} <LocalSwitcher />
<button onClick={() => setIsOpen(!isOpen)} className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg">
<img
src={language === "id" ? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" : "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"}
alt={language === "id" ? "Ind" : "Eng"}
className="w-3 h-3"
/>
<span>{language === "id" ? "Ind" : "Eng"}</span>
<span className="text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 32 32">
<path fill="currentColor" d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z" />
</svg>
</span>
</button>
{/* Dropdown Menu */}
{isOpen && (
<div className="absolute right-0 mt-2 w-auto bg-slate-200 border rounded-md shadow-lg z-10">
<button onClick={() => handleLanguageChange("id")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "id" ? "font-medium" : ""}`}>
<img src="https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" alt="Indonesia" className="w-5 h-5" />
<span>Ind</span>
</button>
<button onClick={() => handleLanguageChange("en")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "en" ? "font-medium" : ""}`}>
<img src="https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg" alt="English" className="w-5 h-5" />
<span>Eng</span>
</button>
</div>
)}
</div> </div>
{/* Dark Mode */}
<ThemeSwitcher /> <ThemeSwitcher />
<div className="relative text-gray-600 dark:text-white"> <div className="relative text-gray-600 dark:text-white">
<input type="text" placeholder="Pencarian" className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white" /> <input
value={onSearch}
onChange={(e) => setOnSearch(e.target.value)}
onKeyPress={() => router.push(`/all/filter?title=${onSearch}`)}
type="text"
placeholder={t("search")}
className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white"
/>
<span className="absolute left-4 top-1/2 transform -translate-y-1/2"> <span className="absolute left-4 top-1/2 transform -translate-y-1/2">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24">
<path <path
@ -227,6 +240,7 @@ const Navbar = () => {
</svg> </svg>
</span> </span>
</div> </div>
{/* <div className="flex items-center space-x-2"> {/* <div className="flex items-center space-x-2">
{fullName ? ( {fullName ? (
<> <>
@ -318,7 +332,7 @@ const Navbar = () => {
<Link href={"/"}> <Link href={"/"}>
<button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}> <button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}>
<Icon icon="heroicons:power" className="w-4 h-4" /> <Icon icon="heroicons:power" className="w-4 h-4" />
Log out {t("logOut")}
</button> </button>
</Link> </Link>
</div> </div>
@ -372,7 +386,7 @@ const Navbar = () => {
<Link href={"/"}> <Link href={"/"}>
<button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}> <button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}>
<Icon icon="heroicons:power" className="w-4 h-4" /> <Icon icon="heroicons:power" className="w-4 h-4" />
Log out {t("logOut")}
</button> </button>
</Link> </Link>
</div> </div>
@ -382,11 +396,11 @@ const Navbar = () => {
) : ( ) : (
// Masuk and Daftar buttons for roleId === null // Masuk and Daftar buttons for roleId === null
<div className="flex justify-center items-center mx-3 gap-5"> <div className="flex justify-center items-center mx-3 gap-5">
<Link href="/auth" className="w-full lg:w-fit px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"> <Link href="/auth" className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center">
Masuk {t("logIn")}
</Link> </Link>
<Link href="#" className="w-full lg:w-fit px-4 py-1 border border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] text-center hover:text-white"> <Link href="#" className="w-full lg:w-fit px-4 py-1 border border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] text-center hover:text-white">
Daftar {t("register")}
</Link> </Link>
</div> </div>
)} )}
@ -407,32 +421,32 @@ const Navbar = () => {
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
Konten {t("content")}
</a> </a>
</NavigationMenuTrigger> </NavigationMenuTrigger>
<NavigationMenuContent className="p-0 rounded-md overflow-hidden w-full"> <NavigationMenuContent className="p-0 rounded-md overflow-hidden w-full">
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 hover:bg-white"> <NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 hover:bg-white">
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer"> <p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiYoutube className="mr-2" /> <FiYoutube className="mr-2" />
Video {t("video")}
</p> </p>
</NavigationMenuLink> </NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white"> <NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer"> <p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiMusic className="mr-2" /> <FiMusic className="mr-2" />
Audio {t("audio")}
</p> </p>
</NavigationMenuLink> </NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white"> <NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer"> <p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiImage className="mr-2" /> <FiImage className="mr-2" />
Foto {t("image")}
</p> </p>
</NavigationMenuLink> </NavigationMenuLink>
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white"> <NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer"> <p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
<FiFile className="mr-2" /> <FiFile className="mr-2" />
Teks {t("text")}
</p> </p>
</NavigationMenuLink> </NavigationMenuLink>
</NavigationMenuContent> </NavigationMenuContent>
@ -448,7 +462,7 @@ const Navbar = () => {
/> />
</svg> </svg>
</span> </span>
Jadwal {t("schedule")}
</NavigationMenuLink> </NavigationMenuLink>
</Link> </Link>
</NavigationMenuItem> </NavigationMenuItem>
@ -465,7 +479,7 @@ const Navbar = () => {
/> />
</svg> </svg>
</span> </span>
Indeks {t("index")}
</NavigationMenuLink> </NavigationMenuLink>
</Link> </Link>
</NavigationMenuItem> </NavigationMenuItem>
@ -481,8 +495,11 @@ const Navbar = () => {
<img src="/assets/polriTv.png" className="object-contain h-11 flex items-center" /> <img src="/assets/polriTv.png" className="object-contain h-11 flex items-center" />
</a> </a>
</div> </div>
<div className="relative inline-block mx-3 text-left"> <div className="relative inline-block mx-3 text-left">
{/* Tombol Utama Bahasa */} <ThemeSwitcher />
{/* Tombol Utama Bahasa
<button onClick={() => setIsOpen(!isOpen)} className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg"> <button onClick={() => setIsOpen(!isOpen)} className="flex items-center space-x-2 p-2 text-gray-700 bg-slate-200 rounded-lg">
<img <img
src={language === "id" ? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" : "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"} src={language === "id" ? "https://upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Indonesia.svg" : "https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg"}
@ -495,9 +512,9 @@ const Navbar = () => {
<path fill="currentColor" d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z" /> <path fill="currentColor" d="M8.037 11.166L14.5 22.36c.825 1.43 2.175 1.43 3 0l6.463-11.195c.826-1.43.15-2.598-1.5-2.598H9.537c-1.65 0-2.326 1.17-1.5 2.6z" />
</svg> </svg>
</span> </span>
</button> </button> */}
{/* Dropdown Menu */} {/* Dropdown Menu
{isOpen && ( {isOpen && (
<div className="absolute right-0 mt-2 w-auto bg-slate-200 border rounded-md shadow-lg z-10"> <div className="absolute right-0 mt-2 w-auto bg-slate-200 border rounded-md shadow-lg z-10">
<button onClick={() => handleLanguageChange("id")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "id" ? "font-medium" : ""}`}> <button onClick={() => handleLanguageChange("id")} className={`flex items-center space-x-2 w-full px-4 py-2 ${language === "id" ? "font-medium" : ""}`}>
@ -509,7 +526,7 @@ const Navbar = () => {
<span>Eng</span> <span>Eng</span>
</button> </button>
</div> </div>
)} )} */}
</div> </div>
</div> </div>
@ -531,20 +548,20 @@ const Navbar = () => {
<DropdownMenuItem> <DropdownMenuItem>
<Link href="/profile" className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg"> <Link href="/profile" className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg">
<Icon icon="iconamoon:profile-circle-fill" /> <Icon icon="iconamoon:profile-circle-fill" />
Profile {t("profile")}
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<Link href="/content-management/galery" className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg"> <Link href="/content-management/galery" className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg">
<Icon icon="stash:save-ribbon-light" /> <Icon icon="stash:save-ribbon-light" />
Kelola Konten {t("contentManagement")}
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<Link href={"/"} className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg"> <Link href={"/"} className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg">
<button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}> <button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}>
<Icon icon="iconamoon:exit-bold" /> <Icon icon="iconamoon:exit-bold" />
Log Out {t("logOut")}
</button> </button>
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
@ -554,10 +571,10 @@ const Navbar = () => {
) : ( ) : (
<> <>
<Link href="/auth" className="px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-[#bb3523]"> <Link href="/auth" className="px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-[#bb3523]">
Masuk {t("logIn")}
</Link> </Link>
<Link href="#" className="px-4 py-1 border border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] hover:text-white"> <Link href="#" className="px-4 py-1 border border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] hover:text-white">
Daftar {t("register")}
</Link>{" "} </Link>{" "}
</> </>
)} )}

View File

@ -2,27 +2,23 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname, useRouter } from "next/navigation";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { formatDateToIndonesian } from "@/utils/globals"; import { formatDateToIndonesian } from "@/utils/globals";
import { getListContent } from "@/service/landing/landing"; import { getListContent } from "@/service/landing/landing";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { Reveal } from "./Reveal"; import { Reveal } from "./Reveal";
import { useTranslations } from "next-intl";
const NewContent = (props: { type: string }) => { const NewContent = (props: { type: string }) => {
const [newContent, setNewContent] = useState<any>(); const [newContent, setNewContent] = useState<any>();
const [selectedTab, setSelectedTab] = useState("video"); const [selectedTab, setSelectedTab] = useState("image");
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const params = useParams(); const params = useParams();
const locale = params?.locale; const locale = params?.locale;
const t = useTranslations("LandingPage");
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
@ -32,16 +28,7 @@ const NewContent = (props: { type: string }) => {
page: 0, page: 0,
size: 5, size: 5,
sortBy: props.type == "popular" ? "clickCount" : "createdAt", sortBy: props.type == "popular" ? "clickCount" : "createdAt",
contentTypeId: contentTypeId: selectedTab == "image" ? "1" : selectedTab == "video" ? "2" : selectedTab == "text" ? "3" : selectedTab == "audio" ? "4" : "",
selectedTab == "image"
? "1"
: selectedTab == "video"
? "2"
: selectedTab == "text"
? "3"
: selectedTab == "audio"
? "4"
: "",
}; };
const response = await getListContent(request); const response = await getListContent(request);
console.log("category", response); console.log("category", response);
@ -54,47 +41,46 @@ const NewContent = (props: { type: string }) => {
<div className="flex flex-col p-4"> <div className="flex flex-col p-4">
<div className="mx-auto w-full max-w-7xl justify-start flex px-5 flex-col lg:flex-row gap-5 mb-4"> <div className="mx-auto w-full max-w-7xl justify-start flex px-5 flex-col lg:flex-row gap-5 mb-4">
<h2 className="flex items-center text-xl lg:text-2xl w-fit font-bold bg-[#bb3523] px-4 py-1 rounded-lg text-white"> <h2 className="flex items-center text-xl lg:text-2xl w-fit font-bold bg-[#bb3523] px-4 py-1 rounded-lg text-white">
<span className="text-black ">Konten&nbsp;</span> {pathname?.split("/")[1] == "in" ? (
{props.type == "popular" <>
? "Populer" <span className="text-black ">{t("content")}</span>&nbsp;
: props.type == "latest" {props.type == "popular" ? "Terpopuler" : props.type == "latest" ? t("new") : "Serupa"}
? "Terbaru" </>
: "Serupa"} ) : (
<>
<span className="text-black ">{props.type == "popular" ? "Popular" : props.type == "latest" ? t("new") : "Serupa"}</span>&nbsp;
{t("content")}
</>
)}
</h2> </h2>
<Tabs value={selectedTab} onValueChange={setSelectedTab}> <Tabs value={selectedTab} onValueChange={setSelectedTab}>
<TabsList className="grid grid-cols-2 lg:flex lg:flex-row "> <TabsList className="grid grid-cols-2 lg:flex lg:flex-row ">
<TabsTrigger
value="video"
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
>
Audio Visual
</TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block">
|
</div>
<TabsTrigger
value="audio"
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
>
Audio
</TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block">
|
</div>
<TabsTrigger <TabsTrigger
value="image" value="image"
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary" className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
> >
Foto {t("image")}
</TabsTrigger> </TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block"> <div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
| <TabsTrigger
</div> value="video"
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
>
{t("video")}
</TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
<TabsTrigger <TabsTrigger
value="text" value="text"
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary" className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
> >
Teks {t("text")}
</TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
<TabsTrigger
value="audio"
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
>
{t("audio")}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</Tabs> </Tabs>
@ -105,29 +91,13 @@ const NewContent = (props: { type: string }) => {
<Carousel className="w-full max-w-7xl mx-auto"> <Carousel className="w-full max-w-7xl mx-auto">
<CarouselContent> <CarouselContent>
{newContent?.map((video: any) => ( {newContent?.map((video: any) => (
<CarouselItem <CarouselItem key={video?.id} className="md:basis-1/2 lg:basis-1/3">
key={video?.id} <Link href={`/video/detail/${video?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg">
className="md:basis-1/2 lg:basis-1/3" <img src={video?.thumbnailLink} className="w-full rounded-lg h-48 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
>
<Link
href={`/video/detail/${video?.slug}`}
className="relative group overflow-hidden shadow-md hover:shadow-lg"
>
<img
src={video?.thumbnailLink}
className="w-full rounded-lg h-48 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2"> <div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible"> <h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{video?.title}</h1>
{video?.title}
</h1>
<p className="flex flex-row items-center text-[10px] gap-2"> <p className="flex flex-row items-center text-[10px] gap-2">
{formatDateToIndonesian( {formatDateToIndonesian(new Date(video?.createdAt))} {video?.timezone ? video?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {video.clickCount}{" "}
new Date(video?.createdAt)
)}{" "}
{video?.timezone ? video?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{video.clickCount}{" "}
</p> </p>
</div> </div>
</Link> </Link>
@ -139,11 +109,7 @@ const NewContent = (props: { type: string }) => {
</Carousel> </Carousel>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<img <img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
) )
) : selectedTab == "audio" ? ( ) : selectedTab == "audio" ? (
@ -151,23 +117,11 @@ const NewContent = (props: { type: string }) => {
<Carousel className="w-full max-w-7xl mx-auto"> <Carousel className="w-full max-w-7xl mx-auto">
<CarouselContent> <CarouselContent>
{newContent?.map((audio: any) => ( {newContent?.map((audio: any) => (
<CarouselItem <CarouselItem key={audio?.id} className="md:basis-1/2 lg:basis-1/3">
key={audio?.id}
className="md:basis-1/2 lg:basis-1/3"
>
<div className="flex flex-row gap-6"> <div className="flex flex-row gap-6">
<Link <Link href={`/audio/detail/${audio?.slug}`} className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full">
href={`/audio/detail/${audio?.slug}`}
className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16"> <div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
<svg <svg width="32" height="34" viewBox="0 0 32 34" fill="null" xmlns="http://www.w3.org/2000/svg">
width="32"
height="34"
viewBox="0 0 32 34"
fill="null"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z" d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
fill="white" fill="white"
@ -177,20 +131,9 @@ const NewContent = (props: { type: string }) => {
<div className="flex flex-col flex-1"> <div className="flex flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm"> <div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
{formatDateToIndonesian( {formatDateToIndonesian(new Date(audio?.createdAt))} {audio?.timezone ? audio?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {audio?.clickCount}{" "}
new Date(audio?.createdAt)
)}{" "}
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
<Icon
icon="formkit:eye"
width="15"
height="15"
/>{" "}
{audio?.clickCount}{" "}
</div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
{audio?.title}
</div> </div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{audio?.title}</div>
</div> </div>
</Link> </Link>
</div> </div>
@ -202,11 +145,7 @@ const NewContent = (props: { type: string }) => {
</Carousel> </Carousel>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<img <img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
) )
) : selectedTab == "image" ? ( ) : selectedTab == "image" ? (
@ -214,29 +153,13 @@ const NewContent = (props: { type: string }) => {
<Carousel className="w-full max-w-7xl mx-auto"> <Carousel className="w-full max-w-7xl mx-auto">
<CarouselContent> <CarouselContent>
{newContent?.map((image: any) => ( {newContent?.map((image: any) => (
<CarouselItem <CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
key={image?.id} <Link href={`/image/detail/${image?.slug}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
className="md:basis-1/2 lg:basis-1/3" <img src={image?.thumbnailLink} className="w-full h-40 lg:h-60 object-cover rounded-lg group-hover:scale-100 transition-transform duration-300" />
>
<Link
href={`/image/detail/${image?.slug}`}
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
>
<img
src={image?.thumbnailLink}
className="w-full h-40 lg:h-60 object-cover rounded-lg group-hover:scale-100 transition-transform duration-300"
/>
<div className="absolute bottom-0 rounded-lg left-0 right-0 border-l-4 border-[#bb3523] bg-gray-600 text-white p-2"> <div className="absolute bottom-0 rounded-lg left-0 right-0 border-l-4 border-[#bb3523] bg-gray-600 text-white p-2">
<h1 className="text-sm font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible"> <h1 className="text-sm font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{image?.title}</h1>
{image?.title}
</h1>
<p className="flex flex-row items-center text-sm gap-2"> <p className="flex flex-row items-center text-sm gap-2">
{formatDateToIndonesian( {formatDateToIndonesian(new Date(image?.createdAt))} {image?.timezone ? image?.timezone : "WIB"}| <Icon icon="formkit:eye" width="15" height="15" /> {image?.clickCount}{" "}
new Date(image?.createdAt)
)}{" "}
{image?.timezone ? image?.timezone : "WIB"}|{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{image?.clickCount}{" "}
</p> </p>
</div> </div>
</Link> </Link>
@ -248,34 +171,18 @@ const NewContent = (props: { type: string }) => {
</Carousel> </Carousel>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<img <img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
) )
) : newContent.length > 0 ? ( ) : newContent.length > 0 ? (
<Carousel className="w-full max-w-7xl mx-auto"> <Carousel className="w-full max-w-7xl mx-auto">
<CarouselContent> <CarouselContent>
{newContent?.map((text: any) => ( {newContent?.map((text: any) => (
<CarouselItem <CarouselItem key={text?.id} className="md:basis-1/2 lg:basis-1/3">
key={text?.id}
className="md:basis-1/2 lg:basis-1/3"
>
<div className="md:basis-1/2 lg:basis-1/3"> <div className="md:basis-1/2 lg:basis-1/3">
<Link <Link href={`/document/detail/${text?.slug}`} className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4">
href={`/document/detail/${text?.slug}`}
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4"
>
<div className="flex items-center justify-center rounded-lg w-16 h-2 lg:h-16"> <div className="flex items-center justify-center rounded-lg w-16 h-2 lg:h-16">
<svg <svg width="28" height="34" viewBox="0 0 28 34" fill="none" xmlns="http://www.w3.org/2000/svg">
width="28"
height="34"
viewBox="0 0 28 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z" d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
fill="black" fill="black"
@ -285,29 +192,17 @@ const NewContent = (props: { type: string }) => {
<div className="flex w-full pr-10 flex-col flex-1"> <div className="flex w-full pr-10 flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center text-xs gap-0 lg:gap-1 mt-1 lg:text-sm"> <div className="text-gray-500 dark:text-gray-400 flex flex-row items-center text-xs gap-0 lg:gap-1 mt-1 lg:text-sm">
{formatDateToIndonesian( {formatDateToIndonesian(new Date(text?.createdAt))}
new Date(text?.createdAt)
)}
{text?.timezone ? text?.timezone : "WIB"}| {text?.timezone ? text?.timezone : "WIB"}|
<Icon icon="formkit:eye" width="15" height="15" /> <Icon icon="formkit:eye" width="15" height="15" />
{text?.clickCount} {text?.clickCount}
</div> </div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible "> <div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible ">{text?.title}</div>
{text?.title}
</div>
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500"> <div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
<svg <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512">
xmlns="http://www.w3.org/2000/svg" <path fill="#f00" d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z" />
width="1em"
height="1em"
viewBox="0 0 512 512"
>
<path
fill="#f00"
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
/>
</svg> </svg>
Download Dokumen Download {t("document")}
</div> </div>
</div> </div>
</Link> </Link>
@ -320,21 +215,14 @@ const NewContent = (props: { type: string }) => {
</Carousel> </Carousel>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<img <img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
)} )}
</div> </div>
</div> </div>
<div className="flex items-center flex-row justify-center"> <div className="flex items-center flex-row justify-center">
<Link <Link href={`/${selectedTab}/filter?sortBy=${props.type}`} className="border text-[#bb3523] rounded-lg text-sm lg:text-md px-4 py-1 border-[#bb3523]">
href={`/${selectedTab}/filter?sortBy=${props.type}`} {t("seeAll")}
className="border text-[#bb3523] rounded-lg text-sm lg:text-md px-4 py-1 border-[#bb3523]"
>
LIHAT SEMUA
</Link> </Link>
</div> </div>
</Reveal> </Reveal>

View File

@ -1,42 +1,26 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi"; import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useRouter } from "@/i18n/routing"; import { useRouter } from "@/i18n/routing";
import { useTranslations } from "next-intl";
const SearchSection = () => { const SearchSection = () => {
const [contentType, setContentType] = useState("all"); const [contentType, setContentType] = useState("all");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const router = useRouter(); const router = useRouter();
const t = useTranslations("LandingPage");
return ( return (
<section className="w-full py-8 px-4 "> <section className="w-full py-8 px-4 ">
<div className="max-w-screen-xl mx-auto text-center"> <div className="max-w-screen-xl mx-auto text-center">
{/* Heading */} {/* Heading */}
<h1 className="text-2xl md:text-3xl font-bold text-gray-800 dark:text-white"> <h1 className="text-2xl md:text-3xl font-bold text-gray-800 dark:text-white">
<span className="text-[#bb3523] dark:text-white">Eksplorasi</span> dan{" "} <span className="text-[#bb3523] dark:text-white">{t("exploration")}</span> {t("and")} <span className="text-[#bb3523] dark:text-white">{t("download")}</span> {t("coverage")}{" "}
<span className="text-[#bb3523] dark:text-white">Download</span>{" "}
Liputan Resmi Kami
</h1> </h1>
<div className="w-[80%] h-1 bg-[#bb3523] mx-auto mt-2"></div> <div className="w-[80%] h-1 bg-[#bb3523] mx-auto mt-2"></div>
<p className="text-sm md:text-base text-gray-500 dark:text-gray-100 mt-4"> <p className="text-sm md:text-base text-gray-500 dark:text-gray-100 mt-4">{t("officialCoverage")}</p>
Liputan resmi yang bersumber dari kegiatan Polri di Mabes dan Polda
seluruh Indonesia
</p>
{/* Search Form */} {/* Search Form */}
<div className="mt-6 flex flex-col md:flex-row justify-center gap-4"> <div className="mt-6 flex flex-col md:flex-row justify-center gap-4">
@ -48,45 +32,31 @@ const SearchSection = () => {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem value="all">Semua Konten</SelectItem> <SelectItem value="all">{t("allContent")}</SelectItem>
<SelectItem value="image">Foto</SelectItem> <SelectItem value="image">{t("image")}</SelectItem>
<SelectItem value="video">Audio Visual</SelectItem> <SelectItem value="video">{t("video")}</SelectItem>
<SelectItem value="document">Teks</SelectItem> <SelectItem value="document">{t("text")}</SelectItem>
<SelectItem value="audio">Audio</SelectItem> <SelectItem value="audio">{t("audio")}</SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
<div className="flex items-center flex-1 border border-gray-300 rounded-lg overflow-hidden"> <div className="flex items-center flex-1 border border-gray-300 rounded-lg overflow-hidden">
<span className="material-icons text-black dark:text-white px-4"> <span className="material-icons text-black dark:text-white px-4">
<svg <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path <path
fill="currentColor" fill="currentColor"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14" d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"
/> />
</svg> </svg>
</span> </span>
<input <input type="text" placeholder={t("search")} className="w-full py-2 px-2 text-sm text-gray-700 dark:text-gray-100 focus:outline-none" onChange={(e) => setSearch(e.target.value)} />
type="text"
placeholder="Pencarian"
className="w-full py-2 px-2 text-sm text-gray-700 dark:text-gray-100 focus:outline-none"
onChange={(e) => setSearch(e.target.value)}
/>
</div> </div>
</div> </div>
{/* Search Input */} {/* Search Input */}
<button <button onClick={() => router.push(`/${contentType}/filter?title=${search}`)} className="flex justify-center items-center px-6 w-full lg:w-[20%] py-2 bg-[#bb3523] gap-2 text-white rounded-lg hover:bg-red-700">
onClick={() => {t("searchCoverage")}
router.push(`/${contentType}/filter?title=${search}`) <Icon icon="ri:arrow-right-s-line" fontSize={20} />
}
className="flex justify-center items-center px-6 w-full lg:w-[20%] py-2 bg-[#bb3523] gap-2 text-white rounded-lg hover:bg-red-700"
>
Cari Liputan <Icon icon="ri:arrow-right-s-line" fontSize={20} />
</button> </button>
</div> </div>
</div> </div>

View File

@ -306,5 +306,68 @@
"version": "Version's", "version": "Version's",
"changelog": "CHANGELOG", "changelog": "CHANGELOG",
"versionHistory": "VERSION HISTORY" "versionHistory": "VERSION HISTORY"
},
"LandingPage": {
"content": "Content",
"new": "Latest",
"schedule": "Schedule",
"index": "Index",
"search": "Search",
"exploration": "Exploration",
"and": "and",
"download": "Download",
"coverage": "Our Official Coverage",
"officialCoverage": "Official coverage sourced from Polri activities at the National Police Headquarters and Regional Police throughout Indonesia",
"allContent": "All Content",
"searchCoverage": "Search Coverage",
"newContent": "Latest Content",
"video": "Video",
"audio": "Audio",
"image": "Image",
"text": "Text",
"document": "Document",
"seeAll": "See All",
"category": "Category",
"contentCategory": "Content Category",
"seeMore": "See More",
"area": "Area",
"coverageOnly": "Coverage",
"searchRegional": "Search for Regional Police",
"divisionCoverage": "Division Coverage",
"searchDivision": "Search Division",
"division": "Division",
"seeLess": "See Less",
"logIn": "Log In",
"logOut": "Log Out",
"register": "Register",
"profile": "Profile",
"contentManagement": "Content Management",
"live": "Live",
"notFound": "Not Found"
},
"FilterPage": {
"image": "Image",
"video": "Video",
"text": "Text",
"audio": "Audio",
"allImage": "All Image",
"allVideo": "All Video",
"allText": "All Text",
"allAudio": "All Audio",
"search": "Search",
"searchTitle": "Find Title...",
"monthYear": "Month and Year",
"thereIs": "There is",
"downloadableImage": "downloadable image",
"downloadableVideo": "downloadable video",
"downloadableText": "downloadable text",
"downloadableAudio": "downloadable audio",
"date": "Date",
"selectYear": "Select Month and Year",
"selectDate": "Select Date",
"categories": "Categories",
"sortBy": "Sort by",
"latest": "Latest",
"mostPopular": "Most Popular"
} }
} }

View File

@ -306,5 +306,68 @@
"version": "Version's", "version": "Version's",
"changelog": "CHANGELOG", "changelog": "CHANGELOG",
"versionHistory": "VERSION HISTORY" "versionHistory": "VERSION HISTORY"
},
"LandingPage": {
"content": "Konten",
"new": "Terbaru",
"schedule": "Jadwal",
"index": "Indeks",
"search": "Pencarian",
"exploration": "Eksplorasi",
"and": "dan",
"download": "Download",
"coverage": "Liputan Resmi Kami",
"officialCoverage": "Liputan resmi yang bersumber dari kegiatan Polri di Mabes dan Polda seluruh Indonesia",
"allContent": "Semua Konten",
"searchCoverage": "Cari Liputan",
"newContent": "Konten Terbaru",
"video": "Audio Visual",
"audio": "Audio",
"image": "Foto",
"text": "Teks",
"document": "Dokumen",
"category": "Kategori",
"seeAll": "Lihat Semua",
"coverageOnly": "Liputan",
"contentCategory": "Kategori Konten",
"seeMore": "Lihat Lebih Banyak",
"area": "Wilayah",
"searchRegional": "Cari Polda",
"divisionCoverage": "Liputan Satker",
"searchDivision": "Cari Satker",
"logIn": "Masuk",
"logOut": "Keluar",
"register": "Daftar",
"profile": "Profil",
"contentManagement": "Kelola Konten",
"live": "Live",
"seeLess": "Lihat Lebih Sedikit",
"notFound": "Tidak Ditemukan",
"division": "Satker"
},
"FilterPage": {
"image": "Foto",
"video": "Audio Visual",
"text": "Teks",
"audio": "Audio",
"allImage": "Semua Foto",
"allVideo": "Semua Audio Visual",
"allText": "Semua Teks",
"allAudio": "Semua Audio",
"search": "Pencarian",
"searchTitle": "Cari Judul...",
"monthYear": "Bulan dan Tahun",
"thereIs": "Terdapat",
"downloadableImage": "artikel berisi Foto yang dapat di unduh",
"downloadableVideo": "artikel berisi Audio Visual yang dapat di unduh",
"downloadableText": "artikel berisi Teks yang dapat di unduh",
"downloadableAudio": "artikel berisi Audio yang dapat di unduh",
"date": "Tanggal",
"selectYear": "Pilih Bulan dan Tahun",
"selectDate": "Pilih Tanggal",
"categories": "Kategori",
"sortBy": "Urutkan Berdasarkan",
"latest": "Terbaru",
"mostPopular": "Terpopuler"
} }
} }

View File

@ -1,4 +1,7 @@
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service"; import {
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
// export async function listDataAll( // export async function listDataAll(
// isForSelf, // isForSelf,
@ -41,40 +44,103 @@ export async function listDataAll(
); );
} }
export async function listDataImage(limit: any, page: any, isForSelf: any, isApproval: any, categoryFilter: any, statusFilter: any, needApprovalFromLevel: any, creator: any, source: any, startDate: any, endDate: any, title: string = "") { export async function listDataImage(
limit: any,
page: any,
isForSelf: any,
isApproval: any,
categoryFilter: any,
statusFilter: any,
needApprovalFromLevel: any,
creator: any,
source: any,
startDate: any,
endDate: any,
title: string = ""
) {
return await httpGetInterceptor( return await httpGetInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=1&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}` `media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=1&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
); );
} }
export async function listDataVideo(limit: any, page: any, isForSelf: any, isApproval: any, categoryFilter: any, statusFilter: any, needApprovalFromLevel: any, creator: any, source: any, startDate: any, endDate: any, title: string = "") { export async function listDataVideo(
limit: any,
page: any,
isForSelf: any,
isApproval: any,
categoryFilter: any,
statusFilter: any,
needApprovalFromLevel: any,
creator: any,
source: any,
startDate: any,
endDate: any,
title: string = ""
) {
return await httpGetInterceptor( return await httpGetInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=2&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}` `media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=2&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
); );
} }
export async function listDataTeks(limit: any, page: any, isForSelf: any, isApproval: any, categoryFilter: any, statusFilter: any, needApprovalFromLevel: any, creator: any, source: any, startDate: any, endDate: any, title: string = "") { export async function listDataTeks(
limit: any,
page: any,
isForSelf: any,
isApproval: any,
categoryFilter: any,
statusFilter: any,
needApprovalFromLevel: any,
creator: any,
source: any,
startDate: any,
endDate: any,
title: string = ""
) {
return await httpGetInterceptor( return await httpGetInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=3&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}` `media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=3&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
); );
} }
export async function listDataAudio(page: any, limit: any, isForSelf: any, isApproval: any, categoryFilter: any, statusFilter: any, needApprovalFromLevel: any, creator: any, source: any, startDate: any, endDate: any, title: string = "") { export async function listDataAudio(
limit: any,
page: any,
isForSelf: any,
isApproval: any,
categoryFilter: any,
statusFilter: any,
needApprovalFromLevel: any,
creator: any,
source: any,
startDate: any,
endDate: any,
title: string = ""
) {
return await httpGetInterceptor( return await httpGetInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=4&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}` `media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=4&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
); );
} }
export async function listSPIT(page: any, limit: any, title = "", isPublish: any) { export async function listSPIT(
return await httpGetInterceptor(`media/spit/pagination?enablePage=1&page=${page}&size=${limit}&sort=desc&sortBy=contentTitleId&title=${title}&isPublish=${isPublish}`); page: any,
limit: any,
title = "",
isPublish: any
) {
return await httpGetInterceptor(
`media/spit/pagination?enablePage=1&page=${page}&size=${limit}&sort=desc&sortBy=contentTitleId&title=${title}&isPublish=${isPublish}`
);
} }
export async function listNulisAI(limit: any, page: any, title: string = "") { export async function listNulisAI(limit: any, page: any, title: string = "") {
return await httpGetInterceptor(`media/nulis-ai/pagination?enablePage=1&page=${page}&size=${limit}&title=${title}`); return await httpGetInterceptor(
`media/nulis-ai/pagination?enablePage=1&page=${page}&size=${limit}&title=${title}`
);
} }
export async function getTagsBySubCategoryId(subCategory: any) { export async function getTagsBySubCategoryId(subCategory: any) {
return await httpGetInterceptor(`media/tags/list?subCategoryId=${subCategory}`); return await httpGetInterceptor(
`media/tags/list?subCategoryId=${subCategory}`
);
} }
export async function listEnableCategory(type: any) { export async function listEnableCategory(type: any) {
@ -100,8 +166,8 @@ export async function createMedia(data: any) {
export async function uploadThumbnail(id: any, data: any) { export async function uploadThumbnail(id: any, data: any) {
const url = `media/upload?id=${id}&operation=thumbnail`; const url = `media/upload?id=${id}&operation=thumbnail`;
const headers = { const headers = {
'Content-Type': 'multipart/form-data' "Content-Type": "multipart/form-data",
} };
return httpPostInterceptor(url, data, { headers }); return httpPostInterceptor(url, data, { headers });
} }
@ -130,6 +196,6 @@ export async function saveContentRewrite(data: any) {
return httpPostInterceptor(url, data); return httpPostInterceptor(url, data);
} }
export async function saveUserReports(data: any) { export async function saveUserReports(data: any) {
const url = 'public/users/reports'; const url = "public/users/reports";
return httpPostInterceptor( url, data ); return httpPostInterceptor(url, data);
} }

View File

@ -22,3 +22,8 @@ export async function getContestById(id: any, pages = 0) {
const url = `contest?id=${id}&page=${pages}`; const url = `contest?id=${id}&page=${pages}`;
return httpGetInterceptor(url); return httpGetInterceptor(url);
} }
export async function postCreateContest(data: any) {
const url = "contest";
return httpPostInterceptor(url, data);
}

View File

@ -3,6 +3,7 @@ import axiosInterceptorInstance from "./axios-interceptor-instance";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
export async function httpGetInterceptor(pathUrl: any) { export async function httpGetInterceptor(pathUrl: any) {
const pathname = window.location.pathname;
const response = await axiosInterceptorInstance const response = await axiosInterceptorInstance
.get(pathUrl) .get(pathUrl)
.catch((error) => error.response); .catch((error) => error.response);
@ -17,7 +18,9 @@ export async function httpGetInterceptor(pathUrl: any) {
Object.keys(Cookies.get()).forEach((cookieName) => { Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName); Cookies.remove(cookieName);
}); });
// window.location.href = "/"; if (pathname?.includes("/contributor/") || pathname?.includes("/admin/") || pathname?.includes("/supervisor/")) {
window.location.href = "/";
}
} else { } else {
return { return {
error: true, error: true,

View File

@ -2,7 +2,11 @@
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; *CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac esac
if [ -x "$basedir/node" ]; then if [ -x "$basedir/node" ]; then

View File

@ -2,7 +2,11 @@
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; *CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac esac
if [ -x "$basedir/node" ]; then if [ -x "$basedir/node" ]; then