feat:update agenda setting, task,content
This commit is contained in:
parent
01b70c708f
commit
ffb75f9b46
|
|
@ -46,7 +46,6 @@ export type CalendarEvent = {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
|
||||
|
|
@ -86,7 +85,7 @@ interface MonthCardProps {
|
|||
|
||||
interface ListItemProps {
|
||||
item: any;
|
||||
text: string
|
||||
text: string;
|
||||
createdBy: string;
|
||||
bgColor: string;
|
||||
}
|
||||
|
|
@ -120,7 +119,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const [selectedYear, setSelectedYear] = useState(new Date());
|
||||
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([
|
||||
|
|
@ -142,7 +141,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const getCalendarEvents = async () => {
|
||||
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);
|
||||
|
||||
if (res?.error) {
|
||||
|
|
@ -175,13 +178,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
};
|
||||
|
||||
const getYearlyEvents = async () => {
|
||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, '', '');
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
"",
|
||||
""
|
||||
);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
setYearlyData(res?.data?.data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
|
|
@ -218,8 +225,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
if (response?.data && Array.isArray(response?.data)) {
|
||||
// Transform API data to match CalendarEvent type
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map(
|
||||
(item) => ({
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
createBy: "Mabes Polri - Approver",
|
||||
|
|
@ -231,8 +237,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
},
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
setApiEvents(eventsFromAPI);
|
||||
} else {
|
||||
|
|
@ -321,7 +326,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const renderEventContent = (eventInfo: any) => {
|
||||
const { title } = eventInfo.event;
|
||||
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps
|
||||
const { createdByName } = eventInfo.event.extendedProps;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -340,6 +345,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
return "bg-blue-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "polres") {
|
||||
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") {
|
||||
return "bg-green-400 border-none";
|
||||
} else {
|
||||
|
|
@ -362,26 +369,26 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
};
|
||||
|
||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||
{ id: 'january', label: 'Januari' },
|
||||
{ id: 'february', label: 'Februari' },
|
||||
{ id: 'march', label: 'Maret' },
|
||||
{ id: 'april', label: 'April' },
|
||||
{ id: 'may', label: 'Mei' },
|
||||
{ id: 'june', label: 'Juni' },
|
||||
{ id: 'july', label: 'Juli' },
|
||||
{ id: 'august', label: 'Agustus' },
|
||||
{ id: 'september', label: 'September' },
|
||||
{ id: 'october', label: 'Oktober' },
|
||||
{ id: 'november', label: 'November' },
|
||||
{ id: 'december', label: 'Desember' }
|
||||
{ id: "january", label: "Januari" },
|
||||
{ id: "february", label: "Februari" },
|
||||
{ id: "march", label: "Maret" },
|
||||
{ id: "april", label: "April" },
|
||||
{ id: "may", label: "Mei" },
|
||||
{ id: "june", label: "Juni" },
|
||||
{ id: "july", label: "Juli" },
|
||||
{ id: "august", label: "Agustus" },
|
||||
{ id: "september", label: "September" },
|
||||
{ id: "october", label: "Oktober" },
|
||||
{ id: "november", label: "November" },
|
||||
{ id: "december", label: "Desember" },
|
||||
];
|
||||
|
||||
const getEventColor = (type: Event['type']): string => {
|
||||
const colors: Record<Event['type'], string> = {
|
||||
mabes: 'bg-yellow-500',
|
||||
polda: 'bg-blue-400',
|
||||
polres: 'bg-slate-400',
|
||||
international: 'bg-green-400'
|
||||
const getEventColor = (type: Event["type"]): string => {
|
||||
const colors: Record<Event["type"], string> = {
|
||||
mabes: "bg-yellow-500",
|
||||
polda: "bg-blue-400",
|
||||
polres: "bg-slate-400",
|
||||
international: "bg-green-400",
|
||||
};
|
||||
return colors[type];
|
||||
};
|
||||
|
|
@ -397,12 +404,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
allDay: true,
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description
|
||||
}
|
||||
description: item.description,
|
||||
},
|
||||
};
|
||||
const finalEvent: any = {
|
||||
event: formattedEvent
|
||||
}
|
||||
event: formattedEvent,
|
||||
};
|
||||
|
||||
console.log("Event click custom : ", finalEvent);
|
||||
|
||||
|
|
@ -410,14 +417,20 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
setSheetOpen(true);
|
||||
setApiEvents(finalEvent);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
}
|
||||
};
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ item, text, createdBy, bgColor }) => (
|
||||
<div className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`} onClick={() => handleClickListItem(item)}>
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
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-xs text-start mt-2">
|
||||
Created By: {createdBy}
|
||||
</p>
|
||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -462,7 +475,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
>
|
||||
<Card>
|
||||
<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>
|
||||
<CardContent className="p-2 text-sm">
|
||||
<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">
|
||||
<CardContent className="p-0">
|
||||
<CardHeader className="border-none mb-2 pt-5">
|
||||
{roleId == 11 || roleId == 12 ?
|
||||
{roleId == 11 || roleId == 12 ? (
|
||||
<Button
|
||||
onClick={handleDateClick}
|
||||
className="dark:bg-background dark:text-foreground"
|
||||
>
|
||||
<Plus className="w-4 h-4 me-1" />
|
||||
{"Tambahkan Agenda baru"}
|
||||
</Button> :
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
}
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<div className="px-3">
|
||||
|
|
@ -597,35 +613,53 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||
handleViewChange(info.view.type);
|
||||
}}
|
||||
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
||||
viewClassNames={
|
||||
activeView === "listYear" ? "hide-calendar-grid" : ""
|
||||
}
|
||||
/>
|
||||
|
||||
{activeView === "listYear" && (
|
||||
<div className="custom-ui">
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(0, 3).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(0, 3).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Second Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(3, 6).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(3, 6).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Third Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(6, 9).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(6, 9).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Fourth Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(9, 12).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(9, 12).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ import { error, loading, success } from "@/lib/swal";
|
|||
import Cookies from "js-cookie";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { postSchedule } from "@/service/schedule/schedule";
|
||||
import {
|
||||
deleteAgendaSettings,
|
||||
getAgendaSettingsById,
|
||||
saveAgendaSettings,
|
||||
} from "@/service/agenda-setting/agenda-setting";
|
||||
|
|
@ -52,6 +52,10 @@ import { Icon } from "@iconify/react";
|
|||
import Image from "next/image";
|
||||
import { UnitMapping } from "./unit-mapping";
|
||||
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({
|
||||
title: z.string().min(3, { message: "Required" }),
|
||||
|
|
@ -80,15 +84,16 @@ const EventModal = ({
|
|||
event: any;
|
||||
selectedDate: any;
|
||||
}) => {
|
||||
const [detail, setDetail] = useState<any>();
|
||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||
const [isPending, startTransition] = React.useTransition();
|
||||
const [agendaType, setAgendaType] = React.useState<any>(categories[0].value);
|
||||
const [listDest, setListDest] = useState([]);
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
|
||||
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||
|
|
@ -129,11 +134,16 @@ const EventModal = ({
|
|||
semua: false,
|
||||
nasional: false,
|
||||
polda: false,
|
||||
polres: 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 [selectedPolres, setSelectedPolres] = React.useState([]);
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
|
|
@ -153,6 +163,14 @@ const EventModal = ({
|
|||
const detail = res?.data?.data;
|
||||
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;
|
||||
setImageUploadedFiles(
|
||||
attachments?.filter((file: any) => file.fileTypeId == 1)
|
||||
|
|
@ -169,7 +187,7 @@ const EventModal = ({
|
|||
}
|
||||
|
||||
fetchDetailData();
|
||||
}, [event]);
|
||||
}, [event, setValue]);
|
||||
|
||||
// useEffect(() => {
|
||||
// async function fetchPoldaPolres() {
|
||||
|
|
@ -226,22 +244,32 @@ const EventModal = ({
|
|||
setWilayahPublish((prev: any) => {
|
||||
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) {
|
||||
setAgendaType("all");
|
||||
return {
|
||||
semua: true,
|
||||
nasional: false,
|
||||
polda: false,
|
||||
satker: false,
|
||||
internasional: false,
|
||||
nasional: true,
|
||||
polda: true,
|
||||
polres: true,
|
||||
satker: true,
|
||||
international: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Uncheck "semua" if any other is selected
|
||||
// Uncheck "semua" if any other option is selected
|
||||
if (key !== "semua") {
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
|
@ -251,12 +279,15 @@ const EventModal = ({
|
|||
if (wilayahPublish.semua) publishTo.push("all");
|
||||
if (wilayahPublish.nasional) publishTo.push("mabes");
|
||||
if (wilayahPublish.polda) publishTo.push(...selectedPolda);
|
||||
if (wilayahPublish.polres) publishTo.push(...selectedPolres);
|
||||
if (wilayahPublish.satker) publishTo.push(...selectedSatker);
|
||||
if (wilayahPublish.internasional) publishTo.push("internasional");
|
||||
if (wilayahPublish.international) publishTo.push("international");
|
||||
|
||||
const reqData = {
|
||||
id: detailData?.id,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
agendaType, // Include agendaType in request
|
||||
publishTo,
|
||||
startDate: format(startDate, "yyyy-MM-dd"),
|
||||
endDate: format(endDate, "yyyy-MM-dd"),
|
||||
|
|
@ -273,50 +304,40 @@ const EventModal = ({
|
|||
const id = response?.data?.data.id;
|
||||
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
if (imageFiles?.length === 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
imageFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "1", "0");
|
||||
});
|
||||
|
||||
if (videoFiles?.length == 0) {
|
||||
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) {
|
||||
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) {
|
||||
if (audioFiles?.length === 0) {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
audioFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
||||
});
|
||||
|
||||
// Optional: Use Swal for success feedback
|
||||
// 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) => {
|
||||
if (
|
||||
(wilayahPublish.polda && selectedPolda.length === 0) ||
|
||||
(wilayahPublish.satker && selectedSatker.length === 0)
|
||||
(wilayahPublish.satker && selectedSatker.length === 0) ||
|
||||
(wilayahPublish.polres && selectedPolres.length === 0)
|
||||
) {
|
||||
toast({
|
||||
title: "Pilih ID untuk Polda/Satker",
|
||||
|
|
@ -524,6 +545,60 @@ const EventModal = ({
|
|||
|
||||
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 (
|
||||
<>
|
||||
<DeleteConfirmationDialog
|
||||
|
|
@ -645,7 +720,7 @@ const EventModal = ({
|
|||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="wilayahPublish">Jenis Agenda</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div>
|
||||
<Checkbox
|
||||
id="semua"
|
||||
|
|
@ -685,6 +760,25 @@ const EventModal = ({
|
|||
/>
|
||||
)}
|
||||
</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>
|
||||
<Checkbox
|
||||
id="satker"
|
||||
|
|
@ -706,11 +800,11 @@ const EventModal = ({
|
|||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="internasional"
|
||||
checked={wilayahPublish.internasional}
|
||||
onCheckedChange={() => toggleWilayah("internasional")}
|
||||
id="international"
|
||||
checked={wilayahPublish.international}
|
||||
onCheckedChange={() => toggleWilayah("international")}
|
||||
/>
|
||||
<label htmlFor="internasional" className="ml-2">
|
||||
<label htmlFor="international" className="ml-2">
|
||||
Internasional
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -743,33 +837,6 @@ const EventModal = ({
|
|||
<div className="space-y-3">
|
||||
<div>
|
||||
<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
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
|
|
@ -777,12 +844,16 @@ const EventModal = ({
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
{videoUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||
<video
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={file.url}
|
||||
controls
|
||||
title={`Video ${file.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
<div
|
||||
key={index}
|
||||
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" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
|
|
@ -817,10 +892,15 @@ const EventModal = ({
|
|||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
{textUploadedFiles?.map((file: any, index: number) => (
|
||||
<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"
|
||||
|
|
@ -846,7 +926,12 @@ const EventModal = ({
|
|||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
|
|
@ -855,10 +940,15 @@ const EventModal = ({
|
|||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
{textUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||
<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"
|
||||
|
|
@ -884,7 +974,12 @@ const EventModal = ({
|
|||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -904,6 +999,67 @@ const EventModal = ({
|
|||
onDrop={(files) => setAudioFiles(files)}
|
||||
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>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
|
|
@ -938,6 +1094,13 @@ const EventModal = ({
|
|||
"Simpan Agenda Setting"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 bg-red-500 text-white"
|
||||
type="button"
|
||||
onClick={() => handleDelete(event?.event?.id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
{event?.length > 1 && (
|
||||
<Button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
|
|
@ -35,11 +36,11 @@ const FormSchema = z.object({
|
|||
interface UnitType {
|
||||
id: number;
|
||||
name: string;
|
||||
subDestination: { id: number; name: string }[];
|
||||
subDestination: { id: number; name: string }[] | null;
|
||||
}
|
||||
|
||||
export function UnitMapping(props: {
|
||||
unit: string;
|
||||
unit: "Polda" | "Satker" | "Polres";
|
||||
sendDataToParent: (data: string[]) => void;
|
||||
isDetail: boolean;
|
||||
initData?: string[];
|
||||
|
|
@ -49,7 +50,11 @@ export function UnitMapping(props: {
|
|||
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
|
|
@ -61,6 +66,7 @@ export function UnitMapping(props: {
|
|||
async function initState() {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setupUnit(response?.data?.data.list);
|
||||
console.log("list", response?.data?.data.list);
|
||||
}
|
||||
|
||||
initState();
|
||||
|
|
@ -74,12 +80,17 @@ export function UnitMapping(props: {
|
|||
const isAllSatkerChecked = satkerList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
const isAllPolresChecked = polresList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
|
||||
const setupUnit = (data: UnitType[]) => {
|
||||
const temp = data.filter((a) => a.name.includes("POLDA"));
|
||||
const temp2 = data.filter((a) => a.name.includes("SATKER"));
|
||||
const temp3 = temp.flatMap((item) => item.subDestination || []);
|
||||
setUnitList(temp);
|
||||
setSatkerList(temp2[0].subDestination);
|
||||
setSatkerList(temp2[0]?.subDestination || []);
|
||||
setPolresList(temp3);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -96,23 +107,32 @@ export function UnitMapping(props: {
|
|||
Pilih {unit}
|
||||
</a>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md">
|
||||
<DialogContent size="md" className="h-[500px] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{unit}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{unit === "Polda" ? (
|
||||
<Form {...form}>
|
||||
<form className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
id={`all-${unit}`}
|
||||
checked={isAllUnitChecked}
|
||||
checked={
|
||||
unit === "Polda"
|
||||
? isAllUnitChecked
|
||||
: unit === "Satker"
|
||||
? isAllSatkerChecked
|
||||
: isAllPolresChecked
|
||||
}
|
||||
disabled={isDetail}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
form.setValue(
|
||||
"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 {
|
||||
form.setValue("items", []);
|
||||
|
|
@ -128,9 +148,16 @@ export function UnitMapping(props: {
|
|||
name="items"
|
||||
render={() => (
|
||||
<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
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
|
|
@ -144,9 +171,7 @@ export function UnitMapping(props: {
|
|||
<FormControl>
|
||||
<Checkbox
|
||||
disabled={isDetail}
|
||||
checked={field.value?.includes(
|
||||
String(item.id)
|
||||
)}
|
||||
checked={field.value?.includes(String(item.id))}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([
|
||||
|
|
@ -173,80 +198,6 @@ export function UnitMapping(props: {
|
|||
/>
|
||||
</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>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
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>[] = [
|
||||
{
|
||||
|
|
@ -135,6 +139,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -159,7 +208,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</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" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
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>[] = [
|
||||
{
|
||||
|
|
@ -131,6 +139,52 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -155,7 +209,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</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" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -53,12 +53,18 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
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 router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
|
|
@ -161,6 +167,37 @@ const TableImage = () => {
|
|||
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 (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
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>[] = [
|
||||
{
|
||||
|
|
@ -136,6 +140,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -160,7 +209,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</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" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
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>[] = [
|
||||
{
|
||||
|
|
@ -136,6 +140,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -160,7 +209,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</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" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
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>[] = [
|
||||
{
|
||||
|
|
@ -115,6 +122,49 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -139,7 +189,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</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" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -81,6 +82,11 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function FormAudioUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -111,10 +117,20 @@ export default function FormAudioUpdate() {
|
|||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[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({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
|
|
@ -139,21 +155,6 @@ export default function FormAudioUpdate() {
|
|||
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>) => {
|
||||
if (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));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
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(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -217,11 +235,13 @@ export default function FormAudioUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -244,7 +264,25 @@ export default function FormAudioUpdate() {
|
|||
initState();
|
||||
}, [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 finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -256,9 +294,9 @@ export default function FormAudioUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -286,12 +324,7 @@ export default function FormAudioUpdate() {
|
|||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
|
|
@ -430,22 +463,24 @@ export default function FormAudioUpdate() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
// const handleRemoveFile = (file: FileWithPreview) => {
|
||||
// const uploadedFiles = files;
|
||||
// const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
// setFiles([...filtered]);
|
||||
// };
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-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) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -460,10 +495,10 @@ export default function FormAudioUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
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>
|
||||
</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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -606,12 +690,12 @@ export default function FormAudioUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -752,14 +836,34 @@ export default function FormAudioUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
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}
|
||||
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()}
|
||||
</Badge>
|
||||
<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
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -767,38 +871,21 @@ export default function FormAudioUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<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
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
id={option.id}
|
||||
checked={
|
||||
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>
|
||||
</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>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
deleteMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -42,7 +44,7 @@ import dynamic from "next/dynamic";
|
|||
import { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
|
||||
|
|
@ -65,7 +67,13 @@ type Detail = {
|
|||
title: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
categoryId: {
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
publishedFor: string;
|
||||
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
|
@ -75,6 +83,11 @@ type Detail = {
|
|||
tags: string;
|
||||
};
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
|
|
@ -122,7 +135,17 @@ export default function FormImageUpdate() {
|
|||
[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({
|
||||
allUnit: 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>) => {
|
||||
if (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));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
|
|
@ -193,14 +212,24 @@ export default function FormImageUpdate() {
|
|||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
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 () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
|
|
@ -238,11 +267,13 @@ export default function FormImageUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -259,7 +290,32 @@ export default function FormImageUpdate() {
|
|||
initState();
|
||||
}, [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) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -271,9 +327,9 @@ export default function FormImageUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -451,16 +507,18 @@ export default function FormImageUpdate() {
|
|||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-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) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -475,10 +533,10 @@ export default function FormImageUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
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>
|
||||
</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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -549,14 +656,17 @@ export default function FormImageUpdate() {
|
|||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<Select
|
||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
||||
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category:", id);
|
||||
setSelectedTarget(id);
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedTarget(id); // Perbarui ID kategori
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
<SelectValue placeholder="Pilih">
|
||||
{categories.find((cat) => cat.id === selectedTarget)
|
||||
?.name || "Pilih"}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
|
|
@ -619,12 +729,12 @@ export default function FormImageUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
onClick={() => handleDeleteFile(id)}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
Remove file
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -782,24 +892,30 @@ export default function FormImageUpdate() {
|
|||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 ">
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
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
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* <div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
|
|
@ -808,44 +924,27 @@ export default function FormImageUpdate() {
|
|||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
id={option.id}
|
||||
checked={
|
||||
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>
|
||||
</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>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
|
|||
import { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
|
||||
|
|
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
|
|||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
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>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string): void => {
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
.map((opt: any) => opt.id)
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const updatedPublishedFor = publishedFor.includes(id)
|
||||
? publishedFor.filter((item) => item !== id)
|
||||
: [...publishedFor, id];
|
||||
|
||||
// Remove "all" if any checkbox is unchecked
|
||||
if (publishedFor.includes("all") && id !== "all") {
|
||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||
} else {
|
||||
setPublishedFor(updatedPublishedFor);
|
||||
}
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: TeksSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -293,7 +284,7 @@ export default function FormTeksUpdate() {
|
|||
statusId: "1",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: 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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -785,7 +799,7 @@ export default function FormTeksUpdate() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
{/* <div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
|
|
@ -794,18 +808,38 @@ export default function FormTeksUpdate() {
|
|||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
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}
|
||||
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()}
|
||||
</Badge>
|
||||
<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
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -817,7 +851,6 @@ export default function FormTeksUpdate() {
|
|||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
value={detail?.publishedForObject.name}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ export default function FormVideo() {
|
|||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
|
|
@ -544,11 +544,11 @@ export default function FormVideo() {
|
|||
filename: file.name,
|
||||
filetype: file.type,
|
||||
duration,
|
||||
isWatermark: "false", // hardcode
|
||||
isWatermark: "true", // hardcode
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject()
|
||||
xhr.withCredentials = true
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -53,7 +54,7 @@ import Image from "next/image";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
|
||||
const videoSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -88,6 +89,11 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function FormVideoUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -126,11 +132,20 @@ export default function FormVideoUpdate() {
|
|||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[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";
|
||||
|
||||
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>) => {
|
||||
if (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));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
getCategories();
|
||||
|
|
@ -189,6 +194,29 @@ export default function FormVideoUpdate() {
|
|||
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 () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
|
|
@ -226,11 +254,13 @@ export default function FormVideoUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -253,7 +283,26 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, [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) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -265,9 +314,9 @@ export default function FormVideoUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -295,32 +344,33 @@ export default function FormVideoUpdate() {
|
|||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/content/image");
|
||||
});
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// text: "Data berhasil disimpan.",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then(() => {
|
||||
// router.push("/en/contributor/content/video");
|
||||
// });
|
||||
};
|
||||
|
||||
const onSubmit = (data: VideoSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/content/video");
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -385,22 +435,6 @@ export default function FormVideoUpdate() {
|
|||
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) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -454,16 +488,18 @@ export default function FormVideoUpdate() {
|
|||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-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) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -478,10 +514,10 @@ export default function FormVideoUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
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>
|
||||
</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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -626,12 +711,12 @@ export default function FormVideoUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -782,14 +867,34 @@ export default function FormVideoUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
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}
|
||||
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()}
|
||||
</Badge>
|
||||
<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
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -797,38 +902,21 @@ export default function FormVideoUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<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
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
id={option.id}
|
||||
checked={
|
||||
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>
|
||||
</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>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
|||
import { successCallback } from "@/config/swal";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
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({
|
||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -162,6 +166,11 @@ interface UploadResult {
|
|||
creatorName: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function FormTaskDetail() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -191,6 +200,7 @@ export default function FormTaskDetail() {
|
|||
const [type, setType] = useState<string>("1");
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [urlInputs, setUrlInputs] = useState<string[]>([]);
|
||||
const [refresh] = useState(false);
|
||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
|
|
@ -224,6 +234,22 @@ export default function FormTaskDetail() {
|
|||
const [isRecording, setIsRecording] = useState(false);
|
||||
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 [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -283,32 +309,60 @@ export default function FormTaskDetail() {
|
|||
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) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
);
|
||||
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();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleUrlChange = (index: number, newUrl: string) => {
|
||||
setUrlInputs((prev: any) =>
|
||||
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (detail?.broadcastType) {
|
||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
||||
setBroadcastType(detail.broadcastType);
|
||||
}
|
||||
}, [detail?.broadcastType]);
|
||||
|
||||
useEffect(() => {
|
||||
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({
|
||||
all: outputSet.has(0),
|
||||
video: outputSet.has(2),
|
||||
audio: outputSet.has(4),
|
||||
image: outputSet.has(1),
|
||||
text: outputSet.has(3),
|
||||
text: outputSet.has(5),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -327,71 +381,6 @@ export default function FormTaskDetail() {
|
|||
}
|
||||
}, [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 = () => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -445,21 +434,6 @@ export default function FormTaskDetail() {
|
|||
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 = () => {
|
||||
sendSuggestionParent();
|
||||
};
|
||||
|
|
@ -527,29 +501,6 @@ export default function FormTaskDetail() {
|
|||
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() {
|
||||
const response = await getAcceptanceAssignmentStatus(id);
|
||||
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
|
||||
};
|
||||
|
||||
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 (
|
||||
<Card>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -802,7 +776,7 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2">
|
||||
<Label>Kode Unik</Label>
|
||||
|
|
@ -1033,27 +1007,7 @@ export default function FormTaskDetail() {
|
|||
))}
|
||||
</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">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<Controller
|
||||
|
|
@ -1063,51 +1017,211 @@ export default function FormTaskDetail() {
|
|||
<ViewEditor initialData={detail?.narration} />
|
||||
)}
|
||||
/>
|
||||
{errors.naration?.message && (
|
||||
{/* {errors.naration?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.naration.message}
|
||||
</p>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<Label htmlFor="attachment">Lampiran</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
"mov/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
||||
{videoUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<video
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={file.url}
|
||||
controls
|
||||
title={`Video ${file.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
<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>
|
||||
<Label>Foto</Label>
|
||||
<FileUploader
|
||||
<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>}
|
||||
{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={{
|
||||
"image/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
<FileUploader
|
||||
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
|
||||
{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={{
|
||||
"pdf/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
/> */}
|
||||
</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
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -1117,7 +1231,8 @@ export default function FormTaskDetail() {
|
|||
downloadOnSavePress={true}
|
||||
downloadFileExtension="webm"
|
||||
/>
|
||||
<FileUploader
|
||||
)}
|
||||
{/* <FileUploader
|
||||
accept={{
|
||||
"mp3/*": [],
|
||||
"wav/*": [],
|
||||
|
|
@ -1126,7 +1241,7 @@ export default function FormTaskDetail() {
|
|||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
className="mt-2"
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
|
|
@ -1144,14 +1259,20 @@ export default function FormTaskDetail() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
{urlInputs.map((url: any, index: any) => (
|
||||
<div className="mt-4 flex flex-col" key={index}>
|
||||
<Label htmlFor={`url-${index}`}>Link Berita</Label>
|
||||
<Button
|
||||
size="sm"
|
||||
id={`url-${index}`}
|
||||
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>
|
||||
|
|
@ -1172,8 +1293,7 @@ export default function FormTaskDetail() {
|
|||
</Button>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="">
|
||||
|
|
@ -1189,7 +1309,8 @@ export default function FormTaskDetail() {
|
|||
<Button
|
||||
className="btn btn-primary ml-3 mr-3"
|
||||
style={
|
||||
statusAcceptance || detail?.createdBy?.id !== Number(userId)
|
||||
statusAcceptance ||
|
||||
detail?.createdBy?.id !== Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
|
|
@ -1211,6 +1332,7 @@ export default function FormTaskDetail() {
|
|||
// }
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
variant={"default"}
|
||||
onClick={() => setIsTableResult(!isTableResult)}
|
||||
|
|
@ -1402,10 +1524,14 @@ export default function FormTaskDetail() {
|
|||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
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" />
|
||||
<span className="ml-1">Delete</span>
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ import {
|
|||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
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({
|
||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -62,6 +68,7 @@ export type taskDetail = {
|
|||
taskType: string;
|
||||
broadcastType: string;
|
||||
narration: string;
|
||||
attachmentUrl: string;
|
||||
is_active: string;
|
||||
};
|
||||
|
||||
|
|
@ -69,6 +76,16 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Url = {
|
||||
id: number;
|
||||
attachmentUrl: string;
|
||||
};
|
||||
|
||||
export default function FormTaskEdit() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -101,6 +118,7 @@ export default function FormTaskEdit() {
|
|||
const [type, setType] = useState<string>("1");
|
||||
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
|
||||
const [refresh] = useState(false);
|
||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
|
|
@ -110,6 +128,19 @@ export default function FormTaskEdit() {
|
|||
const [isRecording, setIsRecording] = useState(false);
|
||||
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 [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -118,6 +149,8 @@ export default function FormTaskEdit() {
|
|||
polres: false,
|
||||
});
|
||||
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
|
|
@ -165,6 +198,11 @@ export default function FormTaskEdit() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
if (details?.urls) {
|
||||
// Save the URLs as objects
|
||||
setUrlInputs(details.urls);
|
||||
}
|
||||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
|
|
@ -172,12 +210,32 @@ export default function FormTaskEdit() {
|
|||
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.
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleUrlChange = (index: number, newUrl: string) => {
|
||||
setUrlInputs((prev: any) =>
|
||||
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (detail?.broadcastType) {
|
||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
||||
|
|
@ -192,7 +250,7 @@ export default function FormTaskEdit() {
|
|||
video: outputSet.has(2),
|
||||
audio: outputSet.has(4),
|
||||
image: outputSet.has(1),
|
||||
text: outputSet.has(3),
|
||||
text: outputSet.has(5),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -265,6 +323,7 @@ export default function FormTaskEdit() {
|
|||
taskType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
|
|
@ -281,22 +340,52 @@ export default function FormTaskEdit() {
|
|||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
||||
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 id = response?.data?.data.id;
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
imageFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "1", "0");
|
||||
});
|
||||
|
||||
if (videoFiles?.length == 0) {
|
||||
setIsVideoUploadFinish(true);
|
||||
}
|
||||
videoFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "2", "0");
|
||||
});
|
||||
|
||||
if (textFiles?.length == 0) {
|
||||
setIsTextUploadFinish(true);
|
||||
}
|
||||
textFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "3", "0");
|
||||
});
|
||||
|
||||
if (audioFiles?.length == 0) {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
audioFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
||||
});
|
||||
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// text: "Data berhasil disimpan.",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then(() => {
|
||||
// router.push("/en/contributor/task");
|
||||
// });
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
|
|
@ -365,6 +436,164 @@ export default function FormTaskEdit() {
|
|||
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 (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -372,23 +601,6 @@ export default function FormTaskEdit() {
|
|||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<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">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
|
|
@ -530,7 +742,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<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)}
|
||||
// value={String(mainType)}
|
||||
// onValueChange={(value) => setMainType(Number(value))}
|
||||
|
|
@ -545,7 +757,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Jenis Tugas </Label>
|
||||
<RadioGroup
|
||||
value={detail.taskType.toString()}
|
||||
defaultValue={detail.taskType.toString()}
|
||||
onValueChange={(value) => setTaskType(String(value))}
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
|
|
@ -559,7 +771,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<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
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
|
|
@ -620,7 +832,7 @@ export default function FormTaskEdit() {
|
|||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
|
|
@ -630,9 +842,38 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .mp4 atau .mov."
|
||||
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>
|
||||
<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
|
||||
accept={{
|
||||
"image/*": [],
|
||||
|
|
@ -641,9 +882,38 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
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>
|
||||
<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
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
|
|
@ -652,9 +922,38 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .pdf."
|
||||
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>
|
||||
<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
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -674,6 +973,35 @@ export default function FormTaskEdit() {
|
|||
onDrop={(files) => setAudioFiles(files)}
|
||||
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>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
|
|
@ -691,15 +1019,31 @@ export default function FormTaskEdit() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
{urlInputs.map((url: any, index: any) => (
|
||||
<div
|
||||
key={url.id}
|
||||
className="flex items-center gap-2 mt-2"
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
className="border rounded p-2 w-full"
|
||||
defaultValue={url.attachmentUrl}
|
||||
onChange={(e) =>
|
||||
handleLinkChange(index, e.target.value)
|
||||
}
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
/>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -37,12 +37,15 @@ import { AudioRecorder } from "react-audio-voice-recorder";
|
|||
import FileUploader from "@/components/form/shared/file-uploader";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { error } from "@/config/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
// url: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
|
|
@ -63,6 +66,7 @@ export type taskDetail = {
|
|||
id: number;
|
||||
name: string;
|
||||
};
|
||||
attachmentUrl: string;
|
||||
taskType: string;
|
||||
broadcastType: string;
|
||||
narration: string;
|
||||
|
|
@ -120,6 +124,7 @@ export default function FormTask() {
|
|||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
register,
|
||||
|
|
@ -217,6 +222,7 @@ export default function FormTask() {
|
|||
taskType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
|
|
@ -232,6 +238,7 @@ export default function FormTask() {
|
|||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
||||
const response = await createTask(requestData);
|
||||
|
|
@ -240,7 +247,7 @@ export default function FormTask() {
|
|||
console.log("response", response);
|
||||
|
||||
const id = response?.data?.data.id;
|
||||
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
|
|
@ -355,20 +362,28 @@ export default function FormTask() {
|
|||
) {
|
||||
console.log(idx, id, file, fileTypeId, duration);
|
||||
|
||||
// const placements = getPlacement(file.placements);
|
||||
// console.log("Placementttt: : ", placements);
|
||||
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,
|
||||
assignmentId: id,
|
||||
filename: file.name,
|
||||
filetype: file.type,
|
||||
contentType: file.type,
|
||||
fileTypeId: fileTypeId,
|
||||
duration: "",
|
||||
isWatermark: "true", // hardcode
|
||||
duration,
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
@ -379,7 +394,7 @@ export default function FormTask() {
|
|||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
) => {
|
||||
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// progressInfo[idx].percentage = uploadPersen;
|
||||
// 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 (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -702,7 +733,7 @@ export default function FormTask() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -765,14 +796,36 @@ export default function FormTask() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
{links.map((link, index) => (
|
||||
<div key={index} className="flex items-center gap-2 mt-2">
|
||||
<input
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
className="border rounded p-2 w-full"
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
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>
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
|
|
@ -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) {
|
||||
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}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function deleteAgendaSettings(id: any) {
|
||||
const url = `agenda-settings?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
httpDeleteInterceptor,
|
||||
httpGetInterceptor,
|
||||
httpPostInterceptor,
|
||||
} from "../http-config/http-interceptor-service";
|
||||
|
|
@ -199,3 +200,13 @@ export async function saveUserReports(data: any) {
|
|||
const url = "public/users/reports";
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@ export async function httpGetInterceptor(pathUrl: any) {
|
|||
Object.keys(Cookies.get()).forEach((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 = "/";
|
||||
}
|
||||
} 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 csrfToken = resCsrf?.data?.token;
|
||||
|
||||
|
|
@ -126,7 +130,7 @@ export async function httpDeleteInterceptor(pathUrl: any) {
|
|||
};
|
||||
|
||||
const response = await axiosInterceptorInstance
|
||||
.delete(pathUrl, { headers: mergedHeaders})
|
||||
.delete(pathUrl, { headers: mergedHeaders, data })
|
||||
.catch((error) => error.response);
|
||||
console.log("Response interceptor : ", response);
|
||||
if (response?.status == 200 || response?.status == 201) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue