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_YEAR = dayjs().format("YYYY");
|
||||||
const INITIAL_MONTH = dayjs().format("M");
|
const INITIAL_MONTH = dayjs().format("M");
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ export interface AgendaSettingsAPIResponse {
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdById: number | null;
|
createdById: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface YearlyData {
|
interface YearlyData {
|
||||||
january?: Event[];
|
january?: Event[];
|
||||||
february?: Event[];
|
february?: Event[];
|
||||||
|
|
@ -86,7 +85,7 @@ interface MonthCardProps {
|
||||||
|
|
||||||
interface ListItemProps {
|
interface ListItemProps {
|
||||||
item: any;
|
item: any;
|
||||||
text: string
|
text: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +119,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
const [selectedYear, setSelectedYear] = useState(new Date());
|
const [selectedYear, setSelectedYear] = useState(new Date());
|
||||||
const [selectedMonth, setSelectedMonth] = useState(
|
const [selectedMonth, setSelectedMonth] = useState(
|
||||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)),
|
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
||||||
);
|
);
|
||||||
|
|
||||||
const [dragEvents] = useState([
|
const [dragEvents] = useState([
|
||||||
|
|
@ -142,7 +141,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
const getCalendarEvents = async () => {
|
const getCalendarEvents = async () => {
|
||||||
console.log("View : ", activeView);
|
console.log("View : ", activeView);
|
||||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, selectedMonth.format("M") || INITIAL_MONTH, "");
|
const res = await getAgendaSettingsList(
|
||||||
|
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||||
|
selectedMonth.format("M") || INITIAL_MONTH,
|
||||||
|
""
|
||||||
|
);
|
||||||
console.log("View : API Response:", res);
|
console.log("View : API Response:", res);
|
||||||
|
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
|
|
@ -175,13 +178,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getYearlyEvents = async () => {
|
const getYearlyEvents = async () => {
|
||||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, '', '');
|
const res = await getAgendaSettingsList(
|
||||||
if (res?.error) {
|
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||||
error(res.message);
|
"",
|
||||||
return false;
|
""
|
||||||
}
|
);
|
||||||
setYearlyData(res?.data?.data);
|
if (res?.error) {
|
||||||
}
|
error(res.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setYearlyData(res?.data?.data);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedCategory(categories?.map((c) => c.value));
|
setSelectedCategory(categories?.map((c) => c.value));
|
||||||
|
|
@ -218,21 +225,19 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
if (response?.data && Array.isArray(response?.data)) {
|
if (response?.data && Array.isArray(response?.data)) {
|
||||||
// Transform API data to match CalendarEvent type
|
// Transform API data to match CalendarEvent type
|
||||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map(
|
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
|
||||||
(item) => ({
|
id: item.id.toString(),
|
||||||
id: item.id.toString(),
|
title: item.title,
|
||||||
title: item.title,
|
createBy: "Mabes Polri - Approver",
|
||||||
createBy: "Mabes Polri - Approver",
|
createdByName: item.createdByName,
|
||||||
createdByName: item.createdByName,
|
start: new Date(item.startDate),
|
||||||
start: new Date(item.startDate),
|
end: new Date(item.endDate),
|
||||||
end: new Date(item.endDate),
|
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
extendedProps: {
|
||||||
extendedProps: {
|
calendar: item.agendaType,
|
||||||
calendar: item.agendaType,
|
description: item.description,
|
||||||
description: item.description,
|
},
|
||||||
},
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setApiEvents(eventsFromAPI);
|
setApiEvents(eventsFromAPI);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -321,7 +326,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
const renderEventContent = (eventInfo: any) => {
|
const renderEventContent = (eventInfo: any) => {
|
||||||
const { title } = eventInfo.event;
|
const { title } = eventInfo.event;
|
||||||
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps
|
const { createdByName } = eventInfo.event.extendedProps;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -340,6 +345,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
return "bg-blue-400 border-none";
|
return "bg-blue-400 border-none";
|
||||||
} else if (arg.event.extendedProps.calendar === "polres") {
|
} else if (arg.event.extendedProps.calendar === "polres") {
|
||||||
return "bg-slate-400 border-none";
|
return "bg-slate-400 border-none";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "satker") {
|
||||||
|
return "bg-orange-500 border-none";
|
||||||
} else if (arg.event.extendedProps.calendar === "international") {
|
} else if (arg.event.extendedProps.calendar === "international") {
|
||||||
return "bg-green-400 border-none";
|
return "bg-green-400 border-none";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -359,29 +366,29 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
const handleViewChange = (viewType: string) => {
|
const handleViewChange = (viewType: string) => {
|
||||||
console.log("Change view : ", viewType);
|
console.log("Change view : ", viewType);
|
||||||
setActiveView(viewType);
|
setActiveView(viewType);
|
||||||
};
|
};
|
||||||
|
|
||||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||||
{ id: 'january', label: 'Januari' },
|
{ id: "january", label: "Januari" },
|
||||||
{ id: 'february', label: 'Februari' },
|
{ id: "february", label: "Februari" },
|
||||||
{ id: 'march', label: 'Maret' },
|
{ id: "march", label: "Maret" },
|
||||||
{ id: 'april', label: 'April' },
|
{ id: "april", label: "April" },
|
||||||
{ id: 'may', label: 'Mei' },
|
{ id: "may", label: "Mei" },
|
||||||
{ id: 'june', label: 'Juni' },
|
{ id: "june", label: "Juni" },
|
||||||
{ id: 'july', label: 'Juli' },
|
{ id: "july", label: "Juli" },
|
||||||
{ id: 'august', label: 'Agustus' },
|
{ id: "august", label: "Agustus" },
|
||||||
{ id: 'september', label: 'September' },
|
{ id: "september", label: "September" },
|
||||||
{ id: 'october', label: 'Oktober' },
|
{ id: "october", label: "Oktober" },
|
||||||
{ id: 'november', label: 'November' },
|
{ id: "november", label: "November" },
|
||||||
{ id: 'december', label: 'Desember' }
|
{ id: "december", label: "Desember" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getEventColor = (type: Event['type']): string => {
|
const getEventColor = (type: Event["type"]): string => {
|
||||||
const colors: Record<Event['type'], string> = {
|
const colors: Record<Event["type"], string> = {
|
||||||
mabes: 'bg-yellow-500',
|
mabes: "bg-yellow-500",
|
||||||
polda: 'bg-blue-400',
|
polda: "bg-blue-400",
|
||||||
polres: 'bg-slate-400',
|
polres: "bg-slate-400",
|
||||||
international: 'bg-green-400'
|
international: "bg-green-400",
|
||||||
};
|
};
|
||||||
return colors[type];
|
return colors[type];
|
||||||
};
|
};
|
||||||
|
|
@ -397,32 +404,38 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
allDay: true,
|
allDay: true,
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
calendar: item.agendaType,
|
calendar: item.agendaType,
|
||||||
description: item.description
|
description: item.description,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const finalEvent: any = {
|
const finalEvent: any = {
|
||||||
event: formattedEvent
|
event: formattedEvent,
|
||||||
}
|
};
|
||||||
|
|
||||||
console.log("Event click custom : ", finalEvent);
|
console.log("Event click custom : ", finalEvent);
|
||||||
|
|
||||||
setSelectedEventDate(null);
|
setSelectedEventDate(null);
|
||||||
setSheetOpen(true);
|
setSheetOpen(true);
|
||||||
setApiEvents(finalEvent);
|
setApiEvents(finalEvent);
|
||||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
}
|
};
|
||||||
|
|
||||||
const ListItem: React.FC<ListItemProps> = ({ item, text, createdBy, bgColor }) => (
|
const ListItem: React.FC<ListItemProps> = ({
|
||||||
<div className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`} onClick={() => handleClickListItem(item)}>
|
item,
|
||||||
|
text,
|
||||||
|
createdBy,
|
||||||
|
bgColor,
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
|
||||||
|
onClick={() => handleClickListItem(item)}
|
||||||
|
>
|
||||||
<p className="ml-1">{text}</p>
|
<p className="ml-1">{text}</p>
|
||||||
<p className="ml-1 text-xs text-start mt-2">
|
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||||
Created By: {createdBy}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
||||||
const { monthId, label } = props;
|
const { monthId, label } = props;
|
||||||
const events: any = yearlyData?.[monthId];
|
const events: any = yearlyData?.[monthId];
|
||||||
const displayedEvents = events?.slice(0, 3);
|
const displayedEvents = events?.slice(0, 3);
|
||||||
const hasMoreEvents = events?.length > 3;
|
const hasMoreEvents = events?.length > 3;
|
||||||
|
|
@ -431,7 +444,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
<h4 className="font-bold text-center">{label}</h4>
|
<h4 className="font-bold text-center">{label}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
{events?.length === 0 ? (
|
{events?.length === 0 ? (
|
||||||
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
||||||
|
|
@ -440,7 +453,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{displayedEvents?.map((event: any, index: number) => (
|
{displayedEvents?.map((event: any, index: number) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={index}
|
key={index}
|
||||||
item={event}
|
item={event}
|
||||||
text={event.title}
|
text={event.title}
|
||||||
|
|
@ -448,7 +461,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
bgColor={getEventColor(event.agendaType)}
|
bgColor={getEventColor(event.agendaType)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{hasMoreEvents && (
|
{hasMoreEvents && (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
|
|
@ -462,12 +475,14 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="bg-default p-2 rounded-t-lg">
|
<CardHeader className="bg-default p-2 rounded-t-lg">
|
||||||
<CardTitle className="text-default-foreground text-base py-0">{label}</CardTitle>
|
<CardTitle className="text-default-foreground text-base py-0">
|
||||||
|
{label}
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-2 text-sm">
|
<CardContent className="p-2 text-sm">
|
||||||
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
|
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
|
||||||
{events.map((event: any, index: number) => (
|
{events.map((event: any, index: number) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={index}
|
key={index}
|
||||||
item={event}
|
item={event}
|
||||||
text={event.title}
|
text={event.title}
|
||||||
|
|
@ -494,16 +509,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<CardHeader className="border-none mb-2 pt-5">
|
<CardHeader className="border-none mb-2 pt-5">
|
||||||
{roleId == 11 || roleId == 12 ?
|
{roleId == 11 || roleId == 12 ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDateClick}
|
onClick={handleDateClick}
|
||||||
className="dark:bg-background dark:text-foreground"
|
className="dark:bg-background dark:text-foreground"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4 me-1" />
|
<Plus className="w-4 h-4 me-1" />
|
||||||
{"Tambahkan Agenda baru"}
|
{"Tambahkan Agenda baru"}
|
||||||
</Button> :
|
</Button>
|
||||||
|
) : (
|
||||||
""
|
""
|
||||||
}
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<div className="px-3">
|
<div className="px-3">
|
||||||
|
|
@ -597,35 +613,53 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||||
handleViewChange(info.view.type);
|
handleViewChange(info.view.type);
|
||||||
}}
|
}}
|
||||||
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
viewClassNames={
|
||||||
|
activeView === "listYear" ? "hide-calendar-grid" : ""
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{activeView === "listYear" && (
|
{activeView === "listYear" && (
|
||||||
<div className="custom-ui">
|
<div className="custom-ui">
|
||||||
<div className="flex gap-1 mt-1">
|
<div className="flex gap-1 mt-1">
|
||||||
{months.slice(0, 3).map(month => (
|
{months.slice(0, 3).map((month) => (
|
||||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
<MonthCard
|
||||||
|
key={month.id}
|
||||||
|
monthId={month.id}
|
||||||
|
label={month.label}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Second Row */}
|
{/* Second Row */}
|
||||||
<div className="flex gap-1 mt-1">
|
<div className="flex gap-1 mt-1">
|
||||||
{months.slice(3, 6).map(month => (
|
{months.slice(3, 6).map((month) => (
|
||||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
<MonthCard
|
||||||
|
key={month.id}
|
||||||
|
monthId={month.id}
|
||||||
|
label={month.label}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Third Row */}
|
{/* Third Row */}
|
||||||
<div className="flex gap-1 mt-1">
|
<div className="flex gap-1 mt-1">
|
||||||
{months.slice(6, 9).map(month => (
|
{months.slice(6, 9).map((month) => (
|
||||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
<MonthCard
|
||||||
|
key={month.id}
|
||||||
|
monthId={month.id}
|
||||||
|
label={month.label}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Fourth Row */}
|
{/* Fourth Row */}
|
||||||
<div className="flex gap-1 mt-1">
|
<div className="flex gap-1 mt-1">
|
||||||
{months.slice(9, 12).map(month => (
|
{months.slice(9, 12).map((month) => (
|
||||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
<MonthCard
|
||||||
|
key={month.id}
|
||||||
|
monthId={month.id}
|
||||||
|
label={month.label}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@ import { error, loading, success } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { postSchedule } from "@/service/schedule/schedule";
|
import { postSchedule } from "@/service/schedule/schedule";
|
||||||
import {
|
import {
|
||||||
|
deleteAgendaSettings,
|
||||||
getAgendaSettingsById,
|
getAgendaSettingsById,
|
||||||
saveAgendaSettings,
|
saveAgendaSettings,
|
||||||
} from "@/service/agenda-setting/agenda-setting";
|
} from "@/service/agenda-setting/agenda-setting";
|
||||||
|
|
@ -52,6 +52,10 @@ import { Icon } from "@iconify/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { UnitMapping } from "./unit-mapping";
|
import { UnitMapping } from "./unit-mapping";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import WavesurferPlayer from "@wavesurfer/react";
|
||||||
|
import WaveSurfer from "wavesurfer.js";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
title: z.string().min(3, { message: "Required" }),
|
title: z.string().min(3, { message: "Required" }),
|
||||||
|
|
@ -80,15 +84,16 @@ const EventModal = ({
|
||||||
event: any;
|
event: any;
|
||||||
selectedDate: any;
|
selectedDate: any;
|
||||||
}) => {
|
}) => {
|
||||||
|
const [detail, setDetail] = useState<any>();
|
||||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
const [endDate, setEndDate] = useState<Date>(new Date());
|
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||||
const [isPending, startTransition] = React.useTransition();
|
const [isPending, startTransition] = React.useTransition();
|
||||||
const [agendaType, setAgendaType] = React.useState<any>(categories[0].value);
|
|
||||||
const [listDest, setListDest] = useState([]);
|
const [listDest, setListDest] = useState([]);
|
||||||
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
|
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
|
||||||
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
|
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||||
|
|
@ -129,11 +134,16 @@ const EventModal = ({
|
||||||
semua: false,
|
semua: false,
|
||||||
nasional: false,
|
nasional: false,
|
||||||
polda: false,
|
polda: false,
|
||||||
|
polres: false,
|
||||||
satker: false,
|
satker: false,
|
||||||
internasional: false,
|
international: false,
|
||||||
});
|
});
|
||||||
const [selectedPolda, setSelectedPolda] = React.useState([]);
|
const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType
|
||||||
|
const [selectedPolda, setSelectedPolda] = React.useState([]); // Untuk data Polda
|
||||||
const [selectedSatker, setSelectedSatker] = React.useState([]);
|
const [selectedSatker, setSelectedSatker] = React.useState([]);
|
||||||
|
const [selectedPolres, setSelectedPolres] = React.useState([]);
|
||||||
|
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|
@ -153,6 +163,14 @@ const EventModal = ({
|
||||||
const detail = res?.data?.data;
|
const detail = res?.data?.data;
|
||||||
setDetailData(detail);
|
setDetailData(detail);
|
||||||
|
|
||||||
|
const description = detail?.description;
|
||||||
|
console.log("description", res?.data?.description);
|
||||||
|
|
||||||
|
// Set nilai awal description ke form control
|
||||||
|
if (description) {
|
||||||
|
setValue("description", description);
|
||||||
|
}
|
||||||
|
|
||||||
const attachments = detail?.attachments;
|
const attachments = detail?.attachments;
|
||||||
setImageUploadedFiles(
|
setImageUploadedFiles(
|
||||||
attachments?.filter((file: any) => file.fileTypeId == 1)
|
attachments?.filter((file: any) => file.fileTypeId == 1)
|
||||||
|
|
@ -169,7 +187,7 @@ const EventModal = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchDetailData();
|
fetchDetailData();
|
||||||
}, [event]);
|
}, [event, setValue]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// async function fetchPoldaPolres() {
|
// async function fetchPoldaPolres() {
|
||||||
|
|
@ -226,22 +244,32 @@ const EventModal = ({
|
||||||
setWilayahPublish((prev: any) => {
|
setWilayahPublish((prev: any) => {
|
||||||
const newState = { ...prev, [key]: !prev[key] };
|
const newState = { ...prev, [key]: !prev[key] };
|
||||||
|
|
||||||
// Handle "semua" logic to uncheck other options
|
// Handle "semua" logic to check all options
|
||||||
if (key === "semua" && newState.semua) {
|
if (key === "semua" && newState.semua) {
|
||||||
|
setAgendaType("all");
|
||||||
return {
|
return {
|
||||||
semua: true,
|
semua: true,
|
||||||
nasional: false,
|
nasional: true,
|
||||||
polda: false,
|
polda: true,
|
||||||
satker: false,
|
polres: true,
|
||||||
internasional: false,
|
satker: true,
|
||||||
|
international: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncheck "semua" if any other is selected
|
// Uncheck "semua" if any other option is selected
|
||||||
if (key !== "semua") {
|
if (key !== "semua") {
|
||||||
newState.semua = false;
|
newState.semua = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set agendaType based on the selected checkbox
|
||||||
|
if (newState.nasional) setAgendaType("mabes");
|
||||||
|
else if (newState.polda) setAgendaType("polda");
|
||||||
|
else if (newState.polres) setAgendaType("polres");
|
||||||
|
else if (newState.satker) setAgendaType("satker");
|
||||||
|
else if (newState.international) setAgendaType("international");
|
||||||
|
else setAgendaType(""); // Reset if no checkbox is selected
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -251,12 +279,15 @@ const EventModal = ({
|
||||||
if (wilayahPublish.semua) publishTo.push("all");
|
if (wilayahPublish.semua) publishTo.push("all");
|
||||||
if (wilayahPublish.nasional) publishTo.push("mabes");
|
if (wilayahPublish.nasional) publishTo.push("mabes");
|
||||||
if (wilayahPublish.polda) publishTo.push(...selectedPolda);
|
if (wilayahPublish.polda) publishTo.push(...selectedPolda);
|
||||||
|
if (wilayahPublish.polres) publishTo.push(...selectedPolres);
|
||||||
if (wilayahPublish.satker) publishTo.push(...selectedSatker);
|
if (wilayahPublish.satker) publishTo.push(...selectedSatker);
|
||||||
if (wilayahPublish.internasional) publishTo.push("internasional");
|
if (wilayahPublish.international) publishTo.push("international");
|
||||||
|
|
||||||
const reqData = {
|
const reqData = {
|
||||||
|
id: detailData?.id,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
|
agendaType, // Include agendaType in request
|
||||||
publishTo,
|
publishTo,
|
||||||
startDate: format(startDate, "yyyy-MM-dd"),
|
startDate: format(startDate, "yyyy-MM-dd"),
|
||||||
endDate: format(endDate, "yyyy-MM-dd"),
|
endDate: format(endDate, "yyyy-MM-dd"),
|
||||||
|
|
@ -273,50 +304,40 @@ const EventModal = ({
|
||||||
const id = response?.data?.data.id;
|
const id = response?.data?.data.id;
|
||||||
|
|
||||||
loading();
|
loading();
|
||||||
if (imageFiles?.length == 0) {
|
if (imageFiles?.length === 0) {
|
||||||
setIsImageUploadFinish(true);
|
setIsImageUploadFinish(true);
|
||||||
}
|
}
|
||||||
imageFiles?.map(async (item: any, index: number) => {
|
imageFiles?.map(async (item: any, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "1", "0");
|
await uploadResumableFile(index, String(id), item, "1", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (videoFiles?.length == 0) {
|
if (videoFiles?.length === 0) {
|
||||||
setIsVideoUploadFinish(true);
|
setIsVideoUploadFinish(true);
|
||||||
}
|
}
|
||||||
videoFiles?.map(async (item: any, index: number) => {
|
videoFiles?.map(async (item: any, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "2", "0");
|
await uploadResumableFile(index, String(id), item, "2", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (textFiles?.length == 0) {
|
if (textFiles?.length === 0) {
|
||||||
setIsTextUploadFinish(true);
|
setIsTextUploadFinish(true);
|
||||||
}
|
}
|
||||||
textFiles?.map(async (item: any, index: number) => {
|
textFiles?.map(async (item: any, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "3", "0");
|
await uploadResumableFile(index, String(id), item, "3", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (audioFiles?.length == 0) {
|
if (audioFiles?.length === 0) {
|
||||||
setIsAudioUploadFinish(true);
|
setIsAudioUploadFinish(true);
|
||||||
}
|
}
|
||||||
audioFiles?.map(async (item: any, index: number) => {
|
audioFiles?.map(async (item: any, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
await uploadResumableFile(index, String(id), item, "4", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Optional: Use Swal for success feedback
|
|
||||||
// MySwal.fire({
|
|
||||||
// title: "Sukses",
|
|
||||||
// text: "Data berhasil disimpan.",
|
|
||||||
// icon: "success",
|
|
||||||
// confirmButtonColor: "#3085d6",
|
|
||||||
// confirmButtonText: "OK",
|
|
||||||
// }).then(() => {
|
|
||||||
// router.push("en/contributor/agenda-setting");
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
if (
|
if (
|
||||||
(wilayahPublish.polda && selectedPolda.length === 0) ||
|
(wilayahPublish.polda && selectedPolda.length === 0) ||
|
||||||
(wilayahPublish.satker && selectedSatker.length === 0)
|
(wilayahPublish.satker && selectedSatker.length === 0) ||
|
||||||
|
(wilayahPublish.polres && selectedPolres.length === 0)
|
||||||
) {
|
) {
|
||||||
toast({
|
toast({
|
||||||
title: "Pilih ID untuk Polda/Satker",
|
title: "Pilih ID untuk Polda/Satker",
|
||||||
|
|
@ -524,6 +545,60 @@ const EventModal = ({
|
||||||
|
|
||||||
const handleRemoveFile = (id: number) => {};
|
const handleRemoveFile = (id: number) => {};
|
||||||
|
|
||||||
|
async function doDelete(id: any) {
|
||||||
|
loading();
|
||||||
|
const resDelete = await deleteAgendaSettings(id);
|
||||||
|
if (resDelete?.error) {
|
||||||
|
error(resDelete.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
successSubmitDelete("/in/contributor/agenda-setting");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data?",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function successSubmitDelete(redirect: string) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
if (redirect === window.location.pathname) {
|
||||||
|
fetch(redirect, { method: "GET", cache: "reload" }).then(() => {
|
||||||
|
console.log("Data diperbarui.");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.location.href = redirect;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReady = (ws: any) => {
|
||||||
|
setWavesurfer(ws);
|
||||||
|
setIsPlaying(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPlayPause = () => {
|
||||||
|
wavesurfer && wavesurfer.playPause();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DeleteConfirmationDialog
|
<DeleteConfirmationDialog
|
||||||
|
|
@ -645,7 +720,7 @@ const EventModal = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="wilayahPublish">Jenis Agenda</Label>
|
<Label htmlFor="wilayahPublish">Jenis Agenda</Label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="semua"
|
id="semua"
|
||||||
|
|
@ -685,6 +760,25 @@ const EventModal = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
id="polres"
|
||||||
|
checked={wilayahPublish.polres}
|
||||||
|
onCheckedChange={() => toggleWilayah("polres")}
|
||||||
|
/>
|
||||||
|
<label htmlFor="polres" className="ml-2">
|
||||||
|
Polres
|
||||||
|
</label>
|
||||||
|
{wilayahPublish.polres && (
|
||||||
|
<UnitMapping
|
||||||
|
isDetail={false}
|
||||||
|
unit="Polres"
|
||||||
|
sendDataToParent={(data: any) =>
|
||||||
|
setSelectedPolres(data)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="satker"
|
id="satker"
|
||||||
|
|
@ -706,11 +800,11 @@ const EventModal = ({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="internasional"
|
id="international"
|
||||||
checked={wilayahPublish.internasional}
|
checked={wilayahPublish.international}
|
||||||
onCheckedChange={() => toggleWilayah("internasional")}
|
onCheckedChange={() => toggleWilayah("international")}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="internasional" className="ml-2">
|
<label htmlFor="international" className="ml-2">
|
||||||
Internasional
|
Internasional
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -743,33 +837,6 @@ const EventModal = ({
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>Video</Label>
|
<Label>Video</Label>
|
||||||
{videoUploadedFiles?.map((file: any, index: number) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
|
||||||
>
|
|
||||||
<div className="flex gap-3 items-center">
|
|
||||||
<div className="file-preview">
|
|
||||||
{renderFilePreview(file.url)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className=" text-sm text-card-foreground">
|
|
||||||
{file.fileName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="destructive"
|
|
||||||
variant="outline"
|
|
||||||
className=" border-none rounded-full"
|
|
||||||
onClick={() => handleRemoveFile(file)}
|
|
||||||
>
|
|
||||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"mp4/*": [],
|
"mp4/*": [],
|
||||||
|
|
@ -777,38 +844,46 @@ const EventModal = ({
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp4 atau .mov."
|
label="Upload file dengan format .mp4 atau .mov."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setVideoFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
{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>
|
||||||
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<Label>Foto</Label>
|
<Label>Foto</Label>
|
||||||
{imageUploadedFiles?.map((file: any, index: number) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
|
||||||
>
|
|
||||||
<div className="flex gap-3 items-center">
|
|
||||||
<div className="file-preview">
|
|
||||||
{renderFilePreview(file.url)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className=" text-sm text-card-foreground">
|
|
||||||
{file.fileName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="destructive"
|
|
||||||
variant="outline"
|
|
||||||
className=" border-none rounded-full"
|
|
||||||
onClick={() => handleRemoveFile(file)}
|
|
||||||
>
|
|
||||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"image/*": [],
|
"image/*": [],
|
||||||
|
|
@ -817,36 +892,46 @@ const EventModal = ({
|
||||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setImageFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
{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>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Teks</Label>
|
<Label>Teks</Label>
|
||||||
{textUploadedFiles?.map((file: any, index: number) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
|
||||||
>
|
|
||||||
<div className="flex gap-3 items-center">
|
|
||||||
<div className="file-preview">
|
|
||||||
{renderFilePreview(file.url)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className=" text-sm text-card-foreground">
|
|
||||||
{file.fileName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="destructive"
|
|
||||||
variant="outline"
|
|
||||||
className=" border-none rounded-full"
|
|
||||||
onClick={() => handleRemoveFile(file)}
|
|
||||||
>
|
|
||||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"pdf/*": [],
|
"pdf/*": [],
|
||||||
|
|
@ -855,36 +940,46 @@ const EventModal = ({
|
||||||
label="Upload file dengan format .pdf."
|
label="Upload file dengan format .pdf."
|
||||||
onDrop={(files) => setTextFiles(files)}
|
onDrop={(files) => setTextFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
{textUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Voice Note</Label>
|
<Label>Voice Note</Label>
|
||||||
{audioUploadedFiles?.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>
|
|
||||||
))}
|
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -904,6 +999,67 @@ const EventModal = ({
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) => setAudioFiles(files)}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
|
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<div key={file.id}>
|
||||||
|
<WavesurferPlayer
|
||||||
|
height={500}
|
||||||
|
waveColor="red"
|
||||||
|
url={file.url}
|
||||||
|
onReady={onReady}
|
||||||
|
onPlay={() => setIsPlaying(true)}
|
||||||
|
onPause={() => setIsPlaying(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
onClick={onPlayPause}
|
||||||
|
disabled={isPlaying}
|
||||||
|
className={`flex items-center gap-2 ${
|
||||||
|
isPlaying
|
||||||
|
? "bg-gray-300 cursor-not-allowed"
|
||||||
|
: "bg-primary text-white"
|
||||||
|
} p-2 rounded`}
|
||||||
|
>
|
||||||
|
{isPlaying ? "Pause" : "Play"}
|
||||||
|
<Icon
|
||||||
|
icon={
|
||||||
|
isPlaying
|
||||||
|
? "carbon:pause-outline"
|
||||||
|
: "famicons:play-sharp"
|
||||||
|
}
|
||||||
|
className="h-5 w-5"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className=" text-sm text-card-foreground">
|
||||||
|
{file.fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="destructive"
|
||||||
|
variant="outline"
|
||||||
|
className=" border-none rounded-full"
|
||||||
|
onClick={() => handleRemoveFile(file)}
|
||||||
|
>
|
||||||
|
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFile && (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
|
@ -938,6 +1094,13 @@ const EventModal = ({
|
||||||
"Simpan Agenda Setting"
|
"Simpan Agenda Setting"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="flex-1 bg-red-500 text-white"
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleDelete(event?.event?.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
{event?.length > 1 && (
|
{event?.length > 1 && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
|
|
@ -35,11 +36,11 @@ const FormSchema = z.object({
|
||||||
interface UnitType {
|
interface UnitType {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
subDestination: { id: number; name: string }[];
|
subDestination: { id: number; name: string }[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UnitMapping(props: {
|
export function UnitMapping(props: {
|
||||||
unit: string;
|
unit: "Polda" | "Satker" | "Polres";
|
||||||
sendDataToParent: (data: string[]) => void;
|
sendDataToParent: (data: string[]) => void;
|
||||||
isDetail: boolean;
|
isDetail: boolean;
|
||||||
initData?: string[];
|
initData?: string[];
|
||||||
|
|
@ -49,7 +50,11 @@ export function UnitMapping(props: {
|
||||||
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
|
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
|
@ -61,6 +66,7 @@ export function UnitMapping(props: {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
const response = await getUserLevelForAssignments();
|
const response = await getUserLevelForAssignments();
|
||||||
setupUnit(response?.data?.data.list);
|
setupUnit(response?.data?.data.list);
|
||||||
|
console.log("list", response?.data?.data.list);
|
||||||
}
|
}
|
||||||
|
|
||||||
initState();
|
initState();
|
||||||
|
|
@ -74,12 +80,17 @@ export function UnitMapping(props: {
|
||||||
const isAllSatkerChecked = satkerList.every((item) =>
|
const isAllSatkerChecked = satkerList.every((item) =>
|
||||||
unitType?.includes(String(item.id))
|
unitType?.includes(String(item.id))
|
||||||
);
|
);
|
||||||
|
const isAllPolresChecked = polresList.every((item) =>
|
||||||
|
unitType?.includes(String(item.id))
|
||||||
|
);
|
||||||
|
|
||||||
const setupUnit = (data: UnitType[]) => {
|
const setupUnit = (data: UnitType[]) => {
|
||||||
const temp = data.filter((a) => a.name.includes("POLDA"));
|
const temp = data.filter((a) => a.name.includes("POLDA"));
|
||||||
const temp2 = data.filter((a) => a.name.includes("SATKER"));
|
const temp2 = data.filter((a) => a.name.includes("SATKER"));
|
||||||
|
const temp3 = temp.flatMap((item) => item.subDestination || []);
|
||||||
setUnitList(temp);
|
setUnitList(temp);
|
||||||
setSatkerList(temp2[0].subDestination);
|
setSatkerList(temp2[0]?.subDestination || []);
|
||||||
|
setPolresList(temp3);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -96,157 +107,97 @@ export function UnitMapping(props: {
|
||||||
Pilih {unit}
|
Pilih {unit}
|
||||||
</a>
|
</a>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent size="md">
|
<DialogContent size="md" className="h-[500px] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{unit}</DialogTitle>
|
<DialogTitle>{unit}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{unit === "Polda" ? (
|
<Form {...form}>
|
||||||
<Form {...form}>
|
<form className="flex flex-col gap-2">
|
||||||
<form className="flex flex-col gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-3">
|
<Checkbox
|
||||||
<Checkbox
|
id={`all-${unit}`}
|
||||||
id={`all-${unit}`}
|
checked={
|
||||||
checked={isAllUnitChecked}
|
unit === "Polda"
|
||||||
disabled={isDetail}
|
? isAllUnitChecked
|
||||||
onCheckedChange={(checked) => {
|
: unit === "Satker"
|
||||||
if (checked) {
|
? isAllSatkerChecked
|
||||||
form.setValue(
|
: isAllPolresChecked
|
||||||
"items",
|
}
|
||||||
unitList.map((item) => String(item.id))
|
disabled={isDetail}
|
||||||
);
|
onCheckedChange={(checked) => {
|
||||||
} else {
|
if (checked) {
|
||||||
form.setValue("items", []);
|
form.setValue(
|
||||||
}
|
"items",
|
||||||
}}
|
unit === "Polda"
|
||||||
/>
|
? unitList.map((item) => String(item.id))
|
||||||
<label htmlFor="all" className="text-sm text-black uppercase">
|
: unit === "Satker"
|
||||||
SEMUA {unit}
|
? satkerList.map((item) => String(item.id))
|
||||||
</label>
|
: polresList.map((item) => String(item.id))
|
||||||
</div>
|
);
|
||||||
<FormField
|
} else {
|
||||||
control={form.control}
|
form.setValue("items", []);
|
||||||
name="items"
|
}
|
||||||
render={() => (
|
}}
|
||||||
<FormItem
|
|
||||||
className={`grid grid-cols-${unit === "Polda" ? "2" : "3"}`}
|
|
||||||
>
|
|
||||||
{unitList?.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>
|
<label htmlFor="all" className="text-sm text-black uppercase">
|
||||||
</Form>
|
SEMUA {unit}
|
||||||
) : (
|
</label>
|
||||||
<Form {...form}>
|
</div>
|
||||||
<form className="flex flex-col gap-2">
|
<FormField
|
||||||
<div className="flex items-center gap-3">
|
control={form.control}
|
||||||
<Checkbox
|
name="items"
|
||||||
id={`all-${unit}`}
|
render={() => (
|
||||||
checked={isAllSatkerChecked}
|
<FormItem
|
||||||
disabled={isDetail}
|
className={`grid grid-cols-${
|
||||||
onCheckedChange={(checked) => {
|
unit === "Polda" ? "2" : unit === "Satker" ? "3" : "4"
|
||||||
if (checked) {
|
}`}
|
||||||
form.setValue(
|
>
|
||||||
"items",
|
{(unit === "Polda"
|
||||||
satkerList.map((item) => String(item.id))
|
? unitList
|
||||||
);
|
: unit === "Satker"
|
||||||
} else {
|
? satkerList
|
||||||
form.setValue("items", []);
|
: polresList
|
||||||
}
|
)?.map((item: any) => (
|
||||||
}}
|
<FormField
|
||||||
/>
|
key={item.id}
|
||||||
<label htmlFor="all" className="text-sm text-black uppercase">
|
control={form.control}
|
||||||
SEMUA {unit}
|
name="items"
|
||||||
</label>
|
render={({ field }) => {
|
||||||
</div>
|
return (
|
||||||
<FormField
|
<FormItem
|
||||||
control={form.control}
|
key={String(item.id)}
|
||||||
name="items"
|
className="flex flex-row items-center space-x-3 space-y-0"
|
||||||
render={() => (
|
>
|
||||||
<FormItem
|
<FormControl>
|
||||||
className={`grid grid-cols-${unit === "Polda" ? "2" : "3"}`}
|
<Checkbox
|
||||||
>
|
disabled={isDetail}
|
||||||
{satkerList?.map((item: any) => (
|
checked={field.value?.includes(String(item.id))}
|
||||||
<FormField
|
onCheckedChange={(checked) => {
|
||||||
key={item.id}
|
return checked
|
||||||
control={form.control}
|
? field.onChange([
|
||||||
name="items"
|
...field.value,
|
||||||
render={({ field }) => {
|
String(item.id),
|
||||||
return (
|
])
|
||||||
<FormItem
|
: field.onChange(
|
||||||
key={String(item.id)}
|
field.value?.filter(
|
||||||
className="flex flex-row items-center space-x-3 space-y-0"
|
(value) => value !== String(item.id)
|
||||||
>
|
)
|
||||||
<FormControl>
|
);
|
||||||
<Checkbox
|
}}
|
||||||
disabled={isDetail}
|
/>
|
||||||
checked={field.value?.includes(
|
</FormControl>
|
||||||
String(item.id)
|
<p className="text-sm text-black">{item.name}</p>
|
||||||
)}
|
</FormItem>
|
||||||
onCheckedChange={(checked) => {
|
);
|
||||||
return checked
|
}}
|
||||||
? field.onChange([
|
/>
|
||||||
...field.value,
|
))}
|
||||||
String(item.id),
|
<FormMessage />
|
||||||
])
|
</FormItem>
|
||||||
: field.onChange(
|
)}
|
||||||
field.value?.filter(
|
/>
|
||||||
(value) => value !== String(item.id)
|
</form>
|
||||||
)
|
</Form>
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<p className="text-sm text-black">{item.name}</p>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { deleteMedia } from "@/service/content/content";
|
||||||
|
import { error } from "@/lib/swal";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -135,6 +139,51 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
async function doDelete(id: any) {
|
||||||
|
// loading();
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await deleteMedia(data);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteMedia = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -159,7 +208,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||||
|
>
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,14 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { deleteMedia } from "@/service/content/content";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -131,6 +139,52 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
async function doDelete(id: any) {
|
||||||
|
// loading();
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await deleteMedia(data);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteMedia = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -155,7 +209,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||||
|
>
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,18 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import columns from "./columns";
|
import columns from "./columns";
|
||||||
import { listDataImage } from "@/service/content/content";
|
import { deleteMedia, listDataImage } from "@/service/content/content";
|
||||||
|
import { loading } from "@/config/swal";
|
||||||
|
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { error } from "@/lib/swal";
|
||||||
|
|
||||||
const TableImage = () => {
|
const TableImage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
|
@ -161,6 +167,37 @@ const TableImage = () => {
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// async function doDelete(id: any) {
|
||||||
|
// loading();
|
||||||
|
// const data = {
|
||||||
|
// id,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const response = await deleteMedia(data);
|
||||||
|
|
||||||
|
// if (response?.error) {
|
||||||
|
// error(response.message);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fetchData();
|
||||||
|
// toast.success("Konten berhasil dihapus");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const handleDeleteMedia = (id: any) => {
|
||||||
|
// MySwal.fire({
|
||||||
|
// title: "Apakah anda ingin menghapus konten?",
|
||||||
|
// showCancelButton: true,
|
||||||
|
// confirmButtonColor: "#dc3545",
|
||||||
|
// confirmButtonText: "Iya",
|
||||||
|
// cancelButtonText: "Tidak",
|
||||||
|
// }).then((result: any) => {
|
||||||
|
// if (result.isConfirmed) {
|
||||||
|
// doDelete(id);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto">
|
<div className="w-full overflow-x-auto">
|
||||||
<div className="flex justify-between items-center px-5">
|
<div className="flex justify-between items-center px-5">
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
|
import { error } from "@/lib/swal";
|
||||||
|
import { deleteMedia } from "@/service/content/content";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -136,6 +140,51 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
async function doDelete(id: any) {
|
||||||
|
// loading();
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await deleteMedia(data);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteMedia = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -160,7 +209,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||||
|
>
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
|
import { deleteMedia } from "@/service/content/content";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { error } from "@/lib/swal";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -136,6 +140,51 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
async function doDelete(id: any) {
|
||||||
|
// loading();
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await deleteMedia(data);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteMedia = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -160,7 +209,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleDeleteMedia(row.original.id)}
|
||||||
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||||
|
>
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,13 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { deleteCategory } from "@/service/settings/settings";
|
||||||
|
import { deleteTask } from "@/service/task";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -115,6 +122,49 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
async function deleteProcess(id: any) {
|
||||||
|
loading();
|
||||||
|
const resDelete = await deleteTask(id);
|
||||||
|
|
||||||
|
if (resDelete?.error) {
|
||||||
|
error(resDelete.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskDelete = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
deleteProcess(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -139,7 +189,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
<DropdownMenuItem
|
||||||
|
onClick={() => TaskDelete(row.original.id)}
|
||||||
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||||
|
>
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
createMedia,
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
|
|
@ -81,6 +82,11 @@ interface FileWithPreview extends File {
|
||||||
preview: string;
|
preview: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default function FormAudioUpdate() {
|
export default function FormAudioUpdate() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -111,10 +117,20 @@ export default function FormAudioUpdate() {
|
||||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||||
const [selectedTarget, setSelectedTarget] = useState("");
|
const [selectedTarget, setSelectedTarget] = useState("");
|
||||||
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedOptions, setSelectedOptions] = useState<{
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
[fileId: number]: string;
|
[fileId: number]: string;
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
|
const options: Option[] = [
|
||||||
|
{ id: "all", name: "SEMUA" },
|
||||||
|
{ id: "5", name: "UMUM" },
|
||||||
|
{ id: "6", name: "JOURNALIS" },
|
||||||
|
{ id: "7", name: "POLRI" },
|
||||||
|
{ id: "8", name: "KSP" },
|
||||||
|
];
|
||||||
|
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
mabes: false,
|
mabes: false,
|
||||||
|
|
@ -139,21 +155,6 @@ export default function FormAudioUpdate() {
|
||||||
resolver: zodResolver(audioSchema),
|
resolver: zodResolver(audioSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
// const handleKeyDown = (e: any) => {
|
|
||||||
// const newTag = e.target.value.trim(); // Ambil nilai input
|
|
||||||
// if (e.key === "Enter" && newTag) {
|
|
||||||
// e.preventDefault(); // Hentikan submit form
|
|
||||||
// if (!tags.includes(newTag)) {
|
|
||||||
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
|
||||||
// setValue("tags", ""); // Kosongkan input
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleRemoveTag = (index: any) => {
|
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (event.target.files) {
|
if (event.target.files) {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
|
|
@ -166,12 +167,6 @@ export default function FormAudioUpdate() {
|
||||||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
|
||||||
setSelectedPublishers((prev) =>
|
|
||||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
getCategories();
|
getCategories();
|
||||||
|
|
@ -205,6 +200,29 @@ export default function FormAudioUpdate() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newTag = e.currentTarget.value.trim();
|
||||||
|
if (!tags.includes(newTag)) {
|
||||||
|
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.value = ""; // Kosongkan input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveTag = (index: number) => {
|
||||||
|
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTag = (index: number, newValue: string) => {
|
||||||
|
setTags((prevTags) =>
|
||||||
|
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -217,11 +235,13 @@ export default function FormAudioUpdate() {
|
||||||
setFiles(details.files);
|
setFiles(details.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedFor) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
// Split string "7" to an array ["7"] if needed
|
||||||
(obj: any) => obj.id
|
setPublishedFor(details.publishedFor.split(","));
|
||||||
);
|
}
|
||||||
setSelectedPublishers(publisherIds);
|
|
||||||
|
if (details?.tags) {
|
||||||
|
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingCategory = categories.find(
|
const matchingCategory = categories.find(
|
||||||
|
|
@ -244,7 +264,25 @@ export default function FormAudioUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (id: string) => {
|
||||||
|
if (id === "all") {
|
||||||
|
// Select all options except "all"
|
||||||
|
const allOptions = options
|
||||||
|
.filter((opt) => opt.id !== "all")
|
||||||
|
.map((opt) => opt.id);
|
||||||
|
setPublishedFor(
|
||||||
|
publishedFor.length === allOptions.length ? [] : allOptions
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Toggle individual option
|
||||||
|
setPublishedFor((prev) =>
|
||||||
|
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const save = async (data: AudioSchema) => {
|
const save = async (data: AudioSchema) => {
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
id: detail?.id,
|
id: detail?.id,
|
||||||
|
|
@ -256,9 +294,9 @@ export default function FormAudioUpdate() {
|
||||||
subCategoryId: selectedTarget,
|
subCategoryId: selectedTarget,
|
||||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
statusId: "1",
|
statusId: "1",
|
||||||
publishedFor: "6",
|
publishedFor: publishedFor.join(","),
|
||||||
creatorName: data.creatorName,
|
creatorName: data.creatorName,
|
||||||
tags: "siap",
|
tags: finalTags,
|
||||||
isYoutube: false,
|
isYoutube: false,
|
||||||
isInternationalMedia: false,
|
isInternationalMedia: false,
|
||||||
};
|
};
|
||||||
|
|
@ -286,12 +324,7 @@ export default function FormAudioUpdate() {
|
||||||
close();
|
close();
|
||||||
// showProgress();
|
// showProgress();
|
||||||
files.map(async (item: any, index: number) => {
|
files.map(async (item: any, index: number) => {
|
||||||
await uploadResumableFile(
|
await uploadResumableFile(index, String(id), item, "0");
|
||||||
index,
|
|
||||||
String(id),
|
|
||||||
item,
|
|
||||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
|
|
@ -430,22 +463,24 @@ export default function FormAudioUpdate() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFile = (file: FileWithPreview) => {
|
// const handleRemoveFile = (file: FileWithPreview) => {
|
||||||
const uploadedFiles = files;
|
// const uploadedFiles = files;
|
||||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
// const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||||
setFiles([...filtered]);
|
// setFiles([...filtered]);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const fileList = files.map((file) => (
|
const fileList = files.map((file: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.name}
|
key={file.id} // Gunakan ID file sebagai key
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
<div className="text-sm text-card-foreground">
|
||||||
<div className=" text-xs font-light text-muted-foreground">
|
{file.fileName || file.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-light text-muted-foreground">
|
||||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -460,10 +495,10 @@ export default function FormAudioUpdate() {
|
||||||
size="icon"
|
size="icon"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className=" border-none rounded-full"
|
className="border-none rounded-full"
|
||||||
onClick={() => handleRemoveFile(file)}
|
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||||
>
|
>
|
||||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
@ -500,6 +535,55 @@ export default function FormAudioUpdate() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -606,12 +690,12 @@ export default function FormAudioUpdate() {
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
<Switch defaultChecked color="primary" id="c2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{/* <Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={handleRemoveAllFiles}
|
||||||
>
|
>
|
||||||
Remove All
|
Remove All
|
||||||
</Button>
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -752,14 +836,34 @@ export default function FormAudioUpdate() {
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Tag</Label>
|
<Label>Tag</Label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<Input
|
||||||
{detail?.tags?.split(",").map((tag, index) => (
|
type="text"
|
||||||
<Badge
|
id="tags"
|
||||||
|
placeholder="Add a tag and press Enter"
|
||||||
|
onKeyDown={handleAddTag}
|
||||||
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="border rounded-md px-2 py-2"
|
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||||
>
|
>
|
||||||
{tag.trim()}
|
<input
|
||||||
</Badge>
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||||
|
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
value={tag}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveTag(index)}
|
||||||
|
className="remove-tag-button text-white"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -767,38 +871,21 @@ export default function FormAudioUpdate() {
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<Label>Target Publish</Label>
|
<Label>Target Publish</Label>
|
||||||
<div className="flex gap-2 items-center">
|
{options.map((option: Option) => (
|
||||||
<Checkbox
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
id="5"
|
<Checkbox
|
||||||
checked={selectedPublishers.includes(5)}
|
id={option.id}
|
||||||
onChange={() => handleCheckboxChange(5)}
|
checked={
|
||||||
/>
|
option.id === "all"
|
||||||
<Label htmlFor="5">UMUM</Label>
|
? publishedFor.length ===
|
||||||
</div>
|
options.filter((opt) => opt.id !== "all").length
|
||||||
<div className="flex gap-2 items-center">
|
: publishedFor.includes(option.id)
|
||||||
<Checkbox
|
}
|
||||||
id="6"
|
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||||
checked={selectedPublishers.includes(6)}
|
/>
|
||||||
onChange={() => handleCheckboxChange(6)}
|
<Label htmlFor={option.id}>{option.name}</Label>
|
||||||
/>
|
</div>
|
||||||
<Label htmlFor="6">JOURNALIS</Label>
|
))}
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="7"
|
|
||||||
checked={selectedPublishers.includes(7)}
|
|
||||||
onChange={() => handleCheckboxChange(7)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="7">POLRI</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="8"
|
|
||||||
checked={selectedPublishers.includes(8)}
|
|
||||||
onChange={() => handleCheckboxChange(8)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="8">KSP</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
createMedia,
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
|
deleteMedia,
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
|
|
@ -42,7 +44,7 @@ import dynamic from "next/dynamic";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { error } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
import { Upload } from "tus-js-client";
|
import { Upload } from "tus-js-client";
|
||||||
|
|
||||||
|
|
@ -65,7 +67,13 @@ type Detail = {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
categoryId: {
|
category: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
publishedFor: string;
|
||||||
|
|
||||||
|
publishedForObject: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
@ -75,6 +83,11 @@ type Detail = {
|
||||||
tags: string;
|
tags: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
const CustomEditor = dynamic(
|
const CustomEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/custom-editor");
|
return import("@/components/editor/custom-editor");
|
||||||
|
|
@ -122,7 +135,17 @@ export default function FormImageUpdate() {
|
||||||
[fileId: number]: string;
|
[fileId: number]: string;
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
const [selectedTarget, setSelectedTarget] = useState("");
|
const options: Option[] = [
|
||||||
|
{ id: "all", name: "SEMUA" },
|
||||||
|
{ id: "5", name: "UMUM" },
|
||||||
|
{ id: "6", name: "JOURNALIS" },
|
||||||
|
{ id: "7", name: "POLRI" },
|
||||||
|
{ id: "8", name: "KSP" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||||
|
detail?.category.id
|
||||||
|
);
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
mabes: false,
|
mabes: false,
|
||||||
|
|
@ -158,10 +181,6 @@ export default function FormImageUpdate() {
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const handleRemoveTag = (index: any) => {
|
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (event.target.files) {
|
if (event.target.files) {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
|
|
@ -174,11 +193,11 @@ export default function FormImageUpdate() {
|
||||||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
// const handleCheckboxChange = (id: number) => {
|
||||||
setSelectedPublishers((prev) =>
|
// setSelectedPublishers((prev) =>
|
||||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
|
|
@ -193,14 +212,24 @@ export default function FormImageUpdate() {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newTag = e.currentTarget.value.trim();
|
const newTag = e.currentTarget.value.trim();
|
||||||
if (!tags.includes(newTag)) {
|
if (!tags.includes(newTag)) {
|
||||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.value = ""; // Clear input field
|
inputRef.current.value = ""; // Kosongkan input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRemoveTag = (index: number) => {
|
||||||
|
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTag = (index: number, newValue: string) => {
|
||||||
|
setTags((prevTags) =>
|
||||||
|
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getCategories = async () => {
|
const getCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const category = await listEnableCategory(fileTypeId);
|
const category = await listEnableCategory(fileTypeId);
|
||||||
|
|
@ -238,11 +267,13 @@ export default function FormImageUpdate() {
|
||||||
setFiles(details.files);
|
setFiles(details.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedFor) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
// Split string "7" to an array ["7"] if needed
|
||||||
(obj: any) => obj.id
|
setPublishedFor(details.publishedFor.split(","));
|
||||||
);
|
}
|
||||||
setSelectedPublishers(publisherIds);
|
|
||||||
|
if (details?.tags) {
|
||||||
|
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingCategory = categories.find(
|
const matchingCategory = categories.find(
|
||||||
|
|
@ -259,7 +290,32 @@ export default function FormImageUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
|
// const handleCheckboxChange = (id: number) => {
|
||||||
|
// setSelectedPublishers((prev) =>
|
||||||
|
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleCheckboxChange = (id: string) => {
|
||||||
|
if (id === "all") {
|
||||||
|
// Select all options except "all"
|
||||||
|
const allOptions = options
|
||||||
|
.filter((opt) => opt.id !== "all")
|
||||||
|
.map((opt) => opt.id);
|
||||||
|
setPublishedFor(
|
||||||
|
publishedFor.length === allOptions.length ? [] : allOptions
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Toggle individual option
|
||||||
|
setPublishedFor((prev) =>
|
||||||
|
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const save = async (data: ImageSchema) => {
|
const save = async (data: ImageSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
id: detail?.id,
|
id: detail?.id,
|
||||||
|
|
@ -271,9 +327,9 @@ export default function FormImageUpdate() {
|
||||||
subCategoryId: selectedTarget,
|
subCategoryId: selectedTarget,
|
||||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
statusId: "1",
|
statusId: "1",
|
||||||
publishedFor: "6",
|
publishedFor: publishedFor.join(","),
|
||||||
creatorName: data.creatorName,
|
creatorName: data.creatorName,
|
||||||
tags: "siap",
|
tags: finalTags,
|
||||||
isYoutube: false,
|
isYoutube: false,
|
||||||
isInternationalMedia: false,
|
isInternationalMedia: false,
|
||||||
};
|
};
|
||||||
|
|
@ -451,16 +507,18 @@ export default function FormImageUpdate() {
|
||||||
setFiles([...filtered]);
|
setFiles([...filtered]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileList = files.map((file) => (
|
const fileList = files.map((file: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.name}
|
key={file.id} // Gunakan ID file sebagai key
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
<div className="text-sm text-card-foreground">
|
||||||
<div className=" text-xs font-light text-muted-foreground">
|
{file.fileName || file.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-light text-muted-foreground">
|
||||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -475,10 +533,10 @@ export default function FormImageUpdate() {
|
||||||
size="icon"
|
size="icon"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className=" border-none rounded-full"
|
className="border-none rounded-full"
|
||||||
onClick={() => handleRemoveFile(file)}
|
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||||
>
|
>
|
||||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
@ -515,6 +573,55 @@ export default function FormImageUpdate() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -549,14 +656,17 @@ export default function FormImageUpdate() {
|
||||||
<div className="py-3 w-full">
|
<div className="py-3 w-full">
|
||||||
<Label>Kategori</Label>
|
<Label>Kategori</Label>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
|
||||||
onValueChange={(id) => {
|
onValueChange={(id) => {
|
||||||
console.log("Selected Category:", id);
|
console.log("Selected Category ID:", id);
|
||||||
setSelectedTarget(id);
|
setSelectedTarget(id); // Perbarui ID kategori
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih">
|
||||||
|
{categories.find((cat) => cat.id === selectedTarget)
|
||||||
|
?.name || "Pilih"}
|
||||||
|
</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
|
|
@ -619,12 +729,12 @@ export default function FormImageUpdate() {
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
<Switch defaultChecked color="primary" id="c2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{/* <Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={() => handleDeleteFile(id)}
|
||||||
>
|
>
|
||||||
Remove All
|
Remove file
|
||||||
</Button>
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -782,24 +892,30 @@ export default function FormImageUpdate() {
|
||||||
onKeyDown={handleAddTag}
|
onKeyDown={handleAddTag}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
<div className="mt-3 ">
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
{tags.map((tag, index) => (
|
{tags.map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||||
>
|
>
|
||||||
{tag}{" "}
|
<input
|
||||||
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||||
|
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
|
value={tag}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleRemoveTag(index)}
|
onClick={() => handleRemoveTag(index)}
|
||||||
className="remove-tag-button"
|
className="remove-tag-button text-white"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
{/* <div className="flex flex-wrap gap-2">
|
||||||
{detail?.tags?.split(",").map((tag, index) => (
|
{detail?.tags?.split(",").map((tag, index) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -808,44 +924,27 @@ export default function FormImageUpdate() {
|
||||||
{tag.trim()}
|
{tag.trim()}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<Label>Target Publish</Label>
|
<Label>Target Publish</Label>
|
||||||
<div className="flex gap-2 items-center">
|
{options.map((option: Option) => (
|
||||||
<Checkbox
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
id="5"
|
<Checkbox
|
||||||
checked={selectedPublishers.includes(5)}
|
id={option.id}
|
||||||
onChange={() => handleCheckboxChange(5)}
|
checked={
|
||||||
/>
|
option.id === "all"
|
||||||
<Label htmlFor="5">UMUM</Label>
|
? publishedFor.length ===
|
||||||
</div>
|
options.filter((opt) => opt.id !== "all").length
|
||||||
<div className="flex gap-2 items-center">
|
: publishedFor.includes(option.id)
|
||||||
<Checkbox
|
}
|
||||||
id="6"
|
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||||
checked={selectedPublishers.includes(6)}
|
/>
|
||||||
onChange={() => handleCheckboxChange(6)}
|
<Label htmlFor={option.id}>{option.name}</Label>
|
||||||
/>
|
</div>
|
||||||
<Label htmlFor="6">JOURNALIS</Label>
|
))}
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="7"
|
|
||||||
checked={selectedPublishers.includes(7)}
|
|
||||||
onChange={() => handleCheckboxChange(7)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="7">POLRI</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="8"
|
|
||||||
checked={selectedPublishers.includes(8)}
|
|
||||||
onChange={() => handleCheckboxChange(8)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="8">KSP</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { error } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import { Upload } from "tus-js-client";
|
import { Upload } from "tus-js-client";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
|
||||||
|
|
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
let fileTypeId = "3";
|
let fileTypeId = "3";
|
||||||
|
|
||||||
|
|
@ -164,10 +165,6 @@ export default function FormTeksUpdate() {
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const handleRemoveTag = (index: any) => {
|
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (event.target.files) {
|
if (event.target.files) {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
|
|
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
|
||||||
setFiles(details.files);
|
setFiles(details.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedFor) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
// Split string "7" to an array ["7"] if needed
|
||||||
(obj: any) => obj.id
|
setPublishedFor(details.publishedFor.split(","));
|
||||||
);
|
}
|
||||||
setSelectedPublishers(publisherIds);
|
|
||||||
|
if (details?.tags) {
|
||||||
|
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingCategory = categories.find(
|
const matchingCategory = categories.find(
|
||||||
|
|
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
const handleCheckboxChange = (id: string): void => {
|
const handleCheckboxChange = (id: string) => {
|
||||||
if (id === "all") {
|
if (id === "all") {
|
||||||
if (publishedFor.includes("all")) {
|
// Select all options except "all"
|
||||||
// Uncheck all checkboxes
|
const allOptions = options
|
||||||
setPublishedFor([]);
|
.filter((opt) => opt.id !== "all")
|
||||||
} else {
|
.map((opt) => opt.id);
|
||||||
// Select all checkboxes
|
setPublishedFor(
|
||||||
setPublishedFor(
|
publishedFor.length === allOptions.length ? [] : allOptions
|
||||||
options
|
);
|
||||||
.filter((opt: any) => opt.id !== "all")
|
|
||||||
.map((opt: any) => opt.id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const updatedPublishedFor = publishedFor.includes(id)
|
// Toggle individual option
|
||||||
? publishedFor.filter((item) => item !== id)
|
setPublishedFor((prev) =>
|
||||||
: [...publishedFor, id];
|
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
);
|
||||||
// Remove "all" if any checkbox is unchecked
|
|
||||||
if (publishedFor.includes("all") && id !== "all") {
|
|
||||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
|
||||||
} else {
|
|
||||||
setPublishedFor(updatedPublishedFor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = async (data: TeksSchema) => {
|
const save = async (data: TeksSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
id: detail?.id,
|
id: detail?.id,
|
||||||
|
|
@ -293,7 +284,7 @@ export default function FormTeksUpdate() {
|
||||||
statusId: "1",
|
statusId: "1",
|
||||||
publishedFor: publishedFor.join(","),
|
publishedFor: publishedFor.join(","),
|
||||||
creatorName: data.creatorName,
|
creatorName: data.creatorName,
|
||||||
tags: "siap",
|
tags: finalTags,
|
||||||
isYoutube: false,
|
isYoutube: false,
|
||||||
isInternationalMedia: false,
|
isInternationalMedia: false,
|
||||||
};
|
};
|
||||||
|
|
@ -535,6 +526,29 @@ export default function FormTeksUpdate() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newTag = e.currentTarget.value.trim();
|
||||||
|
if (!tags.includes(newTag)) {
|
||||||
|
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.value = ""; // Kosongkan input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveTag = (index: number) => {
|
||||||
|
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTag = (index: number, newValue: string) => {
|
||||||
|
setTags((prevTags) =>
|
||||||
|
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -785,7 +799,7 @@ export default function FormTeksUpdate() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 px-3">
|
{/* <div className="mt-3 px-3">
|
||||||
<Label>Pratinjau Gambar Utama</Label>
|
<Label>Pratinjau Gambar Utama</Label>
|
||||||
<Card className="mt-2">
|
<Card className="mt-2">
|
||||||
<img
|
<img
|
||||||
|
|
@ -794,18 +808,38 @@ export default function FormTeksUpdate() {
|
||||||
className="w-full h-auto rounded"
|
className="w-full h-auto rounded"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Tag</Label>
|
<Label>Tag</Label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<Input
|
||||||
{detail?.tags?.split(",").map((tag, index) => (
|
type="text"
|
||||||
<Badge
|
id="tags"
|
||||||
|
placeholder="Add a tag and press Enter"
|
||||||
|
onKeyDown={handleAddTag}
|
||||||
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="border rounded-md px-2 py-2"
|
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||||
>
|
>
|
||||||
{tag.trim()}
|
<input
|
||||||
</Badge>
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||||
|
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
value={tag}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveTag(index)}
|
||||||
|
className="remove-tag-button text-white"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -817,7 +851,6 @@ export default function FormTeksUpdate() {
|
||||||
<div key={option.id} className="flex gap-2 items-center">
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={option.id}
|
id={option.id}
|
||||||
value={detail?.publishedForObject.name}
|
|
||||||
checked={
|
checked={
|
||||||
option.id === "all"
|
option.id === "all"
|
||||||
? publishedFor.length ===
|
? publishedFor.length ===
|
||||||
|
|
|
||||||
|
|
@ -531,7 +531,7 @@ export default function FormVideo() {
|
||||||
const csrfToken = resCsrf?.data?.token;
|
const csrfToken = resCsrf?.data?.token;
|
||||||
console.log("CSRF TOKEN : ", csrfToken);
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
const headers = {
|
const headers = {
|
||||||
"X-XSRF-TOKEN": csrfToken
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
const upload = new Upload(file, {
|
const upload = new Upload(file, {
|
||||||
|
|
@ -544,11 +544,11 @@ export default function FormVideo() {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
filetype: file.type,
|
filetype: file.type,
|
||||||
duration,
|
duration,
|
||||||
isWatermark: "false", // hardcode
|
isWatermark: "true", // hardcode
|
||||||
},
|
},
|
||||||
onBeforeRequest: function (req) {
|
onBeforeRequest: function (req) {
|
||||||
var xhr = req.getUnderlyingObject()
|
var xhr = req.getUnderlyingObject();
|
||||||
xhr.withCredentials = true
|
xhr.withCredentials = true;
|
||||||
},
|
},
|
||||||
onError: async (e: any) => {
|
onError: async (e: any) => {
|
||||||
console.log("Error upload :", e);
|
console.log("Error upload :", e);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
createMedia,
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
uploadThumbnail,
|
uploadThumbnail,
|
||||||
|
|
@ -53,7 +54,7 @@ import Image from "next/image";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { Upload } from "tus-js-client";
|
import { Upload } from "tus-js-client";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
import { error } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
|
|
||||||
const videoSchema = z.object({
|
const videoSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -88,6 +89,11 @@ interface FileWithPreview extends File {
|
||||||
preview: string;
|
preview: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default function FormVideoUpdate() {
|
export default function FormVideoUpdate() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -126,11 +132,20 @@ export default function FormVideoUpdate() {
|
||||||
polda: false,
|
polda: false,
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedOptions, setSelectedOptions] = useState<{
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
[fileId: number]: string;
|
[fileId: number]: string;
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
|
const options: Option[] = [
|
||||||
|
{ id: "all", name: "SEMUA" },
|
||||||
|
{ id: "5", name: "UMUM" },
|
||||||
|
{ id: "6", name: "JOURNALIS" },
|
||||||
|
{ id: "7", name: "POLRI" },
|
||||||
|
{ id: "8", name: "KSP" },
|
||||||
|
];
|
||||||
|
|
||||||
let fileTypeId = "2";
|
let fileTypeId = "2";
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
|
@ -159,10 +174,6 @@ export default function FormVideoUpdate() {
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const handleRemoveTag = (index: any) => {
|
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (event.target.files) {
|
if (event.target.files) {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
|
|
@ -175,12 +186,6 @@ export default function FormVideoUpdate() {
|
||||||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
|
||||||
setSelectedPublishers((prev) =>
|
|
||||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
getCategories();
|
getCategories();
|
||||||
|
|
@ -189,6 +194,29 @@ export default function FormVideoUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newTag = e.currentTarget.value.trim();
|
||||||
|
if (!tags.includes(newTag)) {
|
||||||
|
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.value = ""; // Kosongkan input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveTag = (index: number) => {
|
||||||
|
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTag = (index: number, newValue: string) => {
|
||||||
|
setTags((prevTags) =>
|
||||||
|
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getCategories = async () => {
|
const getCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const category = await listEnableCategory(fileTypeId);
|
const category = await listEnableCategory(fileTypeId);
|
||||||
|
|
@ -226,11 +254,13 @@ export default function FormVideoUpdate() {
|
||||||
setFiles(details.files);
|
setFiles(details.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedFor) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
// Split string "7" to an array ["7"] if needed
|
||||||
(obj: any) => obj.id
|
setPublishedFor(details.publishedFor.split(","));
|
||||||
);
|
}
|
||||||
setSelectedPublishers(publisherIds);
|
|
||||||
|
if (details?.tags) {
|
||||||
|
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingCategory = categories.find(
|
const matchingCategory = categories.find(
|
||||||
|
|
@ -253,7 +283,26 @@ export default function FormVideoUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (id: string) => {
|
||||||
|
if (id === "all") {
|
||||||
|
// Select all options except "all"
|
||||||
|
const allOptions = options
|
||||||
|
.filter((opt) => opt.id !== "all")
|
||||||
|
.map((opt) => opt.id);
|
||||||
|
setPublishedFor(
|
||||||
|
publishedFor.length === allOptions.length ? [] : allOptions
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Toggle individual option
|
||||||
|
setPublishedFor((prev) =>
|
||||||
|
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const save = async (data: VideoSchema) => {
|
const save = async (data: VideoSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
id: detail?.id,
|
id: detail?.id,
|
||||||
|
|
@ -265,9 +314,9 @@ export default function FormVideoUpdate() {
|
||||||
subCategoryId: selectedTarget,
|
subCategoryId: selectedTarget,
|
||||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
statusId: "1",
|
statusId: "1",
|
||||||
publishedFor: "6",
|
publishedFor: publishedFor.join(","),
|
||||||
creatorName: data.creatorName,
|
creatorName: data.creatorName,
|
||||||
tags: "siap",
|
tags: finalTags,
|
||||||
isYoutube: false,
|
isYoutube: false,
|
||||||
isInternationalMedia: false,
|
isInternationalMedia: false,
|
||||||
};
|
};
|
||||||
|
|
@ -295,32 +344,33 @@ export default function FormVideoUpdate() {
|
||||||
close();
|
close();
|
||||||
// showProgress();
|
// showProgress();
|
||||||
files.map(async (item: any, index: number) => {
|
files.map(async (item: any, index: number) => {
|
||||||
await uploadResumableFile(
|
await uploadResumableFile(index, String(id), item, "0");
|
||||||
index,
|
|
||||||
String(id),
|
|
||||||
item,
|
|
||||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
MySwal.fire({
|
// MySwal.fire({
|
||||||
title: "Sukses",
|
// title: "Sukses",
|
||||||
text: "Data berhasil disimpan.",
|
// text: "Data berhasil disimpan.",
|
||||||
icon: "success",
|
// icon: "success",
|
||||||
confirmButtonColor: "#3085d6",
|
// confirmButtonColor: "#3085d6",
|
||||||
confirmButtonText: "OK",
|
// confirmButtonText: "OK",
|
||||||
}).then(() => {
|
// }).then(() => {
|
||||||
router.push("/en/contributor/content/image");
|
// router.push("/en/contributor/content/video");
|
||||||
});
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (data: VideoSchema) => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Simpan Data",
|
||||||
text: "Data berhasil disimpan.",
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
icon: "success",
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
confirmButtonColor: "#3085d6",
|
confirmButtonColor: "#3085d6",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "Simpan",
|
||||||
}).then(() => {
|
}).then((result) => {
|
||||||
router.push("/en/contributor/content/video");
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -385,22 +435,6 @@ export default function FormVideoUpdate() {
|
||||||
upload.start();
|
upload.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = (data: VideoSchema) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Simpan Data",
|
|
||||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonColor: "#d33",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "Simpan",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
save(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const successSubmit = (redirect: string) => {
|
const successSubmit = (redirect: string) => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Sukses",
|
||||||
|
|
@ -454,16 +488,18 @@ export default function FormVideoUpdate() {
|
||||||
setFiles([...filtered]);
|
setFiles([...filtered]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileList = files.map((file) => (
|
const fileList = files.map((file: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.name}
|
key={file.id} // Gunakan ID file sebagai key
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
<div className="text-sm text-card-foreground">
|
||||||
<div className=" text-xs font-light text-muted-foreground">
|
{file.fileName || file.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-light text-muted-foreground">
|
||||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -478,10 +514,10 @@ export default function FormVideoUpdate() {
|
||||||
size="icon"
|
size="icon"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className=" border-none rounded-full"
|
className="border-none rounded-full"
|
||||||
onClick={() => handleRemoveFile(file)}
|
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||||
>
|
>
|
||||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
@ -518,6 +554,55 @@ export default function FormVideoUpdate() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -626,12 +711,12 @@ export default function FormVideoUpdate() {
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
<Switch defaultChecked color="primary" id="c2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{/* <Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={handleRemoveAllFiles}
|
||||||
>
|
>
|
||||||
Remove All
|
Remove All
|
||||||
</Button>
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -782,14 +867,34 @@ export default function FormVideoUpdate() {
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Tag</Label>
|
<Label>Tag</Label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<Input
|
||||||
{detail?.tags?.split(",").map((tag, index) => (
|
type="text"
|
||||||
<Badge
|
id="tags"
|
||||||
|
placeholder="Add a tag and press Enter"
|
||||||
|
onKeyDown={handleAddTag}
|
||||||
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="border rounded-md px-2 py-2"
|
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||||
>
|
>
|
||||||
{tag.trim()}
|
<input
|
||||||
</Badge>
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||||
|
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
value={tag}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveTag(index)}
|
||||||
|
className="remove-tag-button text-white"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -797,38 +902,21 @@ export default function FormVideoUpdate() {
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<Label>Target Publish</Label>
|
<Label>Target Publish</Label>
|
||||||
<div className="flex gap-2 items-center">
|
{options.map((option: Option) => (
|
||||||
<Checkbox
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
id="5"
|
<Checkbox
|
||||||
checked={selectedPublishers.includes(5)}
|
id={option.id}
|
||||||
onChange={() => handleCheckboxChange(5)}
|
checked={
|
||||||
/>
|
option.id === "all"
|
||||||
<Label htmlFor="5">UMUM</Label>
|
? publishedFor.length ===
|
||||||
</div>
|
options.filter((opt) => opt.id !== "all").length
|
||||||
<div className="flex gap-2 items-center">
|
: publishedFor.includes(option.id)
|
||||||
<Checkbox
|
}
|
||||||
id="6"
|
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||||
checked={selectedPublishers.includes(6)}
|
/>
|
||||||
onChange={() => handleCheckboxChange(6)}
|
<Label htmlFor={option.id}>{option.name}</Label>
|
||||||
/>
|
</div>
|
||||||
<Label htmlFor="6">JOURNALIS</Label>
|
))}
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="7"
|
|
||||||
checked={selectedPublishers.includes(7)}
|
|
||||||
onChange={() => handleCheckboxChange(7)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="7">POLRI</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="8"
|
|
||||||
checked={selectedPublishers.includes(8)}
|
|
||||||
onChange={() => handleCheckboxChange(8)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="8">KSP</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { successCallback } from "@/config/swal";
|
import { successCallback } from "@/config/swal";
|
||||||
import FileUploader from "../shared/file-uploader";
|
import FileUploader from "../shared/file-uploader";
|
||||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import WavesurferPlayer from "@wavesurfer/react";
|
||||||
|
import WaveSurfer from "wavesurfer.js";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -162,6 +166,11 @@ interface UploadResult {
|
||||||
creatorName: string;
|
creatorName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FileUploaded {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function FormTaskDetail() {
|
export default function FormTaskDetail() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -191,6 +200,7 @@ export default function FormTaskDetail() {
|
||||||
const [type, setType] = useState<string>("1");
|
const [type, setType] = useState<string>("1");
|
||||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||||
const [detail, setDetail] = useState<taskDetail>();
|
const [detail, setDetail] = useState<taskDetail>();
|
||||||
|
const [urlInputs, setUrlInputs] = useState<string[]>([]);
|
||||||
const [refresh] = useState(false);
|
const [refresh] = useState(false);
|
||||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||||
|
|
@ -224,6 +234,22 @@ export default function FormTaskDetail() {
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
const [timer, setTimer] = useState<number>(120);
|
const [timer, setTimer] = useState<number>(120);
|
||||||
|
|
||||||
|
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
|
||||||
|
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
|
|
@ -283,32 +309,60 @@ export default function FormTaskDetail() {
|
||||||
setUploadResults(details.uploadResults || []);
|
setUploadResults(details.uploadResults || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (details?.urls) {
|
||||||
|
// Extract attachmentUrl from each object in urls
|
||||||
|
const urls = details.urls.map(
|
||||||
|
(urlObj: any) => urlObj.attachmentUrl || ""
|
||||||
|
);
|
||||||
|
setUrlInputs(urls); // Save the URLs to state
|
||||||
|
}
|
||||||
|
|
||||||
if (details?.assignedToLevel) {
|
if (details?.assignedToLevel) {
|
||||||
const levels = new Set(
|
const levels = new Set(
|
||||||
details.assignedToLevel.split(",").map(Number)
|
details.assignedToLevel.split(",").map(Number)
|
||||||
);
|
);
|
||||||
setCheckedLevels(levels);
|
setCheckedLevels(levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attachment = details?.files;
|
||||||
|
setImageUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||||
|
);
|
||||||
|
setVideoUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 2)
|
||||||
|
);
|
||||||
|
setTextUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 3)
|
||||||
|
);
|
||||||
|
setAudioUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 4)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
}, [id, refresh]);
|
}, [id, refresh]);
|
||||||
|
|
||||||
|
const handleUrlChange = (index: number, newUrl: string) => {
|
||||||
|
setUrlInputs((prev: any) =>
|
||||||
|
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (detail?.broadcastType) {
|
if (detail?.broadcastType) {
|
||||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
setBroadcastType(detail.broadcastType);
|
||||||
}
|
}
|
||||||
}, [detail?.broadcastType]);
|
}, [detail?.broadcastType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (detail?.fileTypeOutput) {
|
if (detail?.fileTypeOutput) {
|
||||||
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
|
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number));
|
||||||
setTaskOutput({
|
setTaskOutput({
|
||||||
all: outputSet.has(0),
|
all: outputSet.has(0),
|
||||||
video: outputSet.has(2),
|
video: outputSet.has(2),
|
||||||
audio: outputSet.has(4),
|
audio: outputSet.has(4),
|
||||||
image: outputSet.has(1),
|
image: outputSet.has(1),
|
||||||
text: outputSet.has(3),
|
text: outputSet.has(5),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [detail?.fileTypeOutput]);
|
}, [detail?.fileTypeOutput]);
|
||||||
|
|
@ -327,71 +381,6 @@ export default function FormTaskDetail() {
|
||||||
}
|
}
|
||||||
}, [detail?.fileTypeOutput]);
|
}, [detail?.fileTypeOutput]);
|
||||||
|
|
||||||
const save = async (data: TaskSchema) => {
|
|
||||||
const fileTypeMapping = {
|
|
||||||
all: "1",
|
|
||||||
video: "2",
|
|
||||||
audio: "3",
|
|
||||||
image: "4",
|
|
||||||
text: "5",
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedOutputs = Object.keys(taskOutput)
|
|
||||||
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
|
|
||||||
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
|
|
||||||
.join(",");
|
|
||||||
|
|
||||||
const requestData = {
|
|
||||||
...data,
|
|
||||||
// assignmentType,
|
|
||||||
// assignmentCategory,
|
|
||||||
target: selectedTarget,
|
|
||||||
unitSelection,
|
|
||||||
assignedToRole: "3",
|
|
||||||
taskType: taskType,
|
|
||||||
broadcastType: broadcastType,
|
|
||||||
assignmentMainTypeId: mainType,
|
|
||||||
assignmentPurpose: "1",
|
|
||||||
assignmentTypeId: type,
|
|
||||||
fileTypeOutput: selectedOutputs,
|
|
||||||
id: null,
|
|
||||||
narration: data.naration,
|
|
||||||
platformType: "",
|
|
||||||
title: data.title,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await createTask(requestData);
|
|
||||||
|
|
||||||
console.log("Form Data Submitted:", requestData);
|
|
||||||
console.log("response", response);
|
|
||||||
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Sukses",
|
|
||||||
text: "Data berhasil disimpan.",
|
|
||||||
icon: "success",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "OK",
|
|
||||||
}).then(() => {
|
|
||||||
router.push("/en/contributor/task");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = (data: TaskSchema) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Simpan Data",
|
|
||||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonColor: "#d33",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "Simpan",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
save(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const successConfirm = () => {
|
const successConfirm = () => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Sukses",
|
||||||
|
|
@ -445,21 +434,6 @@ export default function FormTaskDetail() {
|
||||||
setMessage(e.target.value);
|
setMessage(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleSubmitResponse = (): void => {
|
|
||||||
// if (response.trim()) {
|
|
||||||
// const newResponse: Response = {
|
|
||||||
// id: Date.now(), // Unique ID for each response
|
|
||||||
// name: "Mabes Polri - Approver",
|
|
||||||
// content: response,
|
|
||||||
// timestamp: new Date().toLocaleString("id-ID"), // Format timestamp for Indonesia locale
|
|
||||||
// };
|
|
||||||
|
|
||||||
// setResponses([newResponse, ...responses]); // Add new response to the top
|
|
||||||
// setResponse(""); // Reset textarea
|
|
||||||
// setShowInput(false); // Hide input
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const postData = () => {
|
const postData = () => {
|
||||||
sendSuggestionParent();
|
sendSuggestionParent();
|
||||||
};
|
};
|
||||||
|
|
@ -527,29 +501,6 @@ export default function FormTaskDetail() {
|
||||||
console.log(dataId);
|
console.log(dataId);
|
||||||
};
|
};
|
||||||
|
|
||||||
// async function sendSuggestionChild(parentId: any) {
|
|
||||||
// const msg = document.querySelectorAll(`#input-comment-${parentId}`)[0]
|
|
||||||
// .value;
|
|
||||||
|
|
||||||
// if (msg?.length > 1) {
|
|
||||||
// loading();
|
|
||||||
// const data = {
|
|
||||||
// assignmentId: id,
|
|
||||||
// message: msg,
|
|
||||||
// parentId,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// console.log(data);
|
|
||||||
// const response = await createAssignmentResponse(data);
|
|
||||||
// console.log(response);
|
|
||||||
// const responseGet = await getAssignmentResponseList(id);
|
|
||||||
// console.log(responseGet?.data?.data);
|
|
||||||
// setListData(responseGet?.data?.data);
|
|
||||||
// // $(":input").val("");
|
|
||||||
// close();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function getDataAcceptance() {
|
async function getDataAcceptance() {
|
||||||
const response = await getAcceptanceAssignmentStatus(id);
|
const response = await getAcceptanceAssignmentStatus(id);
|
||||||
setStatusAcceptance(response?.data?.data?.isAccept);
|
setStatusAcceptance(response?.data?.data?.isAccept);
|
||||||
|
|
@ -752,6 +703,29 @@ export default function FormTaskDetail() {
|
||||||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderFilePreview = (url: string) => {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
alt={"file preview"}
|
||||||
|
src={url}
|
||||||
|
className=" rounded border p-0.5"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReady = (ws: any) => {
|
||||||
|
setWavesurfer(ws);
|
||||||
|
setIsPlaying(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPlayPause = () => {
|
||||||
|
wavesurfer && wavesurfer.playPause();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFile = (id: number) => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -802,7 +776,7 @@ export default function FormTaskDetail() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Kode Unik</Label>
|
<Label>Kode Unik</Label>
|
||||||
|
|
@ -1033,27 +1007,7 @@ export default function FormTaskDetail() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="mt-6">
|
|
||||||
<Label>Broadcast </Label>
|
|
||||||
<RadioGroup
|
|
||||||
value={broadcastType}
|
|
||||||
onValueChange={(value) => setBroadcastType(value)}
|
|
||||||
className="flex flex-wrap gap-3"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<RadioGroupItem value="all" id="all" />
|
|
||||||
<Label htmlFor="all">Semua</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<RadioGroupItem value="email" id="email" />
|
|
||||||
<Label htmlFor="email">Email Blast</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<RadioGroupItem value="whatsapp" id="whatsapp" />
|
|
||||||
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</div> */}
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Label>Narasi Penugasan</Label>
|
<Label>Narasi Penugasan</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -1063,61 +1017,222 @@ export default function FormTaskDetail() {
|
||||||
<ViewEditor initialData={detail?.narration} />
|
<ViewEditor initialData={detail?.narration} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.naration?.message && (
|
{/* {errors.naration?.message && (
|
||||||
<p className="text-red-400 text-sm">
|
<p className="text-red-400 text-sm">
|
||||||
{errors.naration.message}
|
{errors.naration.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5 mt-5">
|
<div className="space-y-1.5 mt-5">
|
||||||
<Label htmlFor="attachments">Lampiran</Label>
|
<Label htmlFor="attachment">Lampiran</Label>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>Video</Label>
|
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
||||||
<FileUploader
|
{videoUploadedFiles?.map((file: any, index: number) => (
|
||||||
accept={{
|
<div>
|
||||||
"mp4/*": [],
|
<video
|
||||||
"mov/*": [],
|
className="object-fill h-full w-full rounded-md"
|
||||||
}}
|
src={file.url}
|
||||||
maxSize={100}
|
controls
|
||||||
label="Upload file dengan format .mp4 atau .mov."
|
title={`Video ${file.id}`} // Mengganti alt dengan title
|
||||||
onDrop={(files) => setImageFiles(files)}
|
/>
|
||||||
/>
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div 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>
|
||||||
<div>
|
<div>
|
||||||
<Label>Foto</Label>
|
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
|
||||||
<FileUploader
|
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<Card className="mt-2">
|
||||||
|
<img
|
||||||
|
src={file.url}
|
||||||
|
alt="Thumbnail Gambar Utama"
|
||||||
|
className="w-full h-auto rounded-md"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className=" text-sm text-card-foreground">
|
||||||
|
{file.fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="destructive"
|
||||||
|
variant="outline"
|
||||||
|
className=" border-none rounded-full"
|
||||||
|
onClick={() => handleRemoveFile(file)}
|
||||||
|
>
|
||||||
|
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{/* <FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"image/*": [],
|
"image/*": [],
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setImageFiles(files)}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Teks</Label>
|
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
|
||||||
<FileUploader
|
{textUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<iframe
|
||||||
|
className="w-full h-96 rounded-md"
|
||||||
|
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||||
|
file.url
|
||||||
|
)}`}
|
||||||
|
title={file.fileName || "Document"}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className=" text-sm text-card-foreground">
|
||||||
|
{file.fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="destructive"
|
||||||
|
variant="outline"
|
||||||
|
className=" border-none rounded-full"
|
||||||
|
onClick={() => handleRemoveFile(file)}
|
||||||
|
>
|
||||||
|
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{/* <FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"pdf/*": [],
|
"pdf/*": [],
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .pdf."
|
label="Upload file dengan format .pdf."
|
||||||
onDrop={(files) => setTextFiles(files)}
|
onDrop={(files) => setTextFiles(files)}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Voice Note</Label>
|
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
|
||||||
<AudioRecorder
|
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||||
onRecordingComplete={addAudioElement}
|
<div>
|
||||||
audioTrackConstraints={{
|
<div key={file.id}>
|
||||||
noiseSuppression: true,
|
<WavesurferPlayer
|
||||||
echoCancellation: true,
|
height={500}
|
||||||
}}
|
waveColor="red"
|
||||||
downloadOnSavePress={true}
|
url={file.url}
|
||||||
downloadFileExtension="webm"
|
onReady={onReady}
|
||||||
/>
|
onPlay={() => setIsPlaying(true)}
|
||||||
<FileUploader
|
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={{
|
||||||
|
noiseSuppression: true,
|
||||||
|
echoCancellation: true,
|
||||||
|
}}
|
||||||
|
downloadOnSavePress={true}
|
||||||
|
downloadFileExtension="webm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* <FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"mp3/*": [],
|
"mp3/*": [],
|
||||||
"wav/*": [],
|
"wav/*": [],
|
||||||
|
|
@ -1126,7 +1241,7 @@ export default function FormTaskDetail() {
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
label="Upload file dengan format .mp3 atau .wav."
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) => setAudioFiles(files)}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFile && (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
|
@ -1144,14 +1259,20 @@ export default function FormTaskDetail() {
|
||||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||||
{/* Display remaining time */}
|
{/* Display remaining time */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
{urlInputs.map((url: any, index: any) => (
|
||||||
<Input
|
<div className="mt-4 flex flex-col" key={index}>
|
||||||
id="voiceNoteLink"
|
<Label htmlFor={`url-${index}`}>Link Berita</Label>
|
||||||
type="url"
|
<Button
|
||||||
placeholder="Masukkan link voice note"
|
size="sm"
|
||||||
value={voiceNoteLink}
|
id={`url-${index}`}
|
||||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
className="bg-slate-400 text-white py-2 px-4 rounded mt-2 hover:bg-blue-600 text-start"
|
||||||
/>
|
onClick={() => window.open(url, "_blank")}
|
||||||
|
disabled={!url} // Disable button if URL is empty
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1159,21 +1280,20 @@ export default function FormTaskDetail() {
|
||||||
<div className="flex flex-row justify-between gap-3 my-3">
|
<div className="flex flex-row justify-between gap-3 my-3">
|
||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
{detail?.isDone !== true &&
|
{detail?.isDone !== true &&
|
||||||
(Number(userLevelNumber) !== 3 ||
|
(Number(userLevelNumber) !== 3 ||
|
||||||
Number(userLevelNumber) == 2) ? (
|
Number(userLevelNumber) == 2) ? (
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-outline-primary ml-3"
|
className="btn btn-outline-primary ml-3"
|
||||||
onClick={() => handleForward()}
|
onClick={() => handleForward()}
|
||||||
>
|
>
|
||||||
Forward
|
Forward
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="">
|
<div className="">
|
||||||
|
|
@ -1189,7 +1309,8 @@ export default function FormTaskDetail() {
|
||||||
<Button
|
<Button
|
||||||
className="btn btn-primary ml-3 mr-3"
|
className="btn btn-primary ml-3 mr-3"
|
||||||
style={
|
style={
|
||||||
statusAcceptance || detail?.createdBy?.id !== Number(userId)
|
statusAcceptance ||
|
||||||
|
detail?.createdBy?.id !== Number(userId)
|
||||||
? {
|
? {
|
||||||
display: "none",
|
display: "none",
|
||||||
}
|
}
|
||||||
|
|
@ -1211,6 +1332,7 @@ export default function FormTaskDetail() {
|
||||||
// }
|
// }
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant={"default"}
|
variant={"default"}
|
||||||
onClick={() => setIsTableResult(!isTableResult)}
|
onClick={() => setIsTableResult(!isTableResult)}
|
||||||
|
|
@ -1402,10 +1524,14 @@ export default function FormTaskDetail() {
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<div
|
<div
|
||||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||||
onClick={() => deleteData(child2.id)}
|
onClick={() =>
|
||||||
|
deleteData(child2.id)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-4 h-4" />
|
<TrashIcon className="w-4 h-4" />
|
||||||
<span className="ml-1">Delete</span>
|
<span className="ml-1">
|
||||||
|
Delete
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ import {
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import FileUploader from "../shared/file-uploader";
|
import FileUploader from "../shared/file-uploader";
|
||||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import WavesurferPlayer from "@wavesurfer/react";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -62,6 +68,7 @@ export type taskDetail = {
|
||||||
taskType: string;
|
taskType: string;
|
||||||
broadcastType: string;
|
broadcastType: string;
|
||||||
narration: string;
|
narration: string;
|
||||||
|
attachmentUrl: string;
|
||||||
is_active: string;
|
is_active: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -69,6 +76,16 @@ interface FileWithPreview extends File {
|
||||||
preview: string;
|
preview: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FileUploaded {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Url = {
|
||||||
|
id: number;
|
||||||
|
attachmentUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default function FormTaskEdit() {
|
export default function FormTaskEdit() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -101,6 +118,7 @@ export default function FormTaskEdit() {
|
||||||
const [type, setType] = useState<string>("1");
|
const [type, setType] = useState<string>("1");
|
||||||
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
||||||
const [detail, setDetail] = useState<taskDetail>();
|
const [detail, setDetail] = useState<taskDetail>();
|
||||||
|
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
|
||||||
const [refresh] = useState(false);
|
const [refresh] = useState(false);
|
||||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||||
|
|
@ -110,6 +128,19 @@ export default function FormTaskEdit() {
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
const [timer, setTimer] = useState<number>(120);
|
const [timer, setTimer] = useState<number>(120);
|
||||||
|
|
||||||
|
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
|
|
@ -118,6 +149,8 @@ export default function FormTaskEdit() {
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [links, setLinks] = useState<string[]>([""]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|
@ -165,6 +198,11 @@ export default function FormTaskEdit() {
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
|
|
||||||
|
if (details?.urls) {
|
||||||
|
// Save the URLs as objects
|
||||||
|
setUrlInputs(details.urls);
|
||||||
|
}
|
||||||
|
|
||||||
if (details?.assignedToLevel) {
|
if (details?.assignedToLevel) {
|
||||||
const levels = new Set(
|
const levels = new Set(
|
||||||
details.assignedToLevel.split(",").map(Number)
|
details.assignedToLevel.split(",").map(Number)
|
||||||
|
|
@ -172,12 +210,32 @@ export default function FormTaskEdit() {
|
||||||
setCheckedLevels(levels);
|
setCheckedLevels(levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attachment = details?.files;
|
||||||
|
setImageUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||||
|
);
|
||||||
|
setVideoUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 2)
|
||||||
|
);
|
||||||
|
setTextUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 3)
|
||||||
|
);
|
||||||
|
setAudioUploadedFiles(
|
||||||
|
attachment?.filter((file: any) => file.fileTypeId == 4)
|
||||||
|
);
|
||||||
|
|
||||||
// Add more state setting here based on other fields like broadcastType, taskOutput, etc.
|
// Add more state setting here based on other fields like broadcastType, taskOutput, etc.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
}, [id, refresh]);
|
}, [id, refresh]);
|
||||||
|
|
||||||
|
const handleUrlChange = (index: number, newUrl: string) => {
|
||||||
|
setUrlInputs((prev: any) =>
|
||||||
|
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (detail?.broadcastType) {
|
if (detail?.broadcastType) {
|
||||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
||||||
|
|
@ -192,7 +250,7 @@ export default function FormTaskEdit() {
|
||||||
video: outputSet.has(2),
|
video: outputSet.has(2),
|
||||||
audio: outputSet.has(4),
|
audio: outputSet.has(4),
|
||||||
image: outputSet.has(1),
|
image: outputSet.has(1),
|
||||||
text: outputSet.has(3),
|
text: outputSet.has(5),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [detail?.fileTypeOutput]);
|
}, [detail?.fileTypeOutput]);
|
||||||
|
|
@ -265,6 +323,7 @@ export default function FormTaskEdit() {
|
||||||
taskType: string;
|
taskType: string;
|
||||||
assignedToRole: string;
|
assignedToRole: string;
|
||||||
broadcastType: string;
|
broadcastType: string;
|
||||||
|
attachmentUrl: string[];
|
||||||
} = {
|
} = {
|
||||||
...data,
|
...data,
|
||||||
// assignmentType,
|
// assignmentType,
|
||||||
|
|
@ -281,22 +340,52 @@ export default function FormTaskEdit() {
|
||||||
narration: data.naration,
|
narration: data.naration,
|
||||||
platformType: "",
|
platformType: "",
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
attachmentUrl: links,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await createTask(requestData);
|
const response = await createTask(requestData);
|
||||||
|
|
||||||
console.log("Form Data Submitted:", requestData);
|
console.log("Form Data Submitted:", requestData);
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
|
const id = response?.data?.data.id;
|
||||||
MySwal.fire({
|
loading();
|
||||||
title: "Sukses",
|
if (imageFiles?.length == 0) {
|
||||||
text: "Data berhasil disimpan.",
|
setIsImageUploadFinish(true);
|
||||||
icon: "success",
|
}
|
||||||
confirmButtonColor: "#3085d6",
|
imageFiles?.map(async (item: any, index: number) => {
|
||||||
confirmButtonText: "OK",
|
await uploadResumableFile(index, String(id), item, "1", "0");
|
||||||
}).then(() => {
|
|
||||||
router.push("/en/contributor/task");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (videoFiles?.length == 0) {
|
||||||
|
setIsVideoUploadFinish(true);
|
||||||
|
}
|
||||||
|
videoFiles?.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(index, String(id), item, "2", "0");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (textFiles?.length == 0) {
|
||||||
|
setIsTextUploadFinish(true);
|
||||||
|
}
|
||||||
|
textFiles?.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(index, String(id), item, "3", "0");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (audioFiles?.length == 0) {
|
||||||
|
setIsAudioUploadFinish(true);
|
||||||
|
}
|
||||||
|
audioFiles?.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(index, String(id), item, "4", "0");
|
||||||
|
});
|
||||||
|
|
||||||
|
// MySwal.fire({
|
||||||
|
// title: "Sukses",
|
||||||
|
// text: "Data berhasil disimpan.",
|
||||||
|
// icon: "success",
|
||||||
|
// confirmButtonColor: "#3085d6",
|
||||||
|
// confirmButtonText: "OK",
|
||||||
|
// }).then(() => {
|
||||||
|
// router.push("/en/contributor/task");
|
||||||
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data: TaskSchema) => {
|
const onSubmit = (data: TaskSchema) => {
|
||||||
|
|
@ -322,24 +411,6 @@ export default function FormTaskEdit() {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAudioElement = (blob: Blob) => {
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const audio = document.createElement("audio");
|
|
||||||
audio.src = url;
|
|
||||||
audio.controls = true;
|
|
||||||
document.body.appendChild(audio);
|
|
||||||
|
|
||||||
// Convert Blob to File
|
|
||||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
|
||||||
setAudioFile(file);
|
|
||||||
};
|
|
||||||
const handleDeleteAudio = () => {
|
|
||||||
// Remove the audio file by setting state to null
|
|
||||||
setAudioFile(null);
|
|
||||||
const audioElements = document.querySelectorAll("audio");
|
|
||||||
audioElements.forEach((audio) => audio.remove());
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRecordingStart = () => {
|
const onRecordingStart = () => {
|
||||||
setIsRecording(true);
|
setIsRecording(true);
|
||||||
|
|
||||||
|
|
@ -365,6 +436,164 @@ export default function FormTaskEdit() {
|
||||||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addAudioElement = (blob: Blob) => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const audio = document.createElement("audio");
|
||||||
|
audio.src = url;
|
||||||
|
audio.controls = true;
|
||||||
|
document.body.appendChild(audio);
|
||||||
|
|
||||||
|
// Convert Blob to File
|
||||||
|
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
||||||
|
setAudioFile(file);
|
||||||
|
};
|
||||||
|
const handleDeleteAudio = () => {
|
||||||
|
// Remove the audio file by setting state to null
|
||||||
|
setAudioFile(null);
|
||||||
|
const audioElements = document.querySelectorAll("audio");
|
||||||
|
audioElements.forEach((audio) => audio.remove());
|
||||||
|
};
|
||||||
|
|
||||||
|
async function uploadResumableFile(
|
||||||
|
idx: number,
|
||||||
|
id: string,
|
||||||
|
file: any,
|
||||||
|
fileTypeId: string,
|
||||||
|
duration: string
|
||||||
|
) {
|
||||||
|
console.log(idx, id, file, fileTypeId, duration);
|
||||||
|
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = new Upload(file, {
|
||||||
|
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
|
||||||
|
headers: headers,
|
||||||
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
|
chunkSize: 20_000,
|
||||||
|
metadata: {
|
||||||
|
assignmentId: id,
|
||||||
|
filename: file.name,
|
||||||
|
contentType: file.type,
|
||||||
|
fileTypeId: fileTypeId,
|
||||||
|
duration,
|
||||||
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
},
|
||||||
|
onError: async (e: any) => {
|
||||||
|
console.log("Error upload :", e);
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
onChunkComplete: (
|
||||||
|
chunkSize: any,
|
||||||
|
bytesAccepted: any,
|
||||||
|
bytesTotal: any
|
||||||
|
) => {
|
||||||
|
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
|
// progressInfo[idx].percentage = uploadPersen;
|
||||||
|
// counterUpdateProgress++;
|
||||||
|
// console.log(counterUpdateProgress);
|
||||||
|
// setProgressList(progressInfo);
|
||||||
|
// setCounterProgress(counterUpdateProgress);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
// uploadPersen = 100;
|
||||||
|
// progressInfo[idx].percentage = 100;
|
||||||
|
// counterUpdateProgress++;
|
||||||
|
// setCounterProgress(counterUpdateProgress);
|
||||||
|
successTodo();
|
||||||
|
if (fileTypeId == "1") {
|
||||||
|
setIsImageUploadFinish(true);
|
||||||
|
} else if (fileTypeId == "2") {
|
||||||
|
setIsVideoUploadFinish(true);
|
||||||
|
}
|
||||||
|
if (fileTypeId == "3") {
|
||||||
|
setIsTextUploadFinish(true);
|
||||||
|
}
|
||||||
|
if (fileTypeId == "4") {
|
||||||
|
setIsAudioUploadFinish(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
successTodo();
|
||||||
|
}, [
|
||||||
|
isImageUploadFinish,
|
||||||
|
isVideoUploadFinish,
|
||||||
|
isAudioUploadFinish,
|
||||||
|
isTextUploadFinish,
|
||||||
|
]);
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
if (
|
||||||
|
isImageUploadFinish &&
|
||||||
|
isVideoUploadFinish &&
|
||||||
|
isAudioUploadFinish &&
|
||||||
|
isTextUploadFinish
|
||||||
|
) {
|
||||||
|
successSubmit("/in/contributor/task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRow = () => {
|
||||||
|
setLinks([...links, ""]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove a specific link row
|
||||||
|
const handleRemoveRow = (index: number) => {
|
||||||
|
const updatedLinks = links.filter((_: any, i: any) => i !== index);
|
||||||
|
setLinks(updatedLinks);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFilePreview = (url: string) => {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
alt={"file preview"}
|
||||||
|
src={url}
|
||||||
|
className=" rounded border p-0.5"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLinkChange = (index: number, value: string) => {
|
||||||
|
const updatedUrls = [...urlInputs];
|
||||||
|
updatedUrls[index].attachmentUrl = value;
|
||||||
|
setUrlInputs(updatedUrls);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddLink = () => {
|
||||||
|
setUrlInputs([
|
||||||
|
...urlInputs,
|
||||||
|
{ id: Date.now(), attachmentUrl: "" }, // Add a new empty object
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFile = (id: number) => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
|
|
@ -372,23 +601,6 @@ export default function FormTaskEdit() {
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* <div className="space-y-2">
|
|
||||||
<Label>Kode Unik</Label>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="uniqueCode"
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input
|
|
||||||
size="md"
|
|
||||||
type="text"
|
|
||||||
value={detail?.uniqueCode}
|
|
||||||
onChange={field.onChange}
|
|
||||||
placeholder="Enter uniqueCode"
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div> */}
|
|
||||||
<div className="space-y-2 mt-6">
|
<div className="space-y-2 mt-6">
|
||||||
<Label>Judul</Label>
|
<Label>Judul</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -530,7 +742,7 @@ export default function FormTaskEdit() {
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Label>Tipe Penugasan</Label>
|
<Label>Tipe Penugasan</Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||||
onValueChange={(value) => setMainType(value)}
|
onValueChange={(value) => setMainType(value)}
|
||||||
// value={String(mainType)}
|
// value={String(mainType)}
|
||||||
// onValueChange={(value) => setMainType(Number(value))}
|
// onValueChange={(value) => setMainType(Number(value))}
|
||||||
|
|
@ -545,7 +757,7 @@ export default function FormTaskEdit() {
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Label>Jenis Tugas </Label>
|
<Label>Jenis Tugas </Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={detail.taskType.toString()}
|
defaultValue={detail.taskType.toString()}
|
||||||
onValueChange={(value) => setTaskType(String(value))}
|
onValueChange={(value) => setTaskType(String(value))}
|
||||||
className="flex flex-wrap gap-3"
|
className="flex flex-wrap gap-3"
|
||||||
>
|
>
|
||||||
|
|
@ -559,7 +771,7 @@ export default function FormTaskEdit() {
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Label>Jenis Penugasan</Label>
|
<Label>Jenis Penugasan</Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||||
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||||
className="flex flex-wrap gap-3"
|
className="flex flex-wrap gap-3"
|
||||||
>
|
>
|
||||||
|
|
@ -620,7 +832,7 @@ export default function FormTaskEdit() {
|
||||||
<Label htmlFor="attachments">Lampiran</Label>
|
<Label htmlFor="attachments">Lampiran</Label>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>Video</Label>
|
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"mp4/*": [],
|
"mp4/*": [],
|
||||||
|
|
@ -630,9 +842,38 @@ export default function FormTaskEdit() {
|
||||||
label="Upload file dengan format .mp4 atau .mov."
|
label="Upload file dengan format .mp4 atau .mov."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setImageFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
{videoUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div 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>
|
||||||
<div>
|
<div>
|
||||||
<Label>Foto</Label>
|
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"image/*": [],
|
"image/*": [],
|
||||||
|
|
@ -641,9 +882,38 @@ export default function FormTaskEdit() {
|
||||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setImageFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div 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>
|
||||||
<div>
|
<div>
|
||||||
<Label>Teks</Label>
|
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"pdf/*": [],
|
"pdf/*": [],
|
||||||
|
|
@ -652,9 +922,38 @@ export default function FormTaskEdit() {
|
||||||
label="Upload file dengan format .pdf."
|
label="Upload file dengan format .pdf."
|
||||||
onDrop={(files) => setTextFiles(files)}
|
onDrop={(files) => setTextFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
{textUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div 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>
|
||||||
<div>
|
<div>
|
||||||
<Label>Voice Note</Label>
|
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -674,6 +973,35 @@ export default function FormTaskEdit() {
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) => setAudioFiles(files)}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
|
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className=" text-sm text-card-foreground">
|
||||||
|
{file.fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="destructive"
|
||||||
|
variant="outline"
|
||||||
|
className=" border-none rounded-full"
|
||||||
|
onClick={() => handleRemoveFile(file)}
|
||||||
|
>
|
||||||
|
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFile && (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
|
@ -691,14 +1019,30 @@ export default function FormTaskEdit() {
|
||||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||||
{/* Display remaining time */}
|
{/* Display remaining time */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||||
<Input
|
{urlInputs.map((url: any, index: any) => (
|
||||||
id="voiceNoteLink"
|
<div
|
||||||
type="url"
|
key={url.id}
|
||||||
placeholder="Masukkan link voice note"
|
className="flex items-center gap-2 mt-2"
|
||||||
value={voiceNoteLink}
|
>
|
||||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
<input
|
||||||
/>
|
type="url"
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,15 @@ import { AudioRecorder } from "react-audio-voice-recorder";
|
||||||
import FileUploader from "@/components/form/shared/file-uploader";
|
import FileUploader from "@/components/form/shared/file-uploader";
|
||||||
import { Upload } from "tus-js-client";
|
import { Upload } from "tus-js-client";
|
||||||
import { error } from "@/config/swal";
|
import { error } from "@/config/swal";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { loading } from "@/lib/swal";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
naration: z.string().min(2, {
|
naration: z.string().min(2, {
|
||||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||||
}),
|
}),
|
||||||
|
// url: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
interface FileWithPreview extends File {
|
interface FileWithPreview extends File {
|
||||||
|
|
@ -63,6 +66,7 @@ export type taskDetail = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
attachmentUrl: string;
|
||||||
taskType: string;
|
taskType: string;
|
||||||
broadcastType: string;
|
broadcastType: string;
|
||||||
narration: string;
|
narration: string;
|
||||||
|
|
@ -120,6 +124,7 @@ export default function FormTask() {
|
||||||
polda: false,
|
polda: false,
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
|
const [links, setLinks] = useState<string[]>([""]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|
@ -217,6 +222,7 @@ export default function FormTask() {
|
||||||
taskType: string;
|
taskType: string;
|
||||||
assignedToRole: string;
|
assignedToRole: string;
|
||||||
broadcastType: string;
|
broadcastType: string;
|
||||||
|
attachmentUrl: string[];
|
||||||
} = {
|
} = {
|
||||||
...data,
|
...data,
|
||||||
// assignmentType,
|
// assignmentType,
|
||||||
|
|
@ -232,6 +238,7 @@ export default function FormTask() {
|
||||||
narration: data.naration,
|
narration: data.naration,
|
||||||
platformType: "",
|
platformType: "",
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
attachmentUrl: links,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await createTask(requestData);
|
const response = await createTask(requestData);
|
||||||
|
|
@ -240,7 +247,7 @@ export default function FormTask() {
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
|
|
||||||
const id = response?.data?.data.id;
|
const id = response?.data?.data.id;
|
||||||
|
loading();
|
||||||
if (imageFiles?.length == 0) {
|
if (imageFiles?.length == 0) {
|
||||||
setIsImageUploadFinish(true);
|
setIsImageUploadFinish(true);
|
||||||
}
|
}
|
||||||
|
|
@ -355,20 +362,28 @@ export default function FormTask() {
|
||||||
) {
|
) {
|
||||||
console.log(idx, id, file, fileTypeId, duration);
|
console.log(idx, id, file, fileTypeId, duration);
|
||||||
|
|
||||||
// const placements = getPlacement(file.placements);
|
const resCsrf = await getCsrfToken();
|
||||||
// console.log("Placementttt: : ", placements);
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
const upload = new Upload(file, {
|
const upload = new Upload(file, {
|
||||||
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
|
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
|
||||||
|
headers: headers,
|
||||||
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
chunkSize: 20_000,
|
chunkSize: 20_000,
|
||||||
metadata: {
|
metadata: {
|
||||||
assignmentid: id,
|
assignmentId: id,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
filetype: file.type,
|
contentType: file.type,
|
||||||
fileTypeId: fileTypeId,
|
fileTypeId: fileTypeId,
|
||||||
duration: "",
|
duration,
|
||||||
isWatermark: "true", // hardcode
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
},
|
},
|
||||||
onError: async (e: any) => {
|
onError: async (e: any) => {
|
||||||
console.log("Error upload :", e);
|
console.log("Error upload :", e);
|
||||||
|
|
@ -379,7 +394,7 @@ export default function FormTask() {
|
||||||
bytesAccepted: any,
|
bytesAccepted: any,
|
||||||
bytesTotal: any
|
bytesTotal: any
|
||||||
) => {
|
) => {
|
||||||
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
// progressInfo[idx].percentage = uploadPersen;
|
// progressInfo[idx].percentage = uploadPersen;
|
||||||
// counterUpdateProgress++;
|
// counterUpdateProgress++;
|
||||||
// console.log(counterUpdateProgress);
|
// console.log(counterUpdateProgress);
|
||||||
|
|
@ -441,6 +456,22 @@ export default function FormTask() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLinkChange = (index: number, value: string) => {
|
||||||
|
const updatedLinks = [...links];
|
||||||
|
updatedLinks[index] = value;
|
||||||
|
setLinks(updatedLinks);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRow = () => {
|
||||||
|
setLinks([...links, ""]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove a specific link row
|
||||||
|
const handleRemoveRow = (index: number) => {
|
||||||
|
const updatedLinks = links.filter((_: any, i: any) => i !== index);
|
||||||
|
setLinks(updatedLinks);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
|
|
@ -702,7 +733,7 @@ export default function FormTask() {
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp4 atau .mov."
|
label="Upload file dengan format .mp4 atau .mov."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setVideoFiles(files)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -765,14 +796,36 @@ export default function FormTask() {
|
||||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||||
{/* Display remaining time */}
|
{/* Display remaining time */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||||
<Input
|
{links.map((link, index) => (
|
||||||
id="voiceNoteLink"
|
<div key={index} className="flex items-center gap-2 mt-2">
|
||||||
type="url"
|
<input
|
||||||
placeholder="Masukkan link voice note"
|
type="url"
|
||||||
value={voiceNoteLink}
|
className="border rounded p-2 w-full"
|
||||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
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>
|
</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) {
|
export async function getAgendaSettingsById(id: any) {
|
||||||
const url = `agenda-settings?id=${id}`;
|
const url = `agenda-settings?id=${id}`;
|
||||||
|
|
@ -84,3 +87,8 @@ export async function getPlanningDailyMedsosByPlatform(
|
||||||
const url = `planning/pagination/daily?enablePage=1&size=${size}&page=${page}&date=${date}&typeId=2&platformTypeId=${platformTypeId}`;
|
const url = `planning/pagination/daily?enablePage=1&size=${size}&page=${page}&date=${date}&typeId=2&platformTypeId=${platformTypeId}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteAgendaSettings(id: any) {
|
||||||
|
const url = `agenda-settings?id=${id}`;
|
||||||
|
return httpDeleteInterceptor(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
httpDeleteInterceptor,
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
} from "../http-config/http-interceptor-service";
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
@ -199,3 +200,13 @@ export async function saveUserReports(data: any) {
|
||||||
const url = "public/users/reports";
|
const url = "public/users/reports";
|
||||||
return httpPostInterceptor(url, data);
|
return httpPostInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteMedia(data: any) {
|
||||||
|
const url = `media`;
|
||||||
|
return httpDeleteInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFile(data: any) {
|
||||||
|
const url = "media/file";
|
||||||
|
return httpDeleteInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,11 @@ export async function httpGetInterceptor(pathUrl: any) {
|
||||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||||
Cookies.remove(cookieName);
|
Cookies.remove(cookieName);
|
||||||
});
|
});
|
||||||
if (pathname?.includes("/contributor/") || pathname?.includes("/admin/") || pathname?.includes("/supervisor/")) {
|
if (
|
||||||
|
pathname?.includes("/contributor/") ||
|
||||||
|
pathname?.includes("/admin/") ||
|
||||||
|
pathname?.includes("/supervisor/")
|
||||||
|
) {
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -113,7 +117,7 @@ export async function httpPutInterceptor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function httpDeleteInterceptor(pathUrl: any) {
|
export async function httpDeleteInterceptor(pathUrl: any, data?: any) {
|
||||||
const resCsrf = await getCsrfToken();
|
const resCsrf = await getCsrfToken();
|
||||||
const csrfToken = resCsrf?.data?.token;
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
|
||||||
|
|
@ -126,7 +130,7 @@ export async function httpDeleteInterceptor(pathUrl: any) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axiosInterceptorInstance
|
const response = await axiosInterceptorInstance
|
||||||
.delete(pathUrl, { headers: mergedHeaders})
|
.delete(pathUrl, { headers: mergedHeaders, data })
|
||||||
.catch((error) => error.response);
|
.catch((error) => error.response);
|
||||||
console.log("Response interceptor : ", response);
|
console.log("Response interceptor : ", response);
|
||||||
if (response?.status == 200 || response?.status == 201) {
|
if (response?.status == 200 || response?.status == 201) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue