feat:update agenda setting, task,content

This commit is contained in:
Anang Yusman 2025-01-16 03:02:28 +08:00
parent 01b70c708f
commit ffb75f9b46
21 changed files with 2255 additions and 951 deletions

View File

@ -46,7 +46,6 @@ export type CalendarEvent = {
}; };
}; };
const INITIAL_YEAR = dayjs().format("YYYY"); const INITIAL_YEAR = dayjs().format("YYYY");
const INITIAL_MONTH = dayjs().format("M"); const INITIAL_MONTH = dayjs().format("M");
@ -86,7 +85,7 @@ interface MonthCardProps {
interface ListItemProps { interface ListItemProps {
item: any; item: any;
text: string text: string;
createdBy: string; createdBy: string;
bgColor: string; bgColor: string;
} }
@ -120,7 +119,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const [selectedYear, setSelectedYear] = useState(new Date()); const [selectedYear, setSelectedYear] = useState(new Date());
const [selectedMonth, setSelectedMonth] = useState( const [selectedMonth, setSelectedMonth] = useState(
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)), dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
); );
const [dragEvents] = useState([ const [dragEvents] = useState([
@ -142,7 +141,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const getCalendarEvents = async () => { const getCalendarEvents = async () => {
console.log("View : ", activeView); console.log("View : ", activeView);
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, selectedMonth.format("M") || INITIAL_MONTH, ""); const res = await getAgendaSettingsList(
selectedMonth?.format("YYYY") || INITIAL_YEAR,
selectedMonth.format("M") || INITIAL_MONTH,
""
);
console.log("View : API Response:", res); console.log("View : API Response:", res);
if (res?.error) { if (res?.error) {
@ -175,13 +178,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
}; };
const getYearlyEvents = async () => { const getYearlyEvents = async () => {
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, '', ''); const res = await getAgendaSettingsList(
selectedMonth?.format("YYYY") || INITIAL_YEAR,
"",
""
);
if (res?.error) { if (res?.error) {
error(res.message); error(res.message);
return false; return false;
} }
setYearlyData(res?.data?.data); setYearlyData(res?.data?.data);
} };
useEffect(() => { useEffect(() => {
setSelectedCategory(categories?.map((c) => c.value)); setSelectedCategory(categories?.map((c) => c.value));
@ -218,8 +225,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
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( const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
(item) => ({
id: item.id.toString(), id: item.id.toString(),
title: item.title, title: item.title,
createBy: "Mabes Polri - Approver", createBy: "Mabes Polri - Approver",
@ -231,8 +237,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
calendar: item.agendaType, calendar: item.agendaType,
description: item.description, description: item.description,
}, },
}) }));
);
setApiEvents(eventsFromAPI); setApiEvents(eventsFromAPI);
} else { } else {
@ -321,7 +326,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const renderEventContent = (eventInfo: any) => { const renderEventContent = (eventInfo: any) => {
const { title } = eventInfo.event; const { title } = eventInfo.event;
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps const { createdByName } = eventInfo.event.extendedProps;
return ( return (
<> <>
@ -340,6 +345,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
return "bg-blue-400 border-none"; return "bg-blue-400 border-none";
} else if (arg.event.extendedProps.calendar === "polres") { } else if (arg.event.extendedProps.calendar === "polres") {
return "bg-slate-400 border-none"; return "bg-slate-400 border-none";
} else if (arg.event.extendedProps.calendar === "satker") {
return "bg-orange-500 border-none";
} else if (arg.event.extendedProps.calendar === "international") { } else if (arg.event.extendedProps.calendar === "international") {
return "bg-green-400 border-none"; return "bg-green-400 border-none";
} else { } else {
@ -362,26 +369,26 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
}; };
const months: Array<{ id: keyof YearlyData; label: string }> = [ const months: Array<{ id: keyof YearlyData; label: string }> = [
{ id: 'january', label: 'Januari' }, { id: "january", label: "Januari" },
{ id: 'february', label: 'Februari' }, { id: "february", label: "Februari" },
{ id: 'march', label: 'Maret' }, { id: "march", label: "Maret" },
{ id: 'april', label: 'April' }, { id: "april", label: "April" },
{ id: 'may', label: 'Mei' }, { id: "may", label: "Mei" },
{ id: 'june', label: 'Juni' }, { id: "june", label: "Juni" },
{ id: 'july', label: 'Juli' }, { id: "july", label: "Juli" },
{ id: 'august', label: 'Agustus' }, { id: "august", label: "Agustus" },
{ id: 'september', label: 'September' }, { id: "september", label: "September" },
{ id: 'october', label: 'Oktober' }, { id: "october", label: "Oktober" },
{ id: 'november', label: 'November' }, { id: "november", label: "November" },
{ id: 'december', label: 'Desember' } { id: "december", label: "Desember" },
]; ];
const getEventColor = (type: Event['type']): string => { const getEventColor = (type: Event["type"]): string => {
const colors: Record<Event['type'], string> = { const colors: Record<Event["type"], string> = {
mabes: 'bg-yellow-500', mabes: "bg-yellow-500",
polda: 'bg-blue-400', polda: "bg-blue-400",
polres: 'bg-slate-400', polres: "bg-slate-400",
international: 'bg-green-400' international: "bg-green-400",
}; };
return colors[type]; return colors[type];
}; };
@ -397,12 +404,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
allDay: true, allDay: true,
extendedProps: { extendedProps: {
calendar: item.agendaType, calendar: item.agendaType,
description: item.description description: item.description,
} },
}; };
const finalEvent: any = { const finalEvent: any = {
event: formattedEvent event: formattedEvent,
} };
console.log("Event click custom : ", finalEvent); console.log("Event click custom : ", finalEvent);
@ -410,14 +417,20 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
setSheetOpen(true); setSheetOpen(true);
setApiEvents(finalEvent); setApiEvents(finalEvent);
wait().then(() => (document.body.style.pointerEvents = "auto")); wait().then(() => (document.body.style.pointerEvents = "auto"));
} };
const ListItem: React.FC<ListItemProps> = ({ item, text, createdBy, bgColor }) => ( const ListItem: React.FC<ListItemProps> = ({
<div className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`} onClick={() => handleClickListItem(item)}> item,
text,
createdBy,
bgColor,
}) => (
<div
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
onClick={() => handleClickListItem(item)}
>
<p className="ml-1">{text}</p> <p className="ml-1">{text}</p>
<p className="ml-1 text-xs text-start mt-2"> <p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
Created By: {createdBy}
</p>
</div> </div>
); );
@ -462,7 +475,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
> >
<Card> <Card>
<CardHeader className="bg-default p-2 rounded-t-lg"> <CardHeader className="bg-default p-2 rounded-t-lg">
<CardTitle className="text-default-foreground text-base py-0">{label}</CardTitle> <CardTitle className="text-default-foreground text-base py-0">
{label}
</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="p-2 text-sm"> <CardContent className="p-2 text-sm">
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2"> <div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
@ -494,16 +509,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5"> <Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
<CardContent className="p-0"> <CardContent className="p-0">
<CardHeader className="border-none mb-2 pt-5"> <CardHeader className="border-none mb-2 pt-5">
{roleId == 11 || roleId == 12 ? {roleId == 11 || roleId == 12 ? (
<Button <Button
onClick={handleDateClick} onClick={handleDateClick}
className="dark:bg-background dark:text-foreground" className="dark:bg-background dark:text-foreground"
> >
<Plus className="w-4 h-4 me-1" /> <Plus className="w-4 h-4 me-1" />
{"Tambahkan Agenda baru"} {"Tambahkan Agenda baru"}
</Button> : </Button>
) : (
"" ""
} )}
</CardHeader> </CardHeader>
<div className="px-3"> <div className="px-3">
@ -597,35 +613,53 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
handleDateChange(info.view.currentStart, info.view.currentEnd); handleDateChange(info.view.currentStart, info.view.currentEnd);
handleViewChange(info.view.type); handleViewChange(info.view.type);
}} }}
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""} viewClassNames={
activeView === "listYear" ? "hide-calendar-grid" : ""
}
/> />
{activeView === "listYear" && ( {activeView === "listYear" && (
<div className="custom-ui"> <div className="custom-ui">
<div className="flex gap-1 mt-1"> <div className="flex gap-1 mt-1">
{months.slice(0, 3).map(month => ( {months.slice(0, 3).map((month) => (
<MonthCard key={month.id} monthId={month.id} label={month.label} /> <MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))} ))}
</div> </div>
{/* Second Row */} {/* Second Row */}
<div className="flex gap-1 mt-1"> <div className="flex gap-1 mt-1">
{months.slice(3, 6).map(month => ( {months.slice(3, 6).map((month) => (
<MonthCard key={month.id} monthId={month.id} label={month.label} /> <MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))} ))}
</div> </div>
{/* Third Row */} {/* Third Row */}
<div className="flex gap-1 mt-1"> <div className="flex gap-1 mt-1">
{months.slice(6, 9).map(month => ( {months.slice(6, 9).map((month) => (
<MonthCard key={month.id} monthId={month.id} label={month.label} /> <MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))} ))}
</div> </div>
{/* Fourth Row */} {/* Fourth Row */}
<div className="flex gap-1 mt-1"> <div className="flex gap-1 mt-1">
{months.slice(9, 12).map(month => ( {months.slice(9, 12).map((month) => (
<MonthCard key={month.id} monthId={month.id} label={month.label} /> <MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))} ))}
</div> </div>
</div> </div>

View File

@ -36,9 +36,9 @@ 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";
import { useRouter } from "next/navigation";
import { postSchedule } from "@/service/schedule/schedule"; import { postSchedule } from "@/service/schedule/schedule";
import { import {
deleteAgendaSettings,
getAgendaSettingsById, getAgendaSettingsById,
saveAgendaSettings, saveAgendaSettings,
} from "@/service/agenda-setting/agenda-setting"; } from "@/service/agenda-setting/agenda-setting";
@ -52,6 +52,10 @@ import { Icon } from "@iconify/react";
import Image from "next/image"; import Image from "next/image";
import { UnitMapping } from "./unit-mapping"; import { UnitMapping } from "./unit-mapping";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { usePathname, useRouter } from "next/navigation";
import { Card } from "@/components/ui/card";
import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js";
const schema = z.object({ const schema = z.object({
title: z.string().min(3, { message: "Required" }), title: z.string().min(3, { message: "Required" }),
@ -80,15 +84,16 @@ const EventModal = ({
event: any; event: any;
selectedDate: any; selectedDate: any;
}) => { }) => {
const [detail, setDetail] = useState<any>();
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 [agendaType, setAgendaType] = React.useState<any>(categories[0].value);
const [listDest, setListDest] = useState([]); const [listDest, setListDest] = useState([]);
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false); const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null); const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const pathname = usePathname();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [checkedLevels, setCheckedLevels] = useState(new Set()); const [checkedLevels, setCheckedLevels] = useState(new Set());
const [expandedPolda, setExpandedPolda] = useState([{}]); const [expandedPolda, setExpandedPolda] = useState([{}]);
@ -129,11 +134,16 @@ const EventModal = ({
semua: false, semua: false,
nasional: false, nasional: false,
polda: false, polda: false,
polres: false,
satker: false, satker: false,
internasional: false, international: false,
}); });
const [selectedPolda, setSelectedPolda] = React.useState([]); const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType
const [selectedPolda, setSelectedPolda] = React.useState([]); // Untuk data Polda
const [selectedSatker, setSelectedSatker] = React.useState([]); const [selectedSatker, setSelectedSatker] = React.useState([]);
const [selectedPolres, setSelectedPolres] = React.useState([]);
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false);
const { const {
register, register,
@ -153,6 +163,14 @@ const EventModal = ({
const detail = res?.data?.data; const detail = res?.data?.data;
setDetailData(detail); setDetailData(detail);
const description = detail?.description;
console.log("description", res?.data?.description);
// Set nilai awal description ke form control
if (description) {
setValue("description", description);
}
const attachments = detail?.attachments; const attachments = detail?.attachments;
setImageUploadedFiles( setImageUploadedFiles(
attachments?.filter((file: any) => file.fileTypeId == 1) attachments?.filter((file: any) => file.fileTypeId == 1)
@ -169,7 +187,7 @@ const EventModal = ({
} }
fetchDetailData(); fetchDetailData();
}, [event]); }, [event, setValue]);
// useEffect(() => { // useEffect(() => {
// async function fetchPoldaPolres() { // async function fetchPoldaPolres() {
@ -226,22 +244,32 @@ const EventModal = ({
setWilayahPublish((prev: any) => { setWilayahPublish((prev: any) => {
const newState = { ...prev, [key]: !prev[key] }; const newState = { ...prev, [key]: !prev[key] };
// Handle "semua" logic to uncheck other options // Handle "semua" logic to check all options
if (key === "semua" && newState.semua) { if (key === "semua" && newState.semua) {
setAgendaType("all");
return { return {
semua: true, semua: true,
nasional: false, nasional: true,
polda: false, polda: true,
satker: false, polres: true,
internasional: false, satker: true,
international: true,
}; };
} }
// Uncheck "semua" if any other is selected // Uncheck "semua" if any other option is selected
if (key !== "semua") { if (key !== "semua") {
newState.semua = false; newState.semua = false;
} }
// Set agendaType based on the selected checkbox
if (newState.nasional) setAgendaType("mabes");
else if (newState.polda) setAgendaType("polda");
else if (newState.polres) setAgendaType("polres");
else if (newState.satker) setAgendaType("satker");
else if (newState.international) setAgendaType("international");
else setAgendaType(""); // Reset if no checkbox is selected
return newState; return newState;
}); });
}; };
@ -251,12 +279,15 @@ const EventModal = ({
if (wilayahPublish.semua) publishTo.push("all"); if (wilayahPublish.semua) publishTo.push("all");
if (wilayahPublish.nasional) publishTo.push("mabes"); if (wilayahPublish.nasional) publishTo.push("mabes");
if (wilayahPublish.polda) publishTo.push(...selectedPolda); if (wilayahPublish.polda) publishTo.push(...selectedPolda);
if (wilayahPublish.polres) publishTo.push(...selectedPolres);
if (wilayahPublish.satker) publishTo.push(...selectedSatker); if (wilayahPublish.satker) publishTo.push(...selectedSatker);
if (wilayahPublish.internasional) publishTo.push("internasional"); if (wilayahPublish.international) publishTo.push("international");
const reqData = { const reqData = {
id: detailData?.id,
title: data.title, title: data.title,
description: data.description, description: data.description,
agendaType, // Include agendaType in request
publishTo, publishTo,
startDate: format(startDate, "yyyy-MM-dd"), startDate: format(startDate, "yyyy-MM-dd"),
endDate: format(endDate, "yyyy-MM-dd"), endDate: format(endDate, "yyyy-MM-dd"),
@ -273,50 +304,40 @@ const EventModal = ({
const id = response?.data?.data.id; const id = response?.data?.data.id;
loading(); loading();
if (imageFiles?.length == 0) { if (imageFiles?.length === 0) {
setIsImageUploadFinish(true); setIsImageUploadFinish(true);
} }
imageFiles?.map(async (item: any, index: number) => { imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0"); await uploadResumableFile(index, String(id), item, "1", "0");
}); });
if (videoFiles?.length == 0) { if (videoFiles?.length === 0) {
setIsVideoUploadFinish(true); setIsVideoUploadFinish(true);
} }
videoFiles?.map(async (item: any, index: number) => { videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0"); await uploadResumableFile(index, String(id), item, "2", "0");
}); });
if (textFiles?.length == 0) { if (textFiles?.length === 0) {
setIsTextUploadFinish(true); setIsTextUploadFinish(true);
} }
textFiles?.map(async (item: any, index: number) => { textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0"); await uploadResumableFile(index, String(id), item, "3", "0");
}); });
if (audioFiles?.length == 0) { if (audioFiles?.length === 0) {
setIsAudioUploadFinish(true); setIsAudioUploadFinish(true);
} }
audioFiles?.map(async (item: any, index: number) => { audioFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "4", "0"); await uploadResumableFile(index, String(id), item, "4", "0");
}); });
// Optional: Use Swal for success feedback
// MySwal.fire({
// title: "Sukses",
// text: "Data berhasil disimpan.",
// icon: "success",
// confirmButtonColor: "#3085d6",
// confirmButtonText: "OK",
// }).then(() => {
// router.push("en/contributor/agenda-setting");
// });
}; };
const onSubmit = (data: any) => { const onSubmit = (data: any) => {
if ( if (
(wilayahPublish.polda && selectedPolda.length === 0) || (wilayahPublish.polda && selectedPolda.length === 0) ||
(wilayahPublish.satker && selectedSatker.length === 0) (wilayahPublish.satker && selectedSatker.length === 0) ||
(wilayahPublish.polres && selectedPolres.length === 0)
) { ) {
toast({ toast({
title: "Pilih ID untuk Polda/Satker", title: "Pilih ID untuk Polda/Satker",
@ -524,6 +545,60 @@ const EventModal = ({
const handleRemoveFile = (id: number) => {}; const handleRemoveFile = (id: number) => {};
async function doDelete(id: any) {
loading();
const resDelete = await deleteAgendaSettings(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
close();
successSubmitDelete("/in/contributor/agenda-setting");
window.location.reload();
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
function successSubmitDelete(redirect: string) {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
if (redirect === window.location.pathname) {
fetch(redirect, { method: "GET", cache: "reload" }).then(() => {
console.log("Data diperbarui.");
});
} else {
window.location.href = redirect;
}
});
}
const onReady = (ws: any) => {
setWavesurfer(ws);
setIsPlaying(false);
};
const onPlayPause = () => {
wavesurfer && wavesurfer.playPause();
};
return ( return (
<> <>
<DeleteConfirmationDialog <DeleteConfirmationDialog
@ -645,7 +720,7 @@ const EventModal = ({
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label htmlFor="wilayahPublish">Jenis Agenda</Label> <Label htmlFor="wilayahPublish">Jenis Agenda</Label>
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<div> <div>
<Checkbox <Checkbox
id="semua" id="semua"
@ -685,6 +760,25 @@ const EventModal = ({
/> />
)} )}
</div> </div>
<div>
<Checkbox
id="polres"
checked={wilayahPublish.polres}
onCheckedChange={() => toggleWilayah("polres")}
/>
<label htmlFor="polres" className="ml-2">
Polres
</label>
{wilayahPublish.polres && (
<UnitMapping
isDetail={false}
unit="Polres"
sendDataToParent={(data: any) =>
setSelectedPolres(data)
}
/>
)}
</div>
<div> <div>
<Checkbox <Checkbox
id="satker" id="satker"
@ -706,11 +800,11 @@ const EventModal = ({
</div> </div>
<div> <div>
<Checkbox <Checkbox
id="internasional" id="international"
checked={wilayahPublish.internasional} checked={wilayahPublish.international}
onCheckedChange={() => toggleWilayah("internasional")} onCheckedChange={() => toggleWilayah("international")}
/> />
<label htmlFor="internasional" className="ml-2"> <label htmlFor="international" className="ml-2">
Internasional Internasional
</label> </label>
</div> </div>
@ -743,33 +837,6 @@ const EventModal = ({
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label>Video</Label> <Label>Video</Label>
{videoUploadedFiles?.map((file: any, index: number) => (
<div
key={index}
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.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</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>
))}
<FileUploader <FileUploader
accept={{ accept={{
"mp4/*": [], "mp4/*": [],
@ -777,12 +844,16 @@ const EventModal = ({
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .mp4 atau .mov." label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setVideoFiles(files)}
/> />
</div> {videoUploadedFiles?.map((file: any, index: number) => (
<div> <div>
<Label>Foto</Label> <video
{imageUploadedFiles?.map((file: any, index: number) => ( className="object-fill h-full w-full rounded-md"
src={file.url}
controls
title={`Video ${file.id}`} // Mengganti alt dengan title
/>
<div <div
key={index} key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md" className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
@ -808,7 +879,11 @@ const EventModal = ({
<Icon icon="tabler:x" className=" h-5 w-5" /> <Icon icon="tabler:x" className=" h-5 w-5" />
</Button> </Button>
</div> </div>
</div>
))} ))}
</div>
<div>
<Label>Foto</Label>
<FileUploader <FileUploader
accept={{ accept={{
"image/*": [], "image/*": [],
@ -817,10 +892,15 @@ const EventModal = ({
label="Upload file dengan format .png, .jpg, atau .jpeg." label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setImageFiles(files)}
/> />
</div> {imageUploadedFiles?.map((file: any, index: number) => (
<div> <div>
<Label>Teks</Label> <Card className="mt-2">
{textUploadedFiles?.map((file: any, index: number) => ( <img
src={file.url}
alt="Thumbnail Gambar Utama"
className="w-full h-auto rounded-md"
/>
</Card>
<div <div
key={index} key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md" className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
@ -846,7 +926,12 @@ const EventModal = ({
<Icon icon="tabler:x" className=" h-5 w-5" /> <Icon icon="tabler:x" className=" h-5 w-5" />
</Button> </Button>
</div> </div>
</div>
))} ))}
</div>
<div>
<Label>Teks</Label>
<FileUploader <FileUploader
accept={{ accept={{
"pdf/*": [], "pdf/*": [],
@ -855,10 +940,15 @@ const EventModal = ({
label="Upload file dengan format .pdf." label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)} onDrop={(files) => setTextFiles(files)}
/> />
</div> {textUploadedFiles?.map((file: any, index: number) => (
<div> <div>
<Label>Voice Note</Label> <iframe
{audioUploadedFiles?.map((file: any, index: number) => ( className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
file.url
)}`}
title={file.fileName || "Document"}
/>
<div <div
key={index} key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md" className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
@ -884,7 +974,12 @@ const EventModal = ({
<Icon icon="tabler:x" className=" h-5 w-5" /> <Icon icon="tabler:x" className=" h-5 w-5" />
</Button> </Button>
</div> </div>
</div>
))} ))}
</div>
<div>
<Label>Voice Note</Label>
<AudioRecorder <AudioRecorder
onRecordingComplete={addAudioElement} onRecordingComplete={addAudioElement}
audioTrackConstraints={{ audioTrackConstraints={{
@ -904,6 +999,67 @@ const EventModal = ({
onDrop={(files) => setAudioFiles(files)} onDrop={(files) => setAudioFiles(files)}
className="mt-2" className="mt-2"
/> />
{audioUploadedFiles?.map((file: any, index: number) => (
<div>
<div key={file.id}>
<WavesurferPlayer
height={500}
waveColor="red"
url={file.url}
onReady={onReady}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
</div>
<Button
size="sm"
type="button"
onClick={onPlayPause}
disabled={isPlaying}
className={`flex items-center gap-2 ${
isPlaying
? "bg-gray-300 cursor-not-allowed"
: "bg-primary text-white"
} p-2 rounded`}
>
{isPlaying ? "Pause" : "Play"}
<Icon
icon={
isPlaying
? "carbon:pause-outline"
: "famicons:play-sharp"
}
className="h-5 w-5"
/>
</Button>
<div
key={index}
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.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
</div> </div>
{audioFile && ( {audioFile && (
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
@ -938,6 +1094,13 @@ const EventModal = ({
"Simpan Agenda Setting" "Simpan Agenda Setting"
)} )}
</Button> </Button>
<Button
className="flex-1 bg-red-500 text-white"
type="button"
onClick={() => handleDelete(event?.event?.id)}
>
Delete
</Button>
{event?.length > 1 && ( {event?.length > 1 && (
<Button <Button
type="button" type="button"

View File

@ -1,4 +1,5 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Form, Form,
@ -35,11 +36,11 @@ const FormSchema = z.object({
interface UnitType { interface UnitType {
id: number; id: number;
name: string; name: string;
subDestination: { id: number; name: string }[]; subDestination: { id: number; name: string }[] | null;
} }
export function UnitMapping(props: { export function UnitMapping(props: {
unit: string; unit: "Polda" | "Satker" | "Polres";
sendDataToParent: (data: string[]) => void; sendDataToParent: (data: string[]) => void;
isDetail: boolean; isDetail: boolean;
initData?: string[]; initData?: string[];
@ -49,7 +50,11 @@ export function UnitMapping(props: {
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>( const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
[] []
); );
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
[]
);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
defaultValues: { defaultValues: {
@ -61,6 +66,7 @@ export function UnitMapping(props: {
async function initState() { async function initState() {
const response = await getUserLevelForAssignments(); const response = await getUserLevelForAssignments();
setupUnit(response?.data?.data.list); setupUnit(response?.data?.data.list);
console.log("list", response?.data?.data.list);
} }
initState(); initState();
@ -74,12 +80,17 @@ export function UnitMapping(props: {
const isAllSatkerChecked = satkerList.every((item) => const isAllSatkerChecked = satkerList.every((item) =>
unitType?.includes(String(item.id)) unitType?.includes(String(item.id))
); );
const isAllPolresChecked = polresList.every((item) =>
unitType?.includes(String(item.id))
);
const setupUnit = (data: UnitType[]) => { const setupUnit = (data: UnitType[]) => {
const temp = data.filter((a) => a.name.includes("POLDA")); const temp = data.filter((a) => a.name.includes("POLDA"));
const temp2 = data.filter((a) => a.name.includes("SATKER")); const temp2 = data.filter((a) => a.name.includes("SATKER"));
const temp3 = temp.flatMap((item) => item.subDestination || []);
setUnitList(temp); setUnitList(temp);
setSatkerList(temp2[0].subDestination); setSatkerList(temp2[0]?.subDestination || []);
setPolresList(temp3);
}; };
useEffect(() => { useEffect(() => {
@ -96,23 +107,32 @@ export function UnitMapping(props: {
Pilih {unit} Pilih {unit}
</a> </a>
</DialogTrigger> </DialogTrigger>
<DialogContent size="md"> <DialogContent size="md" className="h-[500px] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>{unit}</DialogTitle> <DialogTitle>{unit}</DialogTitle>
</DialogHeader> </DialogHeader>
{unit === "Polda" ? (
<Form {...form}> <Form {...form}>
<form className="flex flex-col gap-2"> <form className="flex flex-col gap-2">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Checkbox <Checkbox
id={`all-${unit}`} id={`all-${unit}`}
checked={isAllUnitChecked} checked={
unit === "Polda"
? isAllUnitChecked
: unit === "Satker"
? isAllSatkerChecked
: isAllPolresChecked
}
disabled={isDetail} disabled={isDetail}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
if (checked) { if (checked) {
form.setValue( form.setValue(
"items", "items",
unitList.map((item) => String(item.id)) unit === "Polda"
? unitList.map((item) => String(item.id))
: unit === "Satker"
? satkerList.map((item) => String(item.id))
: polresList.map((item) => String(item.id))
); );
} else { } else {
form.setValue("items", []); form.setValue("items", []);
@ -128,9 +148,16 @@ export function UnitMapping(props: {
name="items" name="items"
render={() => ( render={() => (
<FormItem <FormItem
className={`grid grid-cols-${unit === "Polda" ? "2" : "3"}`} className={`grid grid-cols-${
unit === "Polda" ? "2" : unit === "Satker" ? "3" : "4"
}`}
> >
{unitList?.map((item: any) => ( {(unit === "Polda"
? unitList
: unit === "Satker"
? satkerList
: polresList
)?.map((item: any) => (
<FormField <FormField
key={item.id} key={item.id}
control={form.control} control={form.control}
@ -144,9 +171,7 @@ export function UnitMapping(props: {
<FormControl> <FormControl>
<Checkbox <Checkbox
disabled={isDetail} disabled={isDetail}
checked={field.value?.includes( checked={field.value?.includes(String(item.id))}
String(item.id)
)}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
return checked return checked
? field.onChange([ ? field.onChange([
@ -173,80 +198,6 @@ export function UnitMapping(props: {
/> />
</form> </form>
</Form> </Form>
) : (
<Form {...form}>
<form className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Checkbox
id={`all-${unit}`}
checked={isAllSatkerChecked}
disabled={isDetail}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"items",
satkerList.map((item) => String(item.id))
);
} else {
form.setValue("items", []);
}
}}
/>
<label htmlFor="all" className="text-sm text-black uppercase">
SEMUA {unit}
</label>
</div>
<FormField
control={form.control}
name="items"
render={() => (
<FormItem
className={`grid grid-cols-${unit === "Polda" ? "2" : "3"}`}
>
{satkerList?.map((item: any) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => {
return (
<FormItem
key={String(item.id)}
className="flex flex-row items-center space-x-3 space-y-0"
>
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(
String(item.id)
)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
String(item.id),
])
: field.onChange(
field.value?.filter(
(value) => value !== String(item.id)
)
);
}}
/>
</FormControl>
<p className="text-sm text-black">{item.name}</p>
</FormItem>
);
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
)}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@ -13,6 +13,10 @@ 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"; import { Link } from "@/components/navigation";
import withReactContent from "sweetalert2-react-content";
import { deleteMedia } from "@/service/content/content";
import { error } from "@/lib/swal";
import Swal from "sweetalert2";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -135,6 +139,51 @@ const columns: ColumnDef<any>[] = [
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const MySwal = withReactContent(Swal);
async function doDelete(id: any) {
// loading();
const data = {
id,
};
const response = await deleteMedia(data);
if (response?.error) {
error(response.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const handleDeleteMedia = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -159,7 +208,10 @@ const columns: ColumnDef<any>[] = [
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
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
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -13,6 +13,14 @@ 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"; import { Link } from "@/components/navigation";
import { useRouter } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";
import { deleteMedia } from "@/service/content/content";
import { error, loading } from "@/lib/swal";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
const MySwal = withReactContent(Swal);
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -131,6 +139,52 @@ const columns: ColumnDef<any>[] = [
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
async function doDelete(id: any) {
// loading();
const data = {
id,
};
const response = await deleteMedia(data);
if (response?.error) {
error(response.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const handleDeleteMedia = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -155,7 +209,10 @@ const columns: ColumnDef<any>[] = [
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
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
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -53,12 +53,18 @@ import { Badge } from "@/components/ui/badge";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination"; import TablePagination from "@/components/table/table-pagination";
import columns from "./columns"; import columns from "./columns";
import { listDataImage } from "@/service/content/content"; import { deleteMedia, listDataImage } from "@/service/content/content";
import { loading } from "@/config/swal";
import { toast } from "sonner";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { error } from "@/lib/swal";
const TableImage = () => { const TableImage = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const MySwal = withReactContent(Swal);
const [dataTable, setDataTable] = React.useState<any[]>([]); const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
@ -161,6 +167,37 @@ const TableImage = () => {
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
}; };
// async function doDelete(id: any) {
// loading();
// const data = {
// id,
// };
// const response = await deleteMedia(data);
// if (response?.error) {
// error(response.message);
// return false;
// }
// fetchData();
// toast.success("Konten berhasil dihapus");
// }
// const handleDeleteMedia = (id: any) => {
// MySwal.fire({
// title: "Apakah anda ingin menghapus konten?",
// showCancelButton: true,
// confirmButtonColor: "#dc3545",
// confirmButtonText: "Iya",
// cancelButtonText: "Tidak",
// }).then((result: any) => {
// if (result.isConfirmed) {
// doDelete(id);
// }
// });
// };
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">

View File

@ -13,6 +13,10 @@ 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"; import { Link } from "@/components/navigation";
import { error } from "@/lib/swal";
import { deleteMedia } from "@/service/content/content";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -136,6 +140,51 @@ const columns: ColumnDef<any>[] = [
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const MySwal = withReactContent(Swal);
async function doDelete(id: any) {
// loading();
const data = {
id,
};
const response = await deleteMedia(data);
if (response?.error) {
error(response.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const handleDeleteMedia = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -160,7 +209,10 @@ const columns: ColumnDef<any>[] = [
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
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
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -13,6 +13,10 @@ 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"; import { Link } from "@/components/navigation";
import { deleteMedia } from "@/service/content/content";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { error } from "@/lib/swal";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -136,6 +140,51 @@ const columns: ColumnDef<any>[] = [
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const MySwal = withReactContent(Swal);
async function doDelete(id: any) {
// loading();
const data = {
id,
};
const response = await deleteMedia(data);
if (response?.error) {
error(response.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const handleDeleteMedia = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -160,7 +209,10 @@ const columns: ColumnDef<any>[] = [
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem
onClick={() => handleDeleteMedia(row.original.id)}
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
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -13,6 +13,13 @@ 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"; import { Link } from "@/components/navigation";
import { useRouter } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";
import { deleteCategory } from "@/service/settings/settings";
import { deleteTask } from "@/service/task";
import { error, loading } from "@/lib/swal";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -115,6 +122,49 @@ const columns: ColumnDef<any>[] = [
header: "Actions", header: "Actions",
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
async function deleteProcess(id: any) {
loading();
const resDelete = await deleteTask(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const TaskDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteProcess(id);
}
});
};
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -139,7 +189,10 @@ const columns: ColumnDef<any>[] = [
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem
onClick={() => TaskDelete(row.original.id)}
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
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { import {
createMedia, createMedia,
deleteFile,
getTagsBySubCategoryId, getTagsBySubCategoryId,
listEnableCategory, listEnableCategory,
uploadThumbnail, uploadThumbnail,
@ -81,6 +82,11 @@ interface FileWithPreview extends File {
preview: string; preview: string;
} }
type Option = {
id: string;
name: string;
};
export default function FormAudioUpdate() { export default function FormAudioUpdate() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -111,10 +117,20 @@ export default function FormAudioUpdate() {
const [detailThumb, setDetailThumb] = useState<any>([]); const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null); const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [selectedOptions, setSelectedOptions] = useState<{ const [selectedOptions, setSelectedOptions] = useState<{
[fileId: number]: string; [fileId: number]: string;
}>({}); }>({});
const options: Option[] = [
{ id: "all", name: "SEMUA" },
{ id: "5", name: "UMUM" },
{ id: "6", name: "JOURNALIS" },
{ id: "7", name: "POLRI" },
{ id: "8", name: "KSP" },
];
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
mabes: false, mabes: false,
@ -139,21 +155,6 @@ export default function FormAudioUpdate() {
resolver: zodResolver(audioSchema), resolver: zodResolver(audioSchema),
}); });
// 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>) => { const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) { if (event.target.files) {
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
@ -166,12 +167,6 @@ export default function FormAudioUpdate() {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
}; };
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
getCategories(); getCategories();
@ -205,6 +200,29 @@ export default function FormAudioUpdate() {
} }
}; };
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
if (inputRef.current) {
inputRef.current.value = ""; // Kosongkan input
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
if (id) { if (id) {
@ -217,11 +235,13 @@ export default function FormAudioUpdate() {
setFiles(details.files); setFiles(details.files);
} }
if (details.publishedForObject) { if (details?.publishedFor) {
const publisherIds = details.publishedForObject.map( // Split string "7" to an array ["7"] if needed
(obj: any) => obj.id setPublishedFor(details.publishedFor.split(","));
); }
setSelectedPublishers(publisherIds);
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
} }
const matchingCategory = categories.find( const matchingCategory = categories.find(
@ -244,7 +264,25 @@ export default function FormAudioUpdate() {
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
const handleCheckboxChange = (id: string) => {
if (id === "all") {
// Select all options except "all"
const allOptions = options
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
publishedFor.length === allOptions.length ? [] : allOptions
);
} else {
// Toggle individual option
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
}
};
const save = async (data: AudioSchema) => { const save = async (data: AudioSchema) => {
const finalTags = tags.join(", ");
const requestData = { const requestData = {
...data, ...data,
id: detail?.id, id: detail?.id,
@ -256,9 +294,9 @@ export default function FormAudioUpdate() {
subCategoryId: selectedTarget, subCategoryId: selectedTarget,
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: "siap", tags: finalTags,
isYoutube: false, isYoutube: false,
isInternationalMedia: false, isInternationalMedia: false,
}; };
@ -286,12 +324,7 @@ export default function FormAudioUpdate() {
close(); close();
// showProgress(); // showProgress();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
await uploadResumableFile( await uploadResumableFile(index, String(id), item, "0");
index,
String(id),
item,
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
);
}); });
MySwal.fire({ MySwal.fire({
@ -430,22 +463,24 @@ export default function FormAudioUpdate() {
} }
}; };
const handleRemoveFile = (file: FileWithPreview) => { // const handleRemoveFile = (file: FileWithPreview) => {
const uploadedFiles = files; // const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name); // const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]); // setFiles([...filtered]);
}; // };
const fileList = files.map((file) => ( const fileList = files.map((file: any) => (
<div <div
key={file.name} key={file.id} // Gunakan ID file sebagai key
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md" className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
> >
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file)}</div> <div className="file-preview">{renderFilePreview(file)}</div>
<div> <div>
<div className=" text-sm text-card-foreground">{file.name}</div> <div className="text-sm text-card-foreground">
<div className=" text-xs font-light text-muted-foreground"> {file.fileName || file.name}
</div>
<div className="text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? ( {Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</> <>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : ( ) : (
@ -460,10 +495,10 @@ export default function FormAudioUpdate() {
size="icon" size="icon"
color="destructive" color="destructive"
variant="outline" variant="outline"
className=" border-none rounded-full" className="border-none rounded-full"
onClick={() => handleRemoveFile(file)} onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
> >
<Icon icon="tabler:x" className=" h-5 w-5" /> <Icon icon="tabler:x" className="h-5 w-5" />
</Button> </Button>
</div> </div>
)); ));
@ -500,6 +535,55 @@ export default function FormAudioUpdate() {
}); });
}; };
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? ( {detail !== undefined ? (
@ -606,12 +690,12 @@ export default function FormAudioUpdate() {
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
</div> </div>
<Button {/* <Button
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All Remove All
</Button> </Button> */}
</div> </div>
</Fragment> </Fragment>
) : null} ) : null}
@ -752,14 +836,34 @@ export default function FormAudioUpdate() {
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>Tag</Label>
<div className="flex flex-wrap gap-2"> <Input
{detail?.tags?.split(",").map((tag, index) => ( type="text"
<Badge id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index} key={index}
className="border rounded-md px-2 py-2" className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
> >
{tag.trim()} <input
</Badge> type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))} ))}
</div> </div>
</div> </div>
@ -767,38 +871,21 @@ export default function FormAudioUpdate() {
<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-6">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id={option.id}
checked={selectedPublishers.includes(5)} checked={
onChange={() => handleCheckboxChange(5)} option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/> />
<Label htmlFor="5">UMUM</Label> <Label htmlFor={option.id}>{option.name}</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>
</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

@ -31,6 +31,8 @@ import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { import {
createMedia, createMedia,
deleteFile,
deleteMedia,
getTagsBySubCategoryId, getTagsBySubCategoryId,
listEnableCategory, listEnableCategory,
uploadThumbnail, uploadThumbnail,
@ -42,7 +44,7 @@ import dynamic from "next/dynamic";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import Image from "next/image"; import Image from "next/image";
import { error } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
@ -65,7 +67,13 @@ type Detail = {
title: string; title: string;
description: string; description: string;
slug: string; slug: string;
categoryId: { category: {
id: string;
name: string;
};
publishedFor: string;
publishedForObject: {
id: number; id: number;
name: string; name: string;
}; };
@ -75,6 +83,11 @@ type Detail = {
tags: string; tags: string;
}; };
type Option = {
id: string;
name: string;
};
const CustomEditor = dynamic( const CustomEditor = dynamic(
() => { () => {
return import("@/components/editor/custom-editor"); return import("@/components/editor/custom-editor");
@ -122,7 +135,17 @@ export default function FormImageUpdate() {
[fileId: number]: string; [fileId: number]: string;
}>({}); }>({});
const [selectedTarget, setSelectedTarget] = useState(""); const options: Option[] = [
{ id: "all", name: "SEMUA" },
{ id: "5", name: "UMUM" },
{ id: "6", name: "JOURNALIS" },
{ id: "7", name: "POLRI" },
{ id: "8", name: "KSP" },
];
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
detail?.category.id
);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
mabes: false, mabes: false,
@ -158,10 +181,6 @@ export default function FormImageUpdate() {
// } // }
// }; // };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => { const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) { if (event.target.files) {
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
@ -174,11 +193,11 @@ export default function FormImageUpdate() {
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() {
@ -193,14 +212,24 @@ export default function FormImageUpdate() {
e.preventDefault(); e.preventDefault();
const newTag = e.currentTarget.value.trim(); const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) { if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Add new tag setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
if (inputRef.current) { if (inputRef.current) {
inputRef.current.value = ""; // Clear input field inputRef.current.value = ""; // Kosongkan input
} }
} }
} }
}; };
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
const getCategories = async () => { const getCategories = async () => {
try { try {
const category = await listEnableCategory(fileTypeId); const category = await listEnableCategory(fileTypeId);
@ -238,11 +267,13 @@ export default function FormImageUpdate() {
setFiles(details.files); setFiles(details.files);
} }
if (details.publishedForObject) { if (details?.publishedFor) {
const publisherIds = details.publishedForObject.map( // Split string "7" to an array ["7"] if needed
(obj: any) => obj.id setPublishedFor(details.publishedFor.split(","));
); }
setSelectedPublishers(publisherIds);
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
} }
const matchingCategory = categories.find( const matchingCategory = categories.find(
@ -259,7 +290,32 @@ export default function FormImageUpdate() {
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
// const handleCheckboxChange = (id: number) => {
// setSelectedPublishers((prev) =>
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
// );
// };
const handleCheckboxChange = (id: string) => {
if (id === "all") {
// Select all options except "all"
const allOptions = options
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
publishedFor.length === allOptions.length ? [] : allOptions
);
} else {
// Toggle individual option
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
}
};
const save = async (data: ImageSchema) => { const save = async (data: ImageSchema) => {
loading();
const finalTags = tags.join(", ");
const requestData = { const requestData = {
...data, ...data,
id: detail?.id, id: detail?.id,
@ -271,9 +327,9 @@ export default function FormImageUpdate() {
subCategoryId: selectedTarget, subCategoryId: selectedTarget,
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: "siap", tags: finalTags,
isYoutube: false, isYoutube: false,
isInternationalMedia: false, isInternationalMedia: false,
}; };
@ -451,16 +507,18 @@ export default function FormImageUpdate() {
setFiles([...filtered]); setFiles([...filtered]);
}; };
const fileList = files.map((file) => ( const fileList = files.map((file: any) => (
<div <div
key={file.name} key={file.id} // Gunakan ID file sebagai key
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md" className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
> >
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file)}</div> <div className="file-preview">{renderFilePreview(file)}</div>
<div> <div>
<div className=" text-sm text-card-foreground">{file.name}</div> <div className="text-sm text-card-foreground">
<div className=" text-xs font-light text-muted-foreground"> {file.fileName || file.name}
</div>
<div className="text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? ( {Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</> <>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : ( ) : (
@ -475,10 +533,10 @@ export default function FormImageUpdate() {
size="icon" size="icon"
color="destructive" color="destructive"
variant="outline" variant="outline"
className=" border-none rounded-full" className="border-none rounded-full"
onClick={() => handleRemoveFile(file)} onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
> >
<Icon icon="tabler:x" className=" h-5 w-5" /> <Icon icon="tabler:x" className="h-5 w-5" />
</Button> </Button>
</div> </div>
)); ));
@ -515,6 +573,55 @@ export default function FormImageUpdate() {
}); });
}; };
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? ( {detail !== undefined ? (
@ -549,14 +656,17 @@ export default function FormImageUpdate() {
<div className="py-3 w-full"> <div className="py-3 w-full">
<Label>Kategori</Label> <Label>Kategori</Label>
<Select <Select
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
onValueChange={(id) => { onValueChange={(id) => {
console.log("Selected Category:", id); console.log("Selected Category ID:", id);
setSelectedTarget(id); setSelectedTarget(id); // Perbarui ID kategori
}} }}
> >
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih">
{categories.find((cat) => cat.id === selectedTarget)
?.name || "Pilih"}
</SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{categories.map((category) => ( {categories.map((category) => (
@ -619,12 +729,12 @@ export default function FormImageUpdate() {
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
</div> </div>
<Button {/* <Button
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={() => handleDeleteFile(id)}
> >
Remove All Remove file
</Button> </Button> */}
</div> </div>
</Fragment> </Fragment>
) : null} ) : null}
@ -782,24 +892,30 @@ export default function FormImageUpdate() {
onKeyDown={handleAddTag} onKeyDown={handleAddTag}
ref={inputRef} ref={inputRef}
/> />
<div className="mt-3 "> <div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => ( {tags.map((tag, index) => (
<span <span
key={index} key={index}
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans" className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
> >
{tag}{" "} <input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button <button
value={tag}
type="button" type="button"
onClick={() => handleRemoveTag(index)} onClick={() => handleRemoveTag(index)}
className="remove-tag-button" className="remove-tag-button text-white"
> >
× ×
</button> </button>
</span> </span>
))} ))}
</div> </div>
<div className="flex flex-wrap gap-2"> {/* <div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => ( {detail?.tags?.split(",").map((tag, index) => (
<Badge <Badge
key={index} key={index}
@ -808,44 +924,27 @@ export default function FormImageUpdate() {
{tag.trim()} {tag.trim()}
</Badge> </Badge>
))} ))}
</div> </div> */}
</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-6">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id={option.id}
checked={selectedPublishers.includes(5)} checked={
onChange={() => handleCheckboxChange(5)} option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/> />
<Label htmlFor="5">UMUM</Label> <Label htmlFor={option.id}>{option.name}</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>
</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

@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import Image from "next/image"; import Image from "next/image";
import { error } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
polres: false, polres: false,
}); });
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
let fileTypeId = "3"; let fileTypeId = "3";
@ -164,10 +165,6 @@ export default function FormTeksUpdate() {
// } // }
// }; // };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => { const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) { if (event.target.files) {
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
setFiles(details.files); setFiles(details.files);
} }
if (details.publishedForObject) { if (details?.publishedFor) {
const publisherIds = details.publishedForObject.map( // Split string "7" to an array ["7"] if needed
(obj: any) => obj.id setPublishedFor(details.publishedFor.split(","));
); }
setSelectedPublishers(publisherIds);
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
} }
const matchingCategory = categories.find( const matchingCategory = categories.find(
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
const handleCheckboxChange = (id: string): void => { const handleCheckboxChange = (id: string) => {
if (id === "all") { if (id === "all") {
if (publishedFor.includes("all")) { // Select all options except "all"
// Uncheck all checkboxes const allOptions = options
setPublishedFor([]); .filter((opt) => opt.id !== "all")
} else { .map((opt) => opt.id);
// Select all checkboxes
setPublishedFor( setPublishedFor(
options publishedFor.length === allOptions.length ? [] : allOptions
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
); );
}
} else { } else {
const updatedPublishedFor = publishedFor.includes(id) // Toggle individual option
? publishedFor.filter((item) => item !== id) setPublishedFor((prev) =>
: [...publishedFor, id]; prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, 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) => { const save = async (data: TeksSchema) => {
loading();
const finalTags = tags.join(", ");
const requestData = { const requestData = {
...data, ...data,
id: detail?.id, id: detail?.id,
@ -293,7 +284,7 @@ export default function FormTeksUpdate() {
statusId: "1", statusId: "1",
publishedFor: publishedFor.join(","), publishedFor: publishedFor.join(","),
creatorName: data.creatorName, creatorName: data.creatorName,
tags: "siap", tags: finalTags,
isYoutube: false, isYoutube: false,
isInternationalMedia: false, isInternationalMedia: false,
}; };
@ -535,6 +526,29 @@ export default function FormTeksUpdate() {
}); });
}; };
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
if (inputRef.current) {
inputRef.current.value = ""; // Kosongkan input
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? ( {detail !== undefined ? (
@ -785,7 +799,7 @@ export default function FormTeksUpdate() {
)} )}
</div> </div>
</div> </div>
<div className="mt-3 px-3"> {/* <div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label> <Label>Pratinjau Gambar Utama</Label>
<Card className="mt-2"> <Card className="mt-2">
<img <img
@ -794,18 +808,38 @@ export default function FormTeksUpdate() {
className="w-full h-auto rounded" className="w-full h-auto rounded"
/> />
</Card> </Card>
</div> </div> */}
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>Tag</Label>
<div className="flex flex-wrap gap-2"> <Input
{detail?.tags?.split(",").map((tag, index) => ( type="text"
<Badge id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index} key={index}
className="border rounded-md px-2 py-2" className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
> >
{tag.trim()} <input
</Badge> type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))} ))}
</div> </div>
</div> </div>
@ -817,7 +851,6 @@ export default function FormTeksUpdate() {
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
id={option.id} id={option.id}
value={detail?.publishedForObject.name}
checked={ checked={
option.id === "all" option.id === "all"
? publishedFor.length === ? publishedFor.length ===

View File

@ -531,7 +531,7 @@ export default function FormVideo() {
const csrfToken = resCsrf?.data?.token; const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken); console.log("CSRF TOKEN : ", csrfToken);
const headers = { const headers = {
"X-XSRF-TOKEN": csrfToken "X-XSRF-TOKEN": csrfToken,
}; };
const upload = new Upload(file, { const upload = new Upload(file, {
@ -544,11 +544,11 @@ export default function FormVideo() {
filename: file.name, filename: file.name,
filetype: file.type, filetype: file.type,
duration, duration,
isWatermark: "false", // hardcode isWatermark: "true", // hardcode
}, },
onBeforeRequest: function (req) { onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject() var xhr = req.getUnderlyingObject();
xhr.withCredentials = true xhr.withCredentials = true;
}, },
onError: async (e: any) => { onError: async (e: any) => {
console.log("Error upload :", e); console.log("Error upload :", e);

View File

@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { import {
createMedia, createMedia,
deleteFile,
getTagsBySubCategoryId, getTagsBySubCategoryId,
listEnableCategory, listEnableCategory,
uploadThumbnail, uploadThumbnail,
@ -53,7 +54,7 @@ import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { error } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
const videoSchema = z.object({ const videoSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -88,6 +89,11 @@ interface FileWithPreview extends File {
preview: string; preview: string;
} }
type Option = {
id: string;
name: string;
};
export default function FormVideoUpdate() { export default function FormVideoUpdate() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -126,11 +132,20 @@ export default function FormVideoUpdate() {
polda: false, polda: false,
polres: false, polres: false,
}); });
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [selectedOptions, setSelectedOptions] = useState<{ const [selectedOptions, setSelectedOptions] = useState<{
[fileId: number]: string; [fileId: number]: string;
}>({}); }>({});
const options: Option[] = [
{ id: "all", name: "SEMUA" },
{ id: "5", name: "UMUM" },
{ id: "6", name: "JOURNALIS" },
{ id: "7", name: "POLRI" },
{ id: "8", name: "KSP" },
];
let fileTypeId = "2"; let fileTypeId = "2";
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
@ -159,10 +174,6 @@ export default function FormVideoUpdate() {
// } // }
// }; // };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => { const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) { if (event.target.files) {
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
@ -175,12 +186,6 @@ export default function FormVideoUpdate() {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
}; };
const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
getCategories(); getCategories();
@ -189,6 +194,29 @@ export default function FormVideoUpdate() {
initState(); initState();
}, []); }, []);
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
if (inputRef.current) {
inputRef.current.value = ""; // Kosongkan input
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
const getCategories = async () => { const getCategories = async () => {
try { try {
const category = await listEnableCategory(fileTypeId); const category = await listEnableCategory(fileTypeId);
@ -226,11 +254,13 @@ export default function FormVideoUpdate() {
setFiles(details.files); setFiles(details.files);
} }
if (details.publishedForObject) { if (details?.publishedFor) {
const publisherIds = details.publishedForObject.map( // Split string "7" to an array ["7"] if needed
(obj: any) => obj.id setPublishedFor(details.publishedFor.split(","));
); }
setSelectedPublishers(publisherIds);
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
} }
const matchingCategory = categories.find( const matchingCategory = categories.find(
@ -253,7 +283,26 @@ export default function FormVideoUpdate() {
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
const handleCheckboxChange = (id: string) => {
if (id === "all") {
// Select all options except "all"
const allOptions = options
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
publishedFor.length === allOptions.length ? [] : allOptions
);
} else {
// Toggle individual option
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
}
};
const save = async (data: VideoSchema) => { const save = async (data: VideoSchema) => {
loading();
const finalTags = tags.join(", ");
const requestData = { const requestData = {
...data, ...data,
id: detail?.id, id: detail?.id,
@ -265,9 +314,9 @@ export default function FormVideoUpdate() {
subCategoryId: selectedTarget, subCategoryId: selectedTarget,
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: "siap", tags: finalTags,
isYoutube: false, isYoutube: false,
isInternationalMedia: false, isInternationalMedia: false,
}; };
@ -295,32 +344,33 @@ export default function FormVideoUpdate() {
close(); close();
// showProgress(); // showProgress();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
await uploadResumableFile( await uploadResumableFile(index, String(id), item, "0");
index,
String(id),
item,
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
);
}); });
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/content/image"); // router.push("/en/contributor/content/video");
}); // });
};
const onSubmit = (data: VideoSchema) => {
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Simpan Data",
text: "Data berhasil disimpan.", text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "success", icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "Simpan",
}).then(() => { }).then((result) => {
router.push("/en/contributor/content/video"); if (result.isConfirmed) {
save(data);
}
}); });
}; };
@ -385,22 +435,6 @@ export default function FormVideoUpdate() {
upload.start(); upload.start();
} }
const onSubmit = (data: VideoSchema) => {
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);
}
});
};
const successSubmit = (redirect: string) => { const successSubmit = (redirect: string) => {
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
@ -454,16 +488,18 @@ export default function FormVideoUpdate() {
setFiles([...filtered]); setFiles([...filtered]);
}; };
const fileList = files.map((file) => ( const fileList = files.map((file: any) => (
<div <div
key={file.name} key={file.id} // Gunakan ID file sebagai key
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md" className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
> >
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file)}</div> <div className="file-preview">{renderFilePreview(file)}</div>
<div> <div>
<div className=" text-sm text-card-foreground">{file.name}</div> <div className="text-sm text-card-foreground">
<div className=" text-xs font-light text-muted-foreground"> {file.fileName || file.name}
</div>
<div className="text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? ( {Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</> <>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : ( ) : (
@ -478,10 +514,10 @@ export default function FormVideoUpdate() {
size="icon" size="icon"
color="destructive" color="destructive"
variant="outline" variant="outline"
className=" border-none rounded-full" className="border-none rounded-full"
onClick={() => handleRemoveFile(file)} onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
> >
<Icon icon="tabler:x" className=" h-5 w-5" /> <Icon icon="tabler:x" className="h-5 w-5" />
</Button> </Button>
</div> </div>
)); ));
@ -518,6 +554,55 @@ export default function FormVideoUpdate() {
}); });
}; };
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? ( {detail !== undefined ? (
@ -626,12 +711,12 @@ export default function FormVideoUpdate() {
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
</div> </div>
<Button {/* <Button
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All Remove All
</Button> </Button> */}
</div> </div>
</Fragment> </Fragment>
) : null} ) : null}
@ -782,14 +867,34 @@ export default function FormVideoUpdate() {
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>Tag</Label>
<div className="flex flex-wrap gap-2"> <Input
{detail?.tags?.split(",").map((tag, index) => ( type="text"
<Badge id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index} key={index}
className="border rounded-md px-2 py-2" className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
> >
{tag.trim()} <input
</Badge> type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))} ))}
</div> </div>
</div> </div>
@ -797,38 +902,21 @@ export default function FormVideoUpdate() {
<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-6">
<Label>Target Publish</Label> <Label>Target Publish</Label>
<div className="flex gap-2 items-center"> {options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id={option.id}
checked={selectedPublishers.includes(5)} checked={
onChange={() => handleCheckboxChange(5)} option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/> />
<Label htmlFor="5">UMUM</Label> <Label htmlFor={option.id}>{option.name}</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>
</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

@ -50,6 +50,10 @@ import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { successCallback } from "@/config/swal"; import { successCallback } from "@/config/swal";
import FileUploader from "../shared/file-uploader"; import FileUploader from "../shared/file-uploader";
import { AudioRecorder } from "react-audio-voice-recorder"; import { AudioRecorder } from "react-audio-voice-recorder";
import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js";
import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js";
const taskSchema = z.object({ const taskSchema = z.object({
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -162,6 +166,11 @@ interface UploadResult {
creatorName: string; creatorName: string;
} }
interface FileUploaded {
id: number;
url: string;
}
export default function FormTaskDetail() { export default function FormTaskDetail() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -191,6 +200,7 @@ export default function FormTaskDetail() {
const [type, setType] = useState<string>("1"); const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all"); const [selectedTarget, setSelectedTarget] = useState("all");
const [detail, setDetail] = useState<taskDetail>(); const [detail, setDetail] = useState<taskDetail>();
const [urlInputs, setUrlInputs] = useState<string[]>([]);
const [refresh] = useState(false); const [refresh] = useState(false);
const [listDest, setListDest] = useState([]); // Data Polda dan Polres const [listDest, setListDest] = useState([]); // Data Polda dan Polres
const [checkedLevels, setCheckedLevels] = useState(new Set()); const [checkedLevels, setCheckedLevels] = useState(new Set());
@ -224,6 +234,22 @@ export default function FormTaskDetail() {
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false);
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -283,32 +309,60 @@ export default function FormTaskDetail() {
setUploadResults(details.uploadResults || []); setUploadResults(details.uploadResults || []);
} }
if (details?.urls) {
// Extract attachmentUrl from each object in urls
const urls = details.urls.map(
(urlObj: any) => urlObj.attachmentUrl || ""
);
setUrlInputs(urls); // Save the URLs to state
}
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels = new Set(
details.assignedToLevel.split(",").map(Number) details.assignedToLevel.split(",").map(Number)
); );
setCheckedLevels(levels); setCheckedLevels(levels);
} }
const attachment = details?.files;
setImageUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 1)
);
setVideoUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 2)
);
setTextUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 3)
);
setAudioUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 4)
);
} }
} }
initState(); initState();
}, [id, refresh]); }, [id, refresh]);
const handleUrlChange = (index: number, newUrl: string) => {
setUrlInputs((prev: any) =>
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
);
};
useEffect(() => { useEffect(() => {
if (detail?.broadcastType) { if (detail?.broadcastType) {
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API setBroadcastType(detail.broadcastType);
} }
}, [detail?.broadcastType]); }, [detail?.broadcastType]);
useEffect(() => { useEffect(() => {
if (detail?.fileTypeOutput) { if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number));
setTaskOutput({ setTaskOutput({
all: outputSet.has(0), all: outputSet.has(0),
video: outputSet.has(2), video: outputSet.has(2),
audio: outputSet.has(4), audio: outputSet.has(4),
image: outputSet.has(1), image: outputSet.has(1),
text: outputSet.has(3), text: outputSet.has(5),
}); });
} }
}, [detail?.fileTypeOutput]); }, [detail?.fileTypeOutput]);
@ -327,71 +381,6 @@ export default function FormTaskDetail() {
} }
}, [detail?.fileTypeOutput]); }, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "3",
image: "4",
text: "5",
};
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(",");
const requestData = {
...data,
// assignmentType,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
narration: data.naration,
platformType: "",
title: data.title,
};
const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
});
};
const onSubmit = (data: TaskSchema) => {
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);
}
});
};
const successConfirm = () => { const successConfirm = () => {
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
@ -445,21 +434,6 @@ export default function FormTaskDetail() {
setMessage(e.target.value); setMessage(e.target.value);
}; };
// const handleSubmitResponse = (): void => {
// if (response.trim()) {
// const newResponse: Response = {
// id: Date.now(), // Unique ID for each response
// name: "Mabes Polri - Approver",
// content: response,
// timestamp: new Date().toLocaleString("id-ID"), // Format timestamp for Indonesia locale
// };
// setResponses([newResponse, ...responses]); // Add new response to the top
// setResponse(""); // Reset textarea
// setShowInput(false); // Hide input
// }
// };
const postData = () => { const postData = () => {
sendSuggestionParent(); sendSuggestionParent();
}; };
@ -527,29 +501,6 @@ export default function FormTaskDetail() {
console.log(dataId); console.log(dataId);
}; };
// async function sendSuggestionChild(parentId: any) {
// const msg = document.querySelectorAll(`#input-comment-${parentId}`)[0]
// .value;
// if (msg?.length > 1) {
// loading();
// const data = {
// assignmentId: id,
// message: msg,
// parentId,
// };
// console.log(data);
// const response = await createAssignmentResponse(data);
// console.log(response);
// const responseGet = await getAssignmentResponseList(id);
// console.log(responseGet?.data?.data);
// setListData(responseGet?.data?.data);
// // $(":input").val("");
// close();
// }
// }
async function getDataAcceptance() { async function getDataAcceptance() {
const response = await getAcceptanceAssignmentStatus(id); const response = await getAcceptanceAssignmentStatus(id);
setStatusAcceptance(response?.data?.data?.isAccept); setStatusAcceptance(response?.data?.data?.isAccept);
@ -752,6 +703,29 @@ export default function FormTaskDetail() {
setTimer(120); // Reset the timer to 2 minutes for the next recording setTimer(120); // Reset the timer to 2 minutes for the next recording
}; };
const renderFilePreview = (url: string) => {
return (
<Image
width={48}
height={48}
alt={"file preview"}
src={url}
className=" rounded border p-0.5"
/>
);
};
const onReady = (ws: any) => {
setWavesurfer(ws);
setIsPlaying(false);
};
const onPlayPause = () => {
wavesurfer && wavesurfer.playPause();
};
const handleRemoveFile = (id: number) => {};
return ( return (
<Card> <Card>
{detail !== undefined ? ( {detail !== undefined ? (
@ -802,7 +776,7 @@ export default function FormTaskDetail() {
</div> </div>
</div> </div>
<form onSubmit={handleSubmit(onSubmit)}> <form>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kode Unik</Label> <Label>Kode Unik</Label>
@ -1033,27 +1007,7 @@ export default function FormTaskDetail() {
))} ))}
</div> </div>
</div> </div>
{/* <div className="mt-6">
<Label>Broadcast </Label>
<RadioGroup
value={broadcastType}
onValueChange={(value) => setBroadcastType(value)}
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="all" id="all" />
<Label htmlFor="all">Semua</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="email" id="email" />
<Label htmlFor="email">Email Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="whatsapp" id="whatsapp" />
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
</div>
</RadioGroup>
</div> */}
<div className="mt-6"> <div className="mt-6">
<Label>Narasi Penugasan</Label> <Label>Narasi Penugasan</Label>
<Controller <Controller
@ -1063,51 +1017,211 @@ export default function FormTaskDetail() {
<ViewEditor initialData={detail?.narration} /> <ViewEditor initialData={detail?.narration} />
)} )}
/> />
{errors.naration?.message && ( {/* {errors.naration?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">
{errors.naration.message} {errors.naration.message}
</p> </p>
)} )} */}
</div> </div>
<div className="space-y-1.5 mt-5"> <div className="space-y-1.5 mt-5">
<Label htmlFor="attachments">Lampiran</Label> <Label htmlFor="attachment">Lampiran</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label>Video</Label> {videoUploadedFiles?.length > 0 && <Label>Video</Label>}
<FileUploader {videoUploadedFiles?.map((file: any, index: number) => (
accept={{ <div>
"mp4/*": [], <video
"mov/*": [], className="object-fill h-full w-full rounded-md"
}} src={file.url}
maxSize={100} controls
label="Upload file dengan format .mp4 atau .mov." title={`Video ${file.id}`} // Mengganti alt dengan title
onDrop={(files) => setImageFiles(files)}
/> />
<div
key={index}
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.url)}
</div> </div>
<div> <div>
<Label>Foto</Label> <div className=" text-sm text-card-foreground">
<FileUploader {file.fileName}
</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>
</div>
))}
</div>
<div>
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
{imageUploadedFiles?.map((file: any, index: number) => (
<div>
<Card className="mt-2">
<img
src={file.url}
alt="Thumbnail Gambar Utama"
className="w-full h-auto rounded-md"
/>
</Card>
<div
key={index}
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.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
{/* <FileUploader
accept={{ accept={{
"image/*": [], "image/*": [],
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg." label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setImageFiles(files)}
/> /> */}
</div> </div>
<div> <div>
<Label>Teks</Label> {textUploadedFiles?.length > 0 && <Label>Teks</Label>}
<FileUploader {textUploadedFiles?.map((file: any, index: number) => (
<div>
<iframe
className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
file.url
)}`}
title={file.fileName || "Document"}
/>
<div
key={index}
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.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
{/* <FileUploader
accept={{ accept={{
"pdf/*": [], "pdf/*": [],
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .pdf." label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)} onDrop={(files) => setTextFiles(files)}
/> /> */}
</div> </div>
<div> <div>
<Label>Voice Note</Label> {audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
{audioUploadedFiles?.map((file: any, index: number) => (
<div>
<div key={file.id}>
<WavesurferPlayer
height={500}
waveColor="red"
url={file.url}
onReady={onReady}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
</div>
<Button
size="sm"
type="button"
onClick={onPlayPause}
disabled={isPlaying}
className={`flex items-center gap-2 ${
isPlaying
? "bg-gray-300 cursor-not-allowed"
: "bg-primary text-white"
} p-2 rounded`}
>
{isPlaying ? "Pause" : "Play"}
<Icon
icon={
isPlaying
? "carbon:pause-outline"
: "famicons:play-sharp"
}
className="h-5 w-5"
/>
</Button>
<div
key={index}
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.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
{audioUploadedFiles?.length > 0 && (
<AudioRecorder <AudioRecorder
onRecordingComplete={addAudioElement} onRecordingComplete={addAudioElement}
audioTrackConstraints={{ audioTrackConstraints={{
@ -1117,7 +1231,8 @@ export default function FormTaskDetail() {
downloadOnSavePress={true} downloadOnSavePress={true}
downloadFileExtension="webm" downloadFileExtension="webm"
/> />
<FileUploader )}
{/* <FileUploader
accept={{ accept={{
"mp3/*": [], "mp3/*": [],
"wav/*": [], "wav/*": [],
@ -1126,7 +1241,7 @@ export default function FormTaskDetail() {
label="Upload file dengan format .mp3 atau .wav." label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) => setAudioFiles(files)} onDrop={(files) => setAudioFiles(files)}
className="mt-2" 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">
@ -1144,14 +1259,20 @@ export default function FormTaskDetail() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4"> <div className="mt-4">
<Label htmlFor="voiceNoteLink">Link Berita</Label> {urlInputs.map((url: any, index: any) => (
<Input <div className="mt-4 flex flex-col" key={index}>
id="voiceNoteLink" <Label htmlFor={`url-${index}`}>Link Berita</Label>
type="url" <Button
placeholder="Masukkan link voice note" size="sm"
value={voiceNoteLink} id={`url-${index}`}
onChange={(e) => setVoiceNoteLink(e.target.value)} className="bg-slate-400 text-white py-2 px-4 rounded mt-2 hover:bg-blue-600 text-start"
/> onClick={() => window.open(url, "_blank")}
disabled={!url} // Disable button if URL is empty
>
{url}
</Button>
</div>
))}
</div> </div>
</div> </div>
</div> </div>
@ -1172,8 +1293,7 @@ export default function FormTaskDetail() {
</Button> </Button>
) : ( ) : (
"" ""
) )}
}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<div className=""> <div className="">
@ -1189,7 +1309,8 @@ export default function FormTaskDetail() {
<Button <Button
className="btn btn-primary ml-3 mr-3" className="btn btn-primary ml-3 mr-3"
style={ style={
statusAcceptance || detail?.createdBy?.id !== Number(userId) statusAcceptance ||
detail?.createdBy?.id !== Number(userId)
? { ? {
display: "none", display: "none",
} }
@ -1211,6 +1332,7 @@ export default function FormTaskDetail() {
// } // }
> >
<Button <Button
type="button"
color="primary" color="primary"
variant={"default"} variant={"default"}
onClick={() => setIsTableResult(!isTableResult)} onClick={() => setIsTableResult(!isTableResult)}
@ -1402,10 +1524,14 @@ export default function FormTaskDetail() {
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<div <div
className="flex items-center mt-1 text-red-500 cursor-pointer" className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(child2.id)} onClick={() =>
deleteData(child2.id)
}
> >
<TrashIcon className="w-4 h-4" /> <TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span> <span className="ml-1">
Delete
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -35,6 +35,12 @@ import {
import { ChevronDown, ChevronUp } from "lucide-react"; import { ChevronDown, ChevronUp } from "lucide-react";
import FileUploader from "../shared/file-uploader"; import FileUploader from "../shared/file-uploader";
import { AudioRecorder } from "react-audio-voice-recorder"; import { AudioRecorder } from "react-audio-voice-recorder";
import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js";
import WavesurferPlayer from "@wavesurfer/react";
import { getCsrfToken } from "@/service/auth";
import { Upload } from "tus-js-client";
import { error, loading } from "@/lib/swal";
const taskSchema = z.object({ const taskSchema = z.object({
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), // uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -62,6 +68,7 @@ export type taskDetail = {
taskType: string; taskType: string;
broadcastType: string; broadcastType: string;
narration: string; narration: string;
attachmentUrl: string;
is_active: string; is_active: string;
}; };
@ -69,6 +76,16 @@ interface FileWithPreview extends File {
preview: string; preview: string;
} }
interface FileUploaded {
id: number;
url: string;
}
type Url = {
id: number;
attachmentUrl: string;
};
export default function FormTaskEdit() { export default function FormTaskEdit() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -101,6 +118,7 @@ export default function FormTaskEdit() {
const [type, setType] = useState<string>("1"); const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("3,4"); const [selectedTarget, setSelectedTarget] = useState("3,4");
const [detail, setDetail] = useState<taskDetail>(); const [detail, setDetail] = useState<taskDetail>();
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
const [refresh] = useState(false); const [refresh] = useState(false);
const [listDest, setListDest] = useState([]); // Data Polda dan Polres const [listDest, setListDest] = useState([]); // Data Polda dan Polres
const [checkedLevels, setCheckedLevels] = useState(new Set()); const [checkedLevels, setCheckedLevels] = useState(new Set());
@ -110,6 +128,19 @@ export default function FormTaskEdit() {
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -118,6 +149,8 @@ export default function FormTaskEdit() {
polres: false, polres: false,
}); });
const [links, setLinks] = useState<string[]>([""]);
const { const {
control, control,
handleSubmit, handleSubmit,
@ -165,6 +198,11 @@ export default function FormTaskEdit() {
setDetail(details); setDetail(details);
if (details?.urls) {
// Save the URLs as objects
setUrlInputs(details.urls);
}
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels = new Set(
details.assignedToLevel.split(",").map(Number) details.assignedToLevel.split(",").map(Number)
@ -172,12 +210,32 @@ export default function FormTaskEdit() {
setCheckedLevels(levels); setCheckedLevels(levels);
} }
const attachment = details?.files;
setImageUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 1)
);
setVideoUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 2)
);
setTextUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 3)
);
setAudioUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 4)
);
// Add more state setting here based on other fields like broadcastType, taskOutput, etc. // Add more state setting here based on other fields like broadcastType, taskOutput, etc.
} }
} }
initState(); initState();
}, [id, refresh]); }, [id, refresh]);
const handleUrlChange = (index: number, newUrl: string) => {
setUrlInputs((prev: any) =>
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
);
};
useEffect(() => { useEffect(() => {
if (detail?.broadcastType) { if (detail?.broadcastType) {
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
@ -192,7 +250,7 @@ export default function FormTaskEdit() {
video: outputSet.has(2), video: outputSet.has(2),
audio: outputSet.has(4), audio: outputSet.has(4),
image: outputSet.has(1), image: outputSet.has(1),
text: outputSet.has(3), text: outputSet.has(5),
}); });
} }
}, [detail?.fileTypeOutput]); }, [detail?.fileTypeOutput]);
@ -265,6 +323,7 @@ export default function FormTaskEdit() {
taskType: string; taskType: string;
assignedToRole: string; assignedToRole: string;
broadcastType: string; broadcastType: string;
attachmentUrl: string[];
} = { } = {
...data, ...data,
// assignmentType, // assignmentType,
@ -281,22 +340,52 @@ export default function FormTaskEdit() {
narration: data.naration, narration: data.naration,
platformType: "", platformType: "",
title: data.title, title: data.title,
attachmentUrl: links,
}; };
const response = await createTask(requestData); const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
console.log("response", response); console.log("response", response);
const id = response?.data?.data.id;
MySwal.fire({ loading();
title: "Sukses", if (imageFiles?.length == 0) {
text: "Data berhasil disimpan.", setIsImageUploadFinish(true);
icon: "success", }
confirmButtonColor: "#3085d6", imageFiles?.map(async (item: any, index: number) => {
confirmButtonText: "OK", await uploadResumableFile(index, String(id), item, "1", "0");
}).then(() => {
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) => {
@ -322,24 +411,6 @@ export default function FormTaskEdit() {
})); }));
}; };
const addAudioElement = (blob: Blob) => {
const url = URL.createObjectURL(blob);
const audio = document.createElement("audio");
audio.src = url;
audio.controls = true;
document.body.appendChild(audio);
// Convert Blob to File
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
setAudioFile(file);
};
const handleDeleteAudio = () => {
// Remove the audio file by setting state to null
setAudioFile(null);
const audioElements = document.querySelectorAll("audio");
audioElements.forEach((audio) => audio.remove());
};
const onRecordingStart = () => { const onRecordingStart = () => {
setIsRecording(true); setIsRecording(true);
@ -365,6 +436,164 @@ export default function FormTaskEdit() {
setTimer(120); // Reset the timer to 2 minutes for the next recording setTimer(120); // Reset the timer to 2 minutes for the next recording
}; };
const addAudioElement = (blob: Blob) => {
const url = URL.createObjectURL(blob);
const audio = document.createElement("audio");
audio.src = url;
audio.controls = true;
document.body.appendChild(audio);
// Convert Blob to File
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
setAudioFile(file);
};
const handleDeleteAudio = () => {
// Remove the audio file by setting state to null
setAudioFile(null);
const audioElements = document.querySelectorAll("audio");
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 resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken);
const headers = {
"X-XSRF-TOKEN": csrfToken,
};
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
assignmentId: id,
filename: file.name,
contentType: file.type,
fileTypeId: fileTypeId,
duration,
},
onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
},
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/task");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
const handleAddRow = () => {
setLinks([...links, ""]);
};
// Remove a specific link row
const handleRemoveRow = (index: number) => {
const updatedLinks = links.filter((_: any, i: any) => i !== index);
setLinks(updatedLinks);
};
const renderFilePreview = (url: string) => {
return (
<Image
width={48}
height={48}
alt={"file preview"}
src={url}
className=" rounded border p-0.5"
/>
);
};
const handleLinkChange = (index: number, value: string) => {
const updatedUrls = [...urlInputs];
updatedUrls[index].attachmentUrl = value;
setUrlInputs(updatedUrls);
};
const handleAddLink = () => {
setUrlInputs([
...urlInputs,
{ id: Date.now(), attachmentUrl: "" }, // Add a new empty object
]);
};
const handleRemoveFile = (id: number) => {};
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
@ -372,23 +601,6 @@ export default function FormTaskEdit() {
{detail !== undefined ? ( {detail !== undefined ? (
<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">
<Label>Kode Unik</Label>
<Controller
control={control}
name="uniqueCode"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.uniqueCode}
onChange={field.onChange}
placeholder="Enter uniqueCode"
readOnly
/>
)}
/>
</div> */}
<div className="space-y-2 mt-6"> <div className="space-y-2 mt-6">
<Label>Judul</Label> <Label>Judul</Label>
<Controller <Controller
@ -530,7 +742,7 @@ export default function FormTaskEdit() {
<div className="mt-6"> <div className="mt-6">
<Label>Tipe Penugasan</Label> <Label>Tipe Penugasan</Label>
<RadioGroup <RadioGroup
value={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setMainType(value)} onValueChange={(value) => setMainType(value)}
// value={String(mainType)} // value={String(mainType)}
// onValueChange={(value) => setMainType(Number(value))} // onValueChange={(value) => setMainType(Number(value))}
@ -545,7 +757,7 @@ export default function FormTaskEdit() {
<div className="mt-6"> <div className="mt-6">
<Label>Jenis Tugas </Label> <Label>Jenis Tugas </Label>
<RadioGroup <RadioGroup
value={detail.taskType.toString()} defaultValue={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3" className="flex flex-wrap gap-3"
> >
@ -559,7 +771,7 @@ export default function FormTaskEdit() {
<div className="mt-6"> <div className="mt-6">
<Label>Jenis Penugasan</Label> <Label>Jenis Penugasan</Label>
<RadioGroup <RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3" className="flex flex-wrap gap-3"
> >
@ -620,7 +832,7 @@ export default function FormTaskEdit() {
<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> {videoUploadedFiles?.length > 0 && <Label>Video</Label>}
<FileUploader <FileUploader
accept={{ accept={{
"mp4/*": [], "mp4/*": [],
@ -630,9 +842,38 @@ export default function FormTaskEdit() {
label="Upload file dengan format .mp4 atau .mov." label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setImageFiles(files)}
/> />
{videoUploadedFiles?.map((file: any, index: number) => (
<div>
<div
key={index}
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.url)}
</div> </div>
<div> <div>
<Label>Foto</Label> <div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
</div>
<div>
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
<FileUploader <FileUploader
accept={{ accept={{
"image/*": [], "image/*": [],
@ -641,9 +882,38 @@ export default function FormTaskEdit() {
label="Upload file dengan format .png, .jpg, atau .jpeg." label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setImageFiles(files)}
/> />
{imageUploadedFiles?.map((file: any, index: number) => (
<div>
<div
key={index}
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.url)}
</div> </div>
<div> <div>
<Label>Teks</Label> <div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
</div>
<div>
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
<FileUploader <FileUploader
accept={{ accept={{
"pdf/*": [], "pdf/*": [],
@ -652,9 +922,38 @@ export default function FormTaskEdit() {
label="Upload file dengan format .pdf." label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)} onDrop={(files) => setTextFiles(files)}
/> />
{textUploadedFiles?.map((file: any, index: number) => (
<div>
<div
key={index}
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.url)}
</div> </div>
<div> <div>
<Label>Voice Note</Label> <div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
</div>
<div>
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
<AudioRecorder <AudioRecorder
onRecordingComplete={addAudioElement} onRecordingComplete={addAudioElement}
audioTrackConstraints={{ audioTrackConstraints={{
@ -674,6 +973,35 @@ export default function FormTaskEdit() {
onDrop={(files) => setAudioFiles(files)} onDrop={(files) => setAudioFiles(files)}
className="mt-2" className="mt-2"
/> />
{audioUploadedFiles?.map((file: any, index: number) => (
<div>
<div
key={index}
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.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</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>
</div>
))}
</div> </div>
{audioFile && ( {audioFile && (
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
@ -691,15 +1019,31 @@ export default function FormTaskEdit() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4"> <div className="mt-4">
<Label htmlFor="voiceNoteLink">Link Berita</Label> <h2 className="text-lg font-bold">Link Berita</h2>
<Input {urlInputs.map((url: any, index: any) => (
id="voiceNoteLink" <div
key={url.id}
className="flex items-center gap-2 mt-2"
>
<input
type="url" type="url"
placeholder="Masukkan link voice note" className="border rounded p-2 w-full"
value={voiceNoteLink} defaultValue={url.attachmentUrl}
onChange={(e) => setVoiceNoteLink(e.target.value)} onChange={(e) =>
handleLinkChange(index, e.target.value)
}
placeholder={`Masukkan link berita ${index + 1}`}
/> />
</div> </div>
))}
<button
type="button"
className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
onClick={handleAddLink}
>
Tambah Link
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -37,12 +37,15 @@ import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader"; import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { error } from "@/config/swal"; import { error } from "@/config/swal";
import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/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" }),
naration: z.string().min(2, { naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.", message: "Narasi Penugasan harus lebih dari 2 karakter.",
}), }),
// url: z.string().min(1, { message: "Judul diperlukan" }),
}); });
interface FileWithPreview extends File { interface FileWithPreview extends File {
@ -63,6 +66,7 @@ export type taskDetail = {
id: number; id: number;
name: string; name: string;
}; };
attachmentUrl: string;
taskType: string; taskType: string;
broadcastType: string; broadcastType: string;
narration: string; narration: string;
@ -120,6 +124,7 @@ export default function FormTask() {
polda: false, polda: false,
polres: false, polres: false,
}); });
const [links, setLinks] = useState<string[]>([""]);
const { const {
register, register,
@ -217,6 +222,7 @@ export default function FormTask() {
taskType: string; taskType: string;
assignedToRole: string; assignedToRole: string;
broadcastType: string; broadcastType: string;
attachmentUrl: string[];
} = { } = {
...data, ...data,
// assignmentType, // assignmentType,
@ -232,6 +238,7 @@ export default function FormTask() {
narration: data.naration, narration: data.naration,
platformType: "", platformType: "",
title: data.title, title: data.title,
attachmentUrl: links,
}; };
const response = await createTask(requestData); const response = await createTask(requestData);
@ -240,7 +247,7 @@ export default function FormTask() {
console.log("response", response); console.log("response", response);
const id = response?.data?.data.id; const id = response?.data?.data.id;
loading();
if (imageFiles?.length == 0) { if (imageFiles?.length == 0) {
setIsImageUploadFinish(true); setIsImageUploadFinish(true);
} }
@ -355,20 +362,28 @@ export default function FormTask() {
) { ) {
console.log(idx, id, file, fileTypeId, duration); console.log(idx, id, file, fileTypeId, duration);
// const placements = getPlacement(file.placements); const resCsrf = await getCsrfToken();
// console.log("Placementttt: : ", placements); const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken);
const headers = {
"X-XSRF-TOKEN": csrfToken,
};
const upload = new Upload(file, { const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`, endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000], retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000, chunkSize: 20_000,
metadata: { metadata: {
assignmentid: id, assignmentId: id,
filename: file.name, filename: file.name,
filetype: file.type, contentType: file.type,
fileTypeId: fileTypeId, fileTypeId: fileTypeId,
duration: "", duration,
isWatermark: "true", // hardcode },
onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
}, },
onError: async (e: any) => { onError: async (e: any) => {
console.log("Error upload :", e); console.log("Error upload :", e);
@ -379,7 +394,7 @@ export default function FormTask() {
bytesAccepted: any, bytesAccepted: any,
bytesTotal: any bytesTotal: any
) => { ) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); // const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// progressInfo[idx].percentage = uploadPersen; // progressInfo[idx].percentage = uploadPersen;
// counterUpdateProgress++; // counterUpdateProgress++;
// console.log(counterUpdateProgress); // console.log(counterUpdateProgress);
@ -441,6 +456,22 @@ export default function FormTask() {
}); });
}; };
const handleLinkChange = (index: number, value: string) => {
const updatedLinks = [...links];
updatedLinks[index] = value;
setLinks(updatedLinks);
};
const handleAddRow = () => {
setLinks([...links, ""]);
};
// Remove a specific link row
const handleRemoveRow = (index: number) => {
const updatedLinks = links.filter((_: any, i: any) => i !== index);
setLinks(updatedLinks);
};
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
@ -702,7 +733,7 @@ export default function FormTask() {
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .mp4 atau .mov." label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setVideoFiles(files)}
/> />
</div> </div>
<div> <div>
@ -765,14 +796,36 @@ 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"> <div className="mt-4">
<Label htmlFor="voiceNoteLink">Link Berita</Label> <h2 className="text-lg font-bold">Link Berita</h2>
<Input {links.map((link, index) => (
id="voiceNoteLink" <div key={index} className="flex items-center gap-2 mt-2">
<input
type="url" type="url"
placeholder="Masukkan link voice note" className="border rounded p-2 w-full"
value={voiceNoteLink} placeholder={`Masukkan link berita ${index + 1}`}
onChange={(e) => setVoiceNoteLink(e.target.value)} value={link}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
/> />
{links.length > 1 && (
<button
type="button"
className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)}
>
Hapus
</button>
)}
</div>
))}
<button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
>
Tambah Link
</button>
</div> </div>
</div> </div>
</div> </div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,5 +1,8 @@
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service"; import {
httpDeleteInterceptor,
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function getAgendaSettingsById(id: any) { export async function getAgendaSettingsById(id: any) {
const url = `agenda-settings?id=${id}`; const url = `agenda-settings?id=${id}`;
@ -84,3 +87,8 @@ export async function getPlanningDailyMedsosByPlatform(
const url = `planning/pagination/daily?enablePage=1&size=${size}&page=${page}&date=${date}&typeId=2&platformTypeId=${platformTypeId}`; const url = `planning/pagination/daily?enablePage=1&size=${size}&page=${page}&date=${date}&typeId=2&platformTypeId=${platformTypeId}`;
return httpGetInterceptor(url); return httpGetInterceptor(url);
} }
export async function deleteAgendaSettings(id: any) {
const url = `agenda-settings?id=${id}`;
return httpDeleteInterceptor(url);
}

View File

@ -1,4 +1,5 @@
import { import {
httpDeleteInterceptor,
httpGetInterceptor, httpGetInterceptor,
httpPostInterceptor, httpPostInterceptor,
} from "../http-config/http-interceptor-service"; } from "../http-config/http-interceptor-service";
@ -199,3 +200,13 @@ export async function saveUserReports(data: any) {
const url = "public/users/reports"; const url = "public/users/reports";
return httpPostInterceptor(url, data); return httpPostInterceptor(url, data);
} }
export async function deleteMedia(data: any) {
const url = `media`;
return httpDeleteInterceptor(url, data);
}
export async function deleteFile(data: any) {
const url = "media/file";
return httpDeleteInterceptor(url, data);
}

View File

@ -19,7 +19,11 @@ export async function httpGetInterceptor(pathUrl: any) {
Object.keys(Cookies.get()).forEach((cookieName) => { Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName); Cookies.remove(cookieName);
}); });
if (pathname?.includes("/contributor/") || pathname?.includes("/admin/") || pathname?.includes("/supervisor/")) { if (
pathname?.includes("/contributor/") ||
pathname?.includes("/admin/") ||
pathname?.includes("/supervisor/")
) {
window.location.href = "/"; window.location.href = "/";
} }
} else { } else {
@ -113,7 +117,7 @@ export async function httpPutInterceptor(
} }
} }
export async function httpDeleteInterceptor(pathUrl: any) { export async function httpDeleteInterceptor(pathUrl: any, data?: any) {
const resCsrf = await getCsrfToken(); const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token; const csrfToken = resCsrf?.data?.token;
@ -126,7 +130,7 @@ export async function httpDeleteInterceptor(pathUrl: any) {
}; };
const response = await axiosInterceptorInstance const response = await axiosInterceptorInstance
.delete(pathUrl, { headers: mergedHeaders}) .delete(pathUrl, { headers: mergedHeaders, data })
.catch((error) => error.response); .catch((error) => error.response);
console.log("Response interceptor : ", response); console.log("Response interceptor : ", response);
if (response?.status == 200 || response?.status == 201) { if (response?.status == 200 || response?.status == 201) {