feat: add file ckeditor
This commit is contained in:
commit
e40c977529
|
|
@ -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>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,204 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getUserLevelForAssignments } from "@/service/task";
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||||
|
message: "Required",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface UnitType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
subDestination: { id: number; name: string }[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UnitMapping(props: {
|
||||||
|
unit: "Polda" | "Satker" | "Polres";
|
||||||
|
sendDataToParent: (data: string[]) => void;
|
||||||
|
isDetail: boolean;
|
||||||
|
initData?: string[];
|
||||||
|
}) {
|
||||||
|
const { unit, sendDataToParent, isDetail } = props;
|
||||||
|
const [unitList, setUnitList] = useState<UnitType[]>([]);
|
||||||
|
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
items: props.initData ? props.initData : [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
const response = await getUserLevelForAssignments();
|
||||||
|
setupUnit(response?.data?.data.list);
|
||||||
|
console.log("list", response?.data?.data.list);
|
||||||
|
}
|
||||||
|
|
||||||
|
initState();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const unitType = form.watch("items");
|
||||||
|
|
||||||
|
const isAllUnitChecked = unitList.every((item) =>
|
||||||
|
unitType?.includes(String(item.id))
|
||||||
|
);
|
||||||
|
const isAllSatkerChecked = satkerList.every((item) =>
|
||||||
|
unitType?.includes(String(item.id))
|
||||||
|
);
|
||||||
|
const isAllPolresChecked = polresList.every((item) =>
|
||||||
|
unitType?.includes(String(item.id))
|
||||||
|
);
|
||||||
|
|
||||||
|
const setupUnit = (data: UnitType[]) => {
|
||||||
|
const temp = data.filter((a) => a.name.includes("POLDA"));
|
||||||
|
const temp2 = data.filter((a) => a.name.includes("SATKER"));
|
||||||
|
const temp3 = temp.flatMap((item) => item.subDestination || []);
|
||||||
|
setUnitList(temp);
|
||||||
|
setSatkerList(temp2[0]?.subDestination || []);
|
||||||
|
setPolresList(temp3);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sendDataToParent(form.getValues("items"));
|
||||||
|
}, [unitType]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<a
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
className="text-primary cursor-pointer text-xs mr-3"
|
||||||
|
>
|
||||||
|
Pilih {unit}
|
||||||
|
</a>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent size="md" className="h-[500px] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{unit}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Checkbox
|
||||||
|
id={`all-${unit}`}
|
||||||
|
checked={
|
||||||
|
unit === "Polda"
|
||||||
|
? isAllUnitChecked
|
||||||
|
: unit === "Satker"
|
||||||
|
? isAllSatkerChecked
|
||||||
|
: isAllPolresChecked
|
||||||
|
}
|
||||||
|
disabled={isDetail}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
form.setValue(
|
||||||
|
"items",
|
||||||
|
unit === "Polda"
|
||||||
|
? unitList.map((item) => String(item.id))
|
||||||
|
: unit === "Satker"
|
||||||
|
? satkerList.map((item) => String(item.id))
|
||||||
|
: polresList.map((item) => String(item.id))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
form.setValue("items", []);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="all" className="text-sm text-black uppercase">
|
||||||
|
SEMUA {unit}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="items"
|
||||||
|
render={() => (
|
||||||
|
<FormItem
|
||||||
|
className={`grid grid-cols-${
|
||||||
|
unit === "Polda" ? "2" : unit === "Satker" ? "3" : "4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{(unit === "Polda"
|
||||||
|
? unitList
|
||||||
|
: unit === "Satker"
|
||||||
|
? satkerList
|
||||||
|
: polresList
|
||||||
|
)?.map((item: any) => (
|
||||||
|
<FormField
|
||||||
|
key={item.id}
|
||||||
|
control={form.control}
|
||||||
|
name="items"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem
|
||||||
|
key={String(item.id)}
|
||||||
|
className="flex flex-row items-center space-x-3 space-y-0"
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
disabled={isDetail}
|
||||||
|
checked={field.value?.includes(String(item.id))}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
return checked
|
||||||
|
? field.onChange([
|
||||||
|
...field.value,
|
||||||
|
String(item.id),
|
||||||
|
])
|
||||||
|
: field.onChange(
|
||||||
|
field.value?.filter(
|
||||||
|
(value) => value !== String(item.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<p className="text-sm text-black">{item.name}</p>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,11 @@ import {
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link, useRouter } from "@/components/navigation";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { deleteBlog } from "@/service/blog/blog";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +88,48 @@ 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 deleteBlog(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 handleDeleteBlog = (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>
|
||||||
|
|
@ -108,7 +154,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={() => handleDeleteBlog(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 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>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorGroup",
|
accessorKey: "creatorName",
|
||||||
header: "Creator Group",
|
header: "Creator Group",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorName",
|
accessorKey: "creatorGroupLevelName",
|
||||||
header: "Sumber",
|
header: "Sumber",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
<span className="whitespace-nowrap">
|
||||||
|
{row.getValue("creatorGroupLevelName")}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -135,6 +141,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 +210,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>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Eye,
|
Eye,
|
||||||
|
|
@ -39,6 +40,7 @@ import {
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
|
@ -55,7 +57,10 @@ import {
|
||||||
listDataAudio,
|
listDataAudio,
|
||||||
listDataImage,
|
listDataImage,
|
||||||
listDataVideo,
|
listDataVideo,
|
||||||
|
listEnableCategory,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
const TableAudio = () => {
|
const TableAudio = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -81,13 +86,17 @@ const TableAudio = () => {
|
||||||
const userId = getCookiesDecrypt("uie");
|
const userId = getCookiesDecrypt("uie");
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
const userLevelId = getCookiesDecrypt("ulie");
|
||||||
|
|
||||||
const [categories, setCategories] = React.useState();
|
const [categories, setCategories] = React.useState<any[]>([]);
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||||
const [startDateString, setStartDateString] = React.useState("");
|
const [startDate, setStartDate] = React.useState("");
|
||||||
const [endDateString, setEndDateString] = React.useState("");
|
const [endDate, setEndDate] = React.useState("");
|
||||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||||
const [filterBySource, setFilterBySource] = React.useState("");
|
const [filterBySource, setFilterBySource] = React.useState("");
|
||||||
|
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||||
|
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
|
|
||||||
|
|
@ -121,35 +130,69 @@ const TableAudio = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, limit, search]);
|
getCategories();
|
||||||
|
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("4");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi menangani perubahan checkbox
|
||||||
|
const handleCheckboxChange = (categoryId: number) => {
|
||||||
|
setSelectedCategories(
|
||||||
|
(prev: any) =>
|
||||||
|
prev.includes(categoryId)
|
||||||
|
? prev.filter((id: any) => id !== categoryId) // Hapus jika sudah dipilih
|
||||||
|
: [...prev, categoryId] // Tambahkan jika belum dipilih
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perbarui filter kategori
|
||||||
|
setCategoryFilter((prev) => {
|
||||||
|
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
|
||||||
|
|
||||||
|
const newCategories = updatedCategories.includes(categoryId)
|
||||||
|
? updatedCategories.filter((id) => id !== categoryId)
|
||||||
|
: [...updatedCategories, categoryId];
|
||||||
|
|
||||||
|
return newCategories.join(",");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
const formattedStartDate = startDate
|
||||||
|
? format(new Date(startDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
|
const formattedEndDate = endDate
|
||||||
|
? format(new Date(endDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
try {
|
try {
|
||||||
const isForSelf = Number(roleId) == 4;
|
const isForSelf = Number(roleId) === 4;
|
||||||
const res = await listDataAudio(
|
const res = await listDataAudio(
|
||||||
limit,
|
limit,
|
||||||
page - 1,
|
page - 1,
|
||||||
isForSelf,
|
isForSelf,
|
||||||
!isForSelf,
|
!isForSelf,
|
||||||
categoryFilter?.sort().join(","),
|
categoryFilter,
|
||||||
statusFilter?.sort().join(",").includes("1")
|
statusFilter?.sort().join(",").includes("1")
|
||||||
? "1,2"
|
? "1,2"
|
||||||
: statusFilter?.sort().join(","),
|
: statusFilter?.sort().join(","),
|
||||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||||
filterByCreator,
|
filterByCreator,
|
||||||
filterBySource,
|
filterBySource,
|
||||||
startDateString,
|
formattedStartDate, // Pastikan format sesuai
|
||||||
endDateString,
|
formattedEndDate, // Pastikan format sesuai
|
||||||
search
|
search,
|
||||||
|
filterByCreatorGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content;
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * limit + index + 1;
|
item.no = (page - 1) * limit + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("contentData : ", contentData);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
setDataTable(contentData);
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
|
|
@ -157,11 +200,28 @@ const TableAudio = () => {
|
||||||
console.error("Error fetching tasks:", error);
|
console.error("Error fetching tasks:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value); // Perbarui state search
|
setSearch(e.target.value); // Perbarui state search
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterBySource = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterBySource(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterByCreator = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterByCreator(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
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">
|
||||||
|
|
@ -179,17 +239,139 @@ const TableAudio = () => {
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex flex-row items-center gap-3">
|
||||||
<Input
|
<div className="flex items-center py-4">
|
||||||
placeholder="Filter Status..."
|
<DropdownMenu>
|
||||||
value={
|
<DropdownMenuTrigger asChild>
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
}
|
Filter <ChevronDown />
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
</Button>
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
</DropdownMenuTrigger>
|
||||||
}
|
<DropdownMenuContent
|
||||||
className="max-w-sm "
|
align="end"
|
||||||
/>
|
className="w-64 h-[200px] overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between my-1 mx-1">
|
||||||
|
<p>Filter</p>
|
||||||
|
{/* <p
|
||||||
|
className="text-blue-600 cursor-pointer"
|
||||||
|
onClick={fetchData}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
|
<Label className="ml-2">Kategori</Label>
|
||||||
|
{categories.length > 0 ? (
|
||||||
|
categories.map((category) => (
|
||||||
|
<div
|
||||||
|
key={category.id}
|
||||||
|
className="flex items-center px-4 py-1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`category-${category.id}`}
|
||||||
|
className="mr-2"
|
||||||
|
checked={selectedCategories.includes(category.id)}
|
||||||
|
onChange={() => handleCheckboxChange(category.id)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`category-${category.id}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500 px-4 py-2">
|
||||||
|
No categories found.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Awal</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Akhir</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Kreator</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterByCreator}
|
||||||
|
onChange={handleSearchFilterByCreator}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Sumber</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterBySource}
|
||||||
|
onChange={handleSearchFilterBySource}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Status</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={
|
||||||
|
(table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.getFilterValue() as string) ?? ""
|
||||||
|
}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
|
className="max-w-sm "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center py-4">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
|
Columns <ChevronDown />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter((column) => column.getCanHide())
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
className="capitalize"
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table className="overflow-hidden mt-3">
|
<Table className="overflow-hidden mt-3">
|
||||||
|
|
|
||||||
|
|
@ -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>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -64,17 +72,19 @@ const columns: ColumnDef<any>[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorGroup",
|
accessorKey: "creatorName",
|
||||||
header: "Creator Group",
|
header: "Creator Group",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorName",
|
accessorKey: "creatorGroupLevelName",
|
||||||
header: "Sumber",
|
header: "Sumber",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
<span className="whitespace-nowrap">
|
||||||
|
{row.getValue("creatorGroupLevelName")}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -131,6 +141,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 +211,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,24 @@ 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,
|
||||||
|
listEnableCategory,
|
||||||
|
} 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";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
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>([]);
|
||||||
|
|
@ -79,13 +91,17 @@ const TableImage = () => {
|
||||||
const userId = getCookiesDecrypt("uie");
|
const userId = getCookiesDecrypt("uie");
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
const userLevelId = getCookiesDecrypt("ulie");
|
||||||
|
|
||||||
const [categories, setCategories] = React.useState();
|
const [categories, setCategories] = React.useState<any[]>([]);
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||||
const [startDateString, setStartDateString] = React.useState("");
|
const [startDate, setStartDate] = React.useState("");
|
||||||
const [endDateString, setEndDateString] = React.useState("");
|
const [endDate, setEndDate] = React.useState("");
|
||||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||||
const [filterBySource, setFilterBySource] = React.useState("");
|
const [filterBySource, setFilterBySource] = React.useState("");
|
||||||
|
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||||
|
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
|
|
||||||
|
|
@ -118,36 +134,71 @@ const TableImage = () => {
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
// Panggil fetchData saat filter kategori berubah
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, limit, search]);
|
getCategories();
|
||||||
|
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("1");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi menangani perubahan checkbox
|
||||||
|
const handleCheckboxChange = (categoryId: number) => {
|
||||||
|
setSelectedCategories(
|
||||||
|
(prev: any) =>
|
||||||
|
prev.includes(categoryId)
|
||||||
|
? prev.filter((id: any) => id !== categoryId) // Hapus jika sudah dipilih
|
||||||
|
: [...prev, categoryId] // Tambahkan jika belum dipilih
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perbarui filter kategori
|
||||||
|
setCategoryFilter((prev) => {
|
||||||
|
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
|
||||||
|
|
||||||
|
const newCategories = updatedCategories.includes(categoryId)
|
||||||
|
? updatedCategories.filter((id) => id !== categoryId)
|
||||||
|
: [...updatedCategories, categoryId];
|
||||||
|
|
||||||
|
return newCategories.join(",");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
const formattedStartDate = startDate
|
||||||
|
? format(new Date(startDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
|
const formattedEndDate = endDate
|
||||||
|
? format(new Date(endDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
try {
|
try {
|
||||||
const isForSelf = Number(roleId) == 4;
|
const isForSelf = Number(roleId) === 4;
|
||||||
const res = await listDataImage(
|
const res = await listDataImage(
|
||||||
limit,
|
limit,
|
||||||
page - 1,
|
page - 1,
|
||||||
isForSelf,
|
isForSelf,
|
||||||
!isForSelf,
|
!isForSelf,
|
||||||
categoryFilter?.sort().join(","),
|
categoryFilter,
|
||||||
statusFilter?.sort().join(",").includes("1")
|
statusFilter?.sort().join(",").includes("1")
|
||||||
? "1,2"
|
? "1,2"
|
||||||
: statusFilter?.sort().join(","),
|
: statusFilter?.sort().join(","),
|
||||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||||
filterByCreator,
|
filterByCreator,
|
||||||
filterBySource,
|
filterBySource,
|
||||||
startDateString,
|
formattedStartDate, // Pastikan format sesuai
|
||||||
endDateString,
|
formattedEndDate, // Pastikan format sesuai
|
||||||
search
|
search,
|
||||||
|
filterByCreatorGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content;
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * limit + index + 1;
|
item.no = (page - 1) * limit + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("contentData : ", contentData);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
setDataTable(contentData);
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
|
|
@ -161,6 +212,22 @@ const TableImage = () => {
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterBySource = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterBySource(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterByCreator = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterByCreator(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
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">
|
||||||
|
|
@ -179,16 +246,110 @@ const TableImage = () => {
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3">
|
<div className="flex flex-row items-center gap-3">
|
||||||
<Input
|
<div className="flex items-center py-4">
|
||||||
placeholder="Filter Status..."
|
<DropdownMenu>
|
||||||
value={
|
<DropdownMenuTrigger asChild>
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
}
|
Filter <ChevronDown />
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
</Button>
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
</DropdownMenuTrigger>
|
||||||
}
|
<DropdownMenuContent
|
||||||
className="max-w-sm "
|
align="end"
|
||||||
/>
|
className="w-64 h-[200px] overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between my-1 mx-1">
|
||||||
|
<p>Filter</p>
|
||||||
|
{/* <p
|
||||||
|
className="text-blue-600 cursor-pointer"
|
||||||
|
onClick={fetchData}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
|
<Label className="ml-2">Kategori</Label>
|
||||||
|
{categories.length > 0 ? (
|
||||||
|
categories.map((category) => (
|
||||||
|
<div
|
||||||
|
key={category.id}
|
||||||
|
className="flex items-center px-4 py-1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`category-${category.id}`}
|
||||||
|
className="mr-2"
|
||||||
|
checked={selectedCategories.includes(category.id)}
|
||||||
|
onChange={() => handleCheckboxChange(category.id)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`category-${category.id}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500 px-4 py-2">
|
||||||
|
No categories found.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Awal</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Akhir</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Kreator</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterByCreator}
|
||||||
|
onChange={handleSearchFilterByCreator}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Sumber</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterBySource}
|
||||||
|
onChange={handleSearchFilterBySource}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Status</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={
|
||||||
|
(table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.getFilterValue() as string) ?? ""
|
||||||
|
}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
|
className="max-w-sm "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
<div className="flex items-center py-4">
|
<div className="flex items-center py-4">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ const columns: ColumnDef<any>[] = [
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color={isPublish ? "success" : "warning"} // Hijau untuk diterima, oranye untuk menunggu review
|
color={isPublish ? "success" : "warning"}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`btn btn-sm ${
|
className={`btn btn-sm ${
|
||||||
isPublish ? "btn-outline-success" : "btn-outline-warning"
|
isPublish ? "btn-outline-success" : "btn-outline-warning"
|
||||||
|
|
|
||||||
|
|
@ -170,10 +170,10 @@ const TableSPIT = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter Status..."
|
placeholder="Filter Status..."
|
||||||
value={
|
value={
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
(table.getColumn("isPublish")?.getFilterValue() as string) ?? ""
|
||||||
}
|
}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
table.getColumn("isPublish")?.setFilterValue(event.target.value)
|
||||||
}
|
}
|
||||||
className="max-w-sm "
|
className="max-w-sm "
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorGroup",
|
accessorKey: "creatorName",
|
||||||
header: "Creator Group",
|
header: "Creator Group",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorName",
|
accessorKey: "creatorGroupLevelName",
|
||||||
header: "Sumber",
|
header: "Sumber",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
<span className="whitespace-nowrap">
|
||||||
|
{row.getValue("creatorGroupLevelName")}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -136,6 +142,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 +211,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>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Eye,
|
Eye,
|
||||||
|
|
@ -39,6 +40,7 @@ import {
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
|
@ -51,7 +53,13 @@ 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, listDataTeks } from "@/service/content/content";
|
import {
|
||||||
|
listDataImage,
|
||||||
|
listDataTeks,
|
||||||
|
listEnableCategory,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
const TableTeks = () => {
|
const TableTeks = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -77,13 +85,17 @@ const TableTeks = () => {
|
||||||
const userId = getCookiesDecrypt("uie");
|
const userId = getCookiesDecrypt("uie");
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
const userLevelId = getCookiesDecrypt("ulie");
|
||||||
|
|
||||||
const [categories, setCategories] = React.useState();
|
const [categories, setCategories] = React.useState<any[]>([]);
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||||
const [startDateString, setStartDateString] = React.useState("");
|
const [startDate, setStartDate] = React.useState("");
|
||||||
const [endDateString, setEndDateString] = React.useState("");
|
const [endDate, setEndDate] = React.useState("");
|
||||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||||
const [filterBySource, setFilterBySource] = React.useState("");
|
const [filterBySource, setFilterBySource] = React.useState("");
|
||||||
|
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||||
|
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
|
|
||||||
|
|
@ -117,35 +129,69 @@ const TableTeks = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, limit, search]);
|
getCategories();
|
||||||
|
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("3");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi menangani perubahan checkbox
|
||||||
|
const handleCheckboxChange = (categoryId: number) => {
|
||||||
|
setSelectedCategories(
|
||||||
|
(prev: any) =>
|
||||||
|
prev.includes(categoryId)
|
||||||
|
? prev.filter((id: any) => id !== categoryId) // Hapus jika sudah dipilih
|
||||||
|
: [...prev, categoryId] // Tambahkan jika belum dipilih
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perbarui filter kategori
|
||||||
|
setCategoryFilter((prev) => {
|
||||||
|
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
|
||||||
|
|
||||||
|
const newCategories = updatedCategories.includes(categoryId)
|
||||||
|
? updatedCategories.filter((id) => id !== categoryId)
|
||||||
|
: [...updatedCategories, categoryId];
|
||||||
|
|
||||||
|
return newCategories.join(",");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
const formattedStartDate = startDate
|
||||||
|
? format(new Date(startDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
|
const formattedEndDate = endDate
|
||||||
|
? format(new Date(endDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
try {
|
try {
|
||||||
const isForSelf = Number(roleId) == 4;
|
const isForSelf = Number(roleId) === 4;
|
||||||
const res = await listDataTeks(
|
const res = await listDataTeks(
|
||||||
limit,
|
limit,
|
||||||
page - 1,
|
page - 1,
|
||||||
isForSelf,
|
isForSelf,
|
||||||
!isForSelf,
|
!isForSelf,
|
||||||
categoryFilter?.sort().join(","),
|
categoryFilter,
|
||||||
statusFilter?.sort().join(",").includes("1")
|
statusFilter?.sort().join(",").includes("1")
|
||||||
? "1,2"
|
? "1,2"
|
||||||
: statusFilter?.sort().join(","),
|
: statusFilter?.sort().join(","),
|
||||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||||
filterByCreator,
|
filterByCreator,
|
||||||
filterBySource,
|
filterBySource,
|
||||||
startDateString,
|
formattedStartDate, // Pastikan format sesuai
|
||||||
endDateString,
|
formattedEndDate, // Pastikan format sesuai
|
||||||
search
|
search,
|
||||||
|
filterByCreatorGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content;
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * limit + index + 1;
|
item.no = (page - 1) * limit + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("contentData : ", contentData);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
setDataTable(contentData);
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
|
|
@ -159,6 +205,22 @@ const TableTeks = () => {
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterBySource = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterBySource(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterByCreator = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterByCreator(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
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">
|
||||||
|
|
@ -176,17 +238,139 @@ const TableTeks = () => {
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex flex-row items-center gap-3">
|
||||||
<Input
|
<div className="flex items-center py-4">
|
||||||
placeholder="Filter Status..."
|
<DropdownMenu>
|
||||||
value={
|
<DropdownMenuTrigger asChild>
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
}
|
Filter <ChevronDown />
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
</Button>
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
</DropdownMenuTrigger>
|
||||||
}
|
<DropdownMenuContent
|
||||||
className="max-w-sm "
|
align="end"
|
||||||
/>
|
className="w-64 h-[200px] overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between my-1 mx-1">
|
||||||
|
<p>Filter</p>
|
||||||
|
{/* <p
|
||||||
|
className="text-blue-600 cursor-pointer"
|
||||||
|
onClick={fetchData}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
|
<Label className="ml-2">Kategori</Label>
|
||||||
|
{categories.length > 0 ? (
|
||||||
|
categories.map((category) => (
|
||||||
|
<div
|
||||||
|
key={category.id}
|
||||||
|
className="flex items-center px-4 py-1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`category-${category.id}`}
|
||||||
|
className="mr-2"
|
||||||
|
checked={selectedCategories.includes(category.id)}
|
||||||
|
onChange={() => handleCheckboxChange(category.id)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`category-${category.id}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500 px-4 py-2">
|
||||||
|
No categories found.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Awal</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Akhir</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Kreator</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterByCreator}
|
||||||
|
onChange={handleSearchFilterByCreator}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Sumber</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterBySource}
|
||||||
|
onChange={handleSearchFilterBySource}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Status</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={
|
||||||
|
(table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.getFilterValue() as string) ?? ""
|
||||||
|
}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
|
className="max-w-sm "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center py-4">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
|
Columns <ChevronDown />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter((column) => column.getCanHide())
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
className="capitalize"
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table className="overflow-hidden mt-3">
|
<Table className="overflow-hidden mt-3">
|
||||||
|
|
|
||||||
|
|
@ -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>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorGroup",
|
accessorKey: "creatorName",
|
||||||
header: "Creator Group",
|
header: "Creator Group",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "creatorName",
|
accessorKey: "creatorGroupLevelName",
|
||||||
header: "Sumber",
|
header: "Sumber",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
<span className="whitespace-nowrap">
|
||||||
|
{row.getValue("creatorGroupLevelName")}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -110,13 +116,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
"menunggu review": "bg-orange-100 text-orange-600",
|
"menunggu review": "bg-orange-100 text-orange-600",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mengambil `statusName` dari data API
|
|
||||||
const status = row.getValue("statusName") as string;
|
const status = row.getValue("statusName") as string;
|
||||||
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
|
const statusName = status?.toLocaleLowerCase();
|
||||||
|
|
||||||
// Gunakan `statusName` untuk pencocokan
|
|
||||||
const statusStyles =
|
const statusStyles =
|
||||||
statusColors[statusName] || "bg-gray-100 text-gray-600";
|
statusColors[statusName] || "bg-red-200 text-red-600";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
|
|
@ -136,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>
|
||||||
|
|
@ -160,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>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Eye,
|
Eye,
|
||||||
|
|
@ -39,6 +40,7 @@ import {
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
|
@ -51,9 +53,15 @@ 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, listDataVideo } from "@/service/content/content";
|
import {
|
||||||
|
listDataImage,
|
||||||
|
listDataVideo,
|
||||||
|
listEnableCategory,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
const TableImage = () => {
|
const TableVideo = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
|
@ -77,13 +85,17 @@ const TableImage = () => {
|
||||||
const userId = getCookiesDecrypt("uie");
|
const userId = getCookiesDecrypt("uie");
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
const userLevelId = getCookiesDecrypt("ulie");
|
||||||
|
|
||||||
const [categories, setCategories] = React.useState();
|
const [categories, setCategories] = React.useState<any[]>([]);
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||||
const [startDateString, setStartDateString] = React.useState("");
|
const [startDate, setStartDate] = React.useState("");
|
||||||
const [endDateString, setEndDateString] = React.useState("");
|
const [endDate, setEndDate] = React.useState("");
|
||||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||||
const [filterBySource, setFilterBySource] = React.useState("");
|
const [filterBySource, setFilterBySource] = React.useState("");
|
||||||
|
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||||
|
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
|
|
||||||
|
|
@ -117,35 +129,69 @@ const TableImage = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, limit, search]);
|
getCategories();
|
||||||
|
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("2");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi menangani perubahan checkbox
|
||||||
|
const handleCheckboxChange = (categoryId: number) => {
|
||||||
|
setSelectedCategories(
|
||||||
|
(prev: any) =>
|
||||||
|
prev.includes(categoryId)
|
||||||
|
? prev.filter((id: any) => id !== categoryId) // Hapus jika sudah dipilih
|
||||||
|
: [...prev, categoryId] // Tambahkan jika belum dipilih
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perbarui filter kategori
|
||||||
|
setCategoryFilter((prev) => {
|
||||||
|
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
|
||||||
|
|
||||||
|
const newCategories = updatedCategories.includes(categoryId)
|
||||||
|
? updatedCategories.filter((id) => id !== categoryId)
|
||||||
|
: [...updatedCategories, categoryId];
|
||||||
|
|
||||||
|
return newCategories.join(",");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
const formattedStartDate = startDate
|
||||||
|
? format(new Date(startDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
|
const formattedEndDate = endDate
|
||||||
|
? format(new Date(endDate), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
try {
|
try {
|
||||||
const isForSelf = Number(roleId) == 4;
|
const isForSelf = Number(roleId) === 4;
|
||||||
const res = await listDataVideo(
|
const res = await listDataVideo(
|
||||||
limit,
|
limit,
|
||||||
page - 1,
|
page - 1,
|
||||||
isForSelf,
|
isForSelf,
|
||||||
!isForSelf,
|
!isForSelf,
|
||||||
categoryFilter?.sort().join(","),
|
categoryFilter,
|
||||||
statusFilter?.sort().join(",").includes("1")
|
statusFilter?.sort().join(",").includes("1")
|
||||||
? "1,2"
|
? "1,2"
|
||||||
: statusFilter?.sort().join(","),
|
: statusFilter?.sort().join(","),
|
||||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||||
filterByCreator,
|
filterByCreator,
|
||||||
filterBySource,
|
filterBySource,
|
||||||
startDateString,
|
formattedStartDate, // Pastikan format sesuai
|
||||||
endDateString,
|
formattedEndDate, // Pastikan format sesuai
|
||||||
search
|
search,
|
||||||
|
filterByCreatorGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content;
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * limit + index + 1;
|
item.no = (page - 1) * limit + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("contentData : ", contentData);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
setDataTable(contentData);
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
|
|
@ -159,6 +205,22 @@ const TableImage = () => {
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterBySource = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterBySource(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchFilterByCreator = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterByCreator(value); // Perbarui state filter
|
||||||
|
fetchData(); // Panggil ulang data dengan filter baru
|
||||||
|
};
|
||||||
|
|
||||||
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">
|
||||||
|
|
@ -176,17 +238,140 @@ const TableImage = () => {
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
|
||||||
<Input
|
<div className="flex flex-row items-center gap-3">
|
||||||
placeholder="Filter Status..."
|
<div className="flex items-center py-4">
|
||||||
value={
|
<DropdownMenu>
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
<DropdownMenuTrigger asChild>
|
||||||
}
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
Filter <ChevronDown />
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
</Button>
|
||||||
}
|
</DropdownMenuTrigger>
|
||||||
className="max-w-sm "
|
<DropdownMenuContent
|
||||||
/>
|
align="end"
|
||||||
|
className="w-64 h-[200px] overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between my-1 mx-1">
|
||||||
|
<p>Filter</p>
|
||||||
|
{/* <p
|
||||||
|
className="text-blue-600 cursor-pointer"
|
||||||
|
onClick={fetchData}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
|
<Label className="ml-2">Kategori</Label>
|
||||||
|
{categories.length > 0 ? (
|
||||||
|
categories.map((category) => (
|
||||||
|
<div
|
||||||
|
key={category.id}
|
||||||
|
className="flex items-center px-4 py-1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`category-${category.id}`}
|
||||||
|
className="mr-2"
|
||||||
|
checked={selectedCategories.includes(category.id)}
|
||||||
|
onChange={() => handleCheckboxChange(category.id)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`category-${category.id}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500 px-4 py-2">
|
||||||
|
No categories found.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Awal</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Akhir</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Kreator</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterByCreator}
|
||||||
|
onChange={handleSearchFilterByCreator}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Sumber</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterBySource}
|
||||||
|
onChange={handleSearchFilterBySource}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Status</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={
|
||||||
|
(table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.getFilterValue() as string) ?? ""
|
||||||
|
}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
table
|
||||||
|
.getColumn("statusName")
|
||||||
|
?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
|
className="max-w-sm "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center py-4">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
|
Columns <ChevronDown />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter((column) => column.getCanHide())
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
className="capitalize"
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table className="overflow-hidden mt-3">
|
<Table className="overflow-hidden mt-3">
|
||||||
|
|
@ -239,4 +424,4 @@ const TableImage = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TableImage;
|
export default TableVideo;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,14 @@ import { Button } from "@/components/ui/button";
|
||||||
import InternalTable from "./internal/components/internal-table";
|
import InternalTable from "./internal/components/internal-table";
|
||||||
|
|
||||||
const CommunicationPage = () => {
|
const CommunicationPage = () => {
|
||||||
const [tab, setTab] = useState("Komunikasi");
|
const [tab, setTab] = useState("Pertanyaan Internal");
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<p className="text-lg">{tab}</p>
|
<p className="text-lg">{tab}</p>
|
||||||
{tab === "Komunikasi" && (
|
{tab === "Pertanyaan Internal" && (
|
||||||
<Link href="/shared/communication/internal/create">
|
<Link href="/shared/communication/internal/create">
|
||||||
<Button color="primary" size="md">
|
<Button color="primary" size="md">
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
|
|
@ -39,15 +39,15 @@ const CommunicationPage = () => {
|
||||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||||
<Button
|
<Button
|
||||||
rounded="md"
|
rounded="md"
|
||||||
onClick={() => setTab("Komunikasi")}
|
onClick={() => setTab("Pertanyaan Internal")}
|
||||||
className={` hover:text-white
|
className={` hover:text-white
|
||||||
${
|
${
|
||||||
tab === "Komunikasi"
|
tab === "Pertanyaan Internal"
|
||||||
? "bg-black text-white "
|
? "bg-black text-white "
|
||||||
: "bg-white text-black "
|
: "bg-white text-black "
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Komunikasi
|
Pertanyaan Internal
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
rounded="md"
|
rounded="md"
|
||||||
|
|
@ -74,7 +74,7 @@ const CommunicationPage = () => {
|
||||||
Kolaborasi
|
Kolaborasi
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{tab === "Komunikasi" && <InternalTable />}
|
{tab === "Pertanyaan Internal" && <InternalTable />}
|
||||||
{tab === "Eskalasi" && <EscalationTable />}
|
{tab === "Eskalasi" && <EscalationTable />}
|
||||||
{tab === "Kolaborasi" && <CollaborationTable />}
|
{tab === "Kolaborasi" && <CollaborationTable />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../../video/audio-visual";
|
||||||
|
|
||||||
|
const AudioAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Audio</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<AudioSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioAllPage;
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
} from "@/components/ui/carousel";
|
} from "@/components/ui/carousel";
|
||||||
|
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||||
import { getListContent } from "@/service/landing/landing";
|
import { getListContent } from "@/service/landing/landing";
|
||||||
import {
|
import {
|
||||||
formatDateToIndonesian,
|
formatDateToIndonesian,
|
||||||
|
|
@ -21,10 +22,12 @@ const AudioSliderPage = () => {
|
||||||
const [audioData, setAudioData] = useState<any>();
|
const [audioData, setAudioData] = useState<any>();
|
||||||
const [displayAudio, setDisplayAudio] = useState<any[]>([]);
|
const [displayAudio, setDisplayAudio] = useState<any[]>([]);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
const [limit, setLimit] = React.useState(10);
|
||||||
|
const [search, setSearch] = React.useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initFetch();
|
initFetch();
|
||||||
}, []);
|
}, [page, limit, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (audioData?.length > 0) {
|
if (audioData?.length > 0) {
|
||||||
|
|
@ -35,14 +38,12 @@ const AudioSliderPage = () => {
|
||||||
}, [audioData]);
|
}, [audioData]);
|
||||||
|
|
||||||
const initFetch = async () => {
|
const initFetch = async () => {
|
||||||
const response = await getListContent({
|
const response = await listCuratedContent(search, limit, page - 1, 4, "1");
|
||||||
page: page - 1,
|
|
||||||
size: 12,
|
|
||||||
sortBy: "createdAt",
|
|
||||||
contentTypeId: "4",
|
|
||||||
});
|
|
||||||
console.log(response);
|
console.log(response);
|
||||||
setAudioData(response?.data?.data?.content);
|
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
setAudioData(contentData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const shuffleAndSetVideos = () => {
|
const shuffleAndSetVideos = () => {
|
||||||
|
|
@ -65,7 +66,7 @@ const AudioSliderPage = () => {
|
||||||
<Link
|
<Link
|
||||||
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
||||||
key={audio?.id}
|
key={audio?.id}
|
||||||
className="flex flex-col sm:flex-row items-center hover:scale-110 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
className="flex flex-col sm:flex-row items-center hover:scale-100 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
|
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
|
||||||
<svg
|
<svg
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../../video/audio-visual";
|
||||||
|
|
||||||
|
const DocumentAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Teks</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Teks</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<TeksSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="giat-penugasan">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<Label>Audio Visual</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<VideoSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Audio</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<AudioSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Foto</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<ImageSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Teks</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<TeksSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="contest">
|
||||||
|
<Card>
|
||||||
|
<div className="py-3">
|
||||||
|
<ContestTable />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentAllPage;
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../../video/audio-visual";
|
||||||
|
|
||||||
|
const ImageAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Image</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Image</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<ImageSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageAllPage;
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../audio-visual";
|
||||||
|
|
||||||
|
const VideoAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Video</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio Visual</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<VideoSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="giat-penugasan">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<Label>Audio Visual</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<VideoSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Audio</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<AudioSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Foto</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<ImageSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Teks</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<TeksSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="contest">
|
||||||
|
<Card>
|
||||||
|
<div className="py-3">
|
||||||
|
<ContestTable />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoAllPage;
|
||||||
|
|
@ -28,7 +28,7 @@ const VideoSliderPage = () => {
|
||||||
}, [allVideoData]);
|
}, [allVideoData]);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const response = await listCuratedContent(search, limit, page - 1, 1, "2");
|
const response = await listCuratedContent(search, limit, page - 1, 2, "1");
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
||||||
const data = response?.data?.data;
|
const data = response?.data?.data;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Search, UploadIcon } from "lucide-react";
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
@ -73,19 +73,69 @@ const CuratedContentPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-5 pb-3">
|
<div className="ml-5 pb-3">
|
||||||
<Label>Audio Visual</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio Visual</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/video/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<VideoSliderPage />
|
<VideoSliderPage />
|
||||||
</div>
|
</div>
|
||||||
<Label>Audio</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/audio/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<AudioSliderPage />
|
<AudioSliderPage />
|
||||||
</div>
|
</div>
|
||||||
<Label>Foto</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Foto</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/image/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<ImageSliderPage />
|
<ImageSliderPage />
|
||||||
</div>
|
</div>
|
||||||
<Label>Teks</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Teks</Label>
|
||||||
|
<Label>
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/document/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<TeksSliderPage />
|
<TeksSliderPage />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import {
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import { getBlog, postBlog } from "@/service/blog/blog";
|
import { getBlog, postBlog } from "@/service/blog/blog";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -76,6 +77,13 @@ const initialCategories: Category[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ViewEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/view-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
export default function FormBlogDetail() {
|
export default function FormBlogDetail() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -236,12 +244,7 @@ export default function FormBlogDetail() {
|
||||||
control={control}
|
control={control}
|
||||||
name="narration"
|
name="narration"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<JoditEditor
|
<ViewEditor initialData={detail?.narration} />
|
||||||
ref={editor}
|
|
||||||
value={detail.narration}
|
|
||||||
onChange={onChange}
|
|
||||||
className="dark:text-black"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.narration?.message && (
|
{errors.narration?.message && (
|
||||||
|
|
@ -300,7 +303,7 @@ export default function FormBlogDetail() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="w-4/12">
|
<div className="w-4/12">
|
||||||
<Card className=" h-[600px]">
|
<Card className=" h-[650px]">
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,10 @@ import {
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import { getBlog, postBlog } from "@/service/blog/blog";
|
import { getBlog, postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
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" }),
|
||||||
|
|
@ -38,7 +40,7 @@ const taskSchema = z.object({
|
||||||
narration: z
|
narration: z
|
||||||
.string()
|
.string()
|
||||||
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||||
categoryName: z.string().min(1, { message: "Kategori diperlukan" }),
|
categoryId: z.string().min(1, { message: "Kategori diperlukan" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Category = {
|
type Category = {
|
||||||
|
|
@ -77,6 +79,13 @@ const initialCategories: Category[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const CustomEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/custom-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
export default function FormBlogUpdate() {
|
export default function FormBlogUpdate() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -98,6 +107,9 @@ export default function FormBlogUpdate() {
|
||||||
|
|
||||||
const [detail, setDetail] = useState<Detail>();
|
const [detail, setDetail] = useState<Detail>();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||||
|
const [preview, setPreview] = useState<string | null>(null);
|
||||||
|
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
|
|
@ -117,18 +129,6 @@ export default function FormBlogUpdate() {
|
||||||
resolver: zodResolver(taskSchema),
|
resolver: zodResolver(taskSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveTag = (index: any) => {
|
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (event.target.files) {
|
|
||||||
const files = Array.from(event.target.files);
|
|
||||||
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
|
|
||||||
console.log("DATAFILE::", selectedFiles);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveImage = (index: number) => {
|
const handleRemoveImage = (index: number) => {
|
||||||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
@ -141,15 +141,20 @@ export default function FormBlogUpdate() {
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
|
|
||||||
// Set categoryId dari API ke form dan Select
|
if (details?.tags) {
|
||||||
setValue("categoryName", details?.categoryName);
|
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||||
setSelectedTarget(details?.categoryId); // Untuk dropdown
|
}
|
||||||
|
|
||||||
|
setValue("categoryId", details?.categoryName);
|
||||||
|
setSelectedTarget(details?.categoryId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
const save = async (data: TaskSchema) => {
|
const save = async (data: TaskSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
id: detail?.id,
|
id: detail?.id,
|
||||||
|
|
@ -158,7 +163,7 @@ export default function FormBlogUpdate() {
|
||||||
categoryId: selectedTarget,
|
categoryId: selectedTarget,
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
metadata: data.metadata,
|
metadata: data.metadata,
|
||||||
tags: "siap",
|
tags: finalTags,
|
||||||
isDraft,
|
isDraft,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -166,6 +171,30 @@ export default function FormBlogUpdate() {
|
||||||
console.log("Form Data Submitted:", requestData);
|
console.log("Form Data Submitted:", requestData);
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
MySwal.fire("Error", response?.message, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blogId = response?.data?.data.id;
|
||||||
|
if (blogId) {
|
||||||
|
if (thumbnail) {
|
||||||
|
const formMedia = new FormData();
|
||||||
|
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
|
||||||
|
console.log("FormMedia:", formMedia.get("file"));
|
||||||
|
|
||||||
|
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
||||||
|
|
||||||
|
if (responseThumbnail?.error) {
|
||||||
|
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("No thumbnail to upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Sukses",
|
||||||
text: "Data berhasil disimpan.",
|
text: "Data berhasil disimpan.",
|
||||||
|
|
@ -193,6 +222,17 @@ export default function FormBlogUpdate() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setThumbnail(file); // Simpan file ke state
|
||||||
|
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
|
||||||
|
console.log("Selected Thumbnail:", file);
|
||||||
|
} else {
|
||||||
|
console.log("No file selected");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handlePublish = () => {
|
const handlePublish = () => {
|
||||||
setIsDraft(false);
|
setIsDraft(false);
|
||||||
};
|
};
|
||||||
|
|
@ -201,6 +241,29 @@ export default function FormBlogUpdate() {
|
||||||
setIsDraft(false);
|
setIsDraft(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 ? (
|
||||||
|
|
@ -238,11 +301,9 @@ export default function FormBlogUpdate() {
|
||||||
control={control}
|
control={control}
|
||||||
name="narration"
|
name="narration"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<JoditEditor
|
<CustomEditor
|
||||||
ref={editor}
|
|
||||||
value={detail.narration}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="dark:text-black"
|
initialData={detail?.narration || value}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -302,31 +363,39 @@ export default function FormBlogUpdate() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="w-4/12">
|
<div className="w-4/12">
|
||||||
<Card className=" h-[600px]">
|
<Card className=" h-[700px]">
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||||
<Input
|
<input
|
||||||
id="fileInput"
|
id="fileInput"
|
||||||
type="file"
|
type="file"
|
||||||
// onChange={(e) => {
|
onChange={handleImageChange}
|
||||||
// const file = e.target.files[0];
|
|
||||||
// if (file) {
|
|
||||||
// console.log("Selected File:", file);
|
|
||||||
// // Tambahkan logika jika diperlukan, misalnya upload file ke server
|
|
||||||
// }
|
|
||||||
// }}
|
|
||||||
className=""
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="mt-3 px-3">
|
{preview ? (
|
||||||
<Label>Pratinjau Gambar Utama</Label>
|
// Menampilkan pratinjau gambar yang baru dipilih
|
||||||
<Card className="mt-2">
|
<div className="mt-3 px-3">
|
||||||
<img
|
<img
|
||||||
src={detail.thumbnailLink}
|
src={preview}
|
||||||
alt="Thumbnail Gambar Utama"
|
alt="Thumbnail Preview"
|
||||||
className="w-full h-auto rounded"
|
className="w-full h-auto rounded"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</div>
|
||||||
|
) : (
|
||||||
|
// Menampilkan gambar dari `detail.thumbnailLink` jika tidak ada file yang dipilih
|
||||||
|
detail?.thumbnailLink && (
|
||||||
|
<div className="mt-3 px-3">
|
||||||
|
<Label>Pratinjau Gambar Utama</Label>
|
||||||
|
<Card className="mt-2">
|
||||||
|
<img
|
||||||
|
src={detail.thumbnailLink}
|
||||||
|
alt="Thumbnail Gambar Utama"
|
||||||
|
className="w-full h-auto rounded"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 mt-6">
|
<div className="px-3 py-3 mt-6">
|
||||||
<label
|
<label
|
||||||
|
|
@ -336,7 +405,7 @@ export default function FormBlogUpdate() {
|
||||||
Kategori
|
Kategori
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
name="categoryName"
|
name="categoryId"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -350,12 +419,9 @@ export default function FormBlogUpdate() {
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{categories.map((category) => (
|
{categories.map((category: any) => (
|
||||||
<SelectItem
|
<SelectItem key={category.id} value={category.id}>
|
||||||
key={category.id}
|
{category.categoryName}
|
||||||
value={category.categoryName}
|
|
||||||
>
|
|
||||||
{category.id}
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -366,14 +432,34 @@ export default function FormBlogUpdate() {
|
||||||
<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>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ import {
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import { postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
|
import { postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { error } from "console";
|
||||||
|
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" }),
|
||||||
|
|
@ -37,7 +40,6 @@ const taskSchema = z.object({
|
||||||
narration: z
|
narration: z
|
||||||
.string()
|
.string()
|
||||||
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||||
tags: z.string().min(1, { message: "Judul diperlukan" }),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type Category = {
|
type Category = {
|
||||||
|
|
@ -64,6 +66,13 @@ const initialCategories: Category[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const CustomEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/custom-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
export default function FormBlog() {
|
export default function FormBlog() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -82,6 +91,7 @@ export default function FormBlog() {
|
||||||
const [isDraft, setIsDraft] = useState(false);
|
const [isDraft, setIsDraft] = useState(false);
|
||||||
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||||
const [preview, setPreview] = useState<string | null>(null);
|
const [preview, setPreview] = useState<string | null>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
|
|
@ -156,6 +166,8 @@ export default function FormBlog() {
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const save = async (data: TaskSchema) => {
|
const save = async (data: TaskSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
|
@ -163,7 +175,7 @@ export default function FormBlog() {
|
||||||
categoryId: selectedTarget,
|
categoryId: selectedTarget,
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
metadata: data.meta,
|
metadata: data.meta,
|
||||||
tags: data.tags,
|
tags: finalTags,
|
||||||
isDraft,
|
isDraft,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -177,19 +189,33 @@ export default function FormBlog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const blogId = response?.data?.data.id;
|
const blogId = response?.data?.data.id;
|
||||||
if (blogId && thumbnail) {
|
if (blogId) {
|
||||||
const formMedia = new FormData();
|
if (thumbnail) {
|
||||||
formMedia.append("file", thumbnail);
|
const formMedia = new FormData();
|
||||||
|
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
|
||||||
|
console.log("FormMedia:", formMedia.get("file"));
|
||||||
|
|
||||||
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
||||||
|
|
||||||
if (responseThumbnail?.error) {
|
if (responseThumbnail?.error) {
|
||||||
MySwal.fire("Error", responseThumbnail?.message, "error");
|
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("No thumbnail to upload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close();
|
||||||
|
|
||||||
MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/en/contributor/blog");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data: TaskSchema) => {
|
const onSubmit = (data: TaskSchema) => {
|
||||||
|
|
@ -198,8 +224,9 @@ export default function FormBlog() {
|
||||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
confirmButtonText: "Simpan",
|
confirmButtonText: "Simpan",
|
||||||
cancelButtonText: "Batal",
|
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
save(data);
|
save(data);
|
||||||
|
|
@ -210,11 +237,11 @@ export default function FormBlog() {
|
||||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
setThumbnail(file);
|
setThumbnail(file); // Simpan file ke state
|
||||||
|
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
|
||||||
console.log("Selected Thumbnail:", file);
|
console.log("Selected Thumbnail:", file);
|
||||||
}
|
} else {
|
||||||
if (file) {
|
console.log("No file selected");
|
||||||
setPreview(URL.createObjectURL(file));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -226,6 +253,19 @@ export default function FormBlog() {
|
||||||
setIsDraft(true);
|
setIsDraft(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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]); // Add new tag
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.value = ""; // Clear input field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="flex lg:flex-row gap-10">
|
<div className="flex lg:flex-row gap-10">
|
||||||
|
|
@ -260,12 +300,7 @@ export default function FormBlog() {
|
||||||
control={control}
|
control={control}
|
||||||
name="narration"
|
name="narration"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<JoditEditor
|
<CustomEditor onChange={onChange} initialData={value} />
|
||||||
ref={editor}
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
className="dark:text-black"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.narration?.message && (
|
{errors.narration?.message && (
|
||||||
|
|
@ -324,10 +359,10 @@ export default function FormBlog() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="w-4/12">
|
<div className="w-4/12">
|
||||||
<Card className=" h-[550px]">
|
<Card className=" h-[600px]">
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
<label htmlFor="fileInput">Gambar Utama</label>
|
||||||
<Input id="fileInput" type="file" onChange={handleImageChange} />
|
<input id="fileInput" type="file" onChange={handleImageChange} />
|
||||||
</div>
|
</div>
|
||||||
{preview && (
|
{preview && (
|
||||||
<div className="mt-3 px-3">
|
<div className="mt-3 px-3">
|
||||||
|
|
@ -366,19 +401,30 @@ export default function FormBlog() {
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<Label>Tags</Label>
|
<Label>Tags</Label>
|
||||||
<Controller
|
<Input
|
||||||
control={control}
|
type="text"
|
||||||
name="tags"
|
id="tags"
|
||||||
render={({ field }) => (
|
placeholder="Add a tag and press Enter"
|
||||||
<Input
|
onKeyDown={handleAddTag}
|
||||||
size="md"
|
ref={inputRef}
|
||||||
type="text"
|
|
||||||
value={field.value}
|
|
||||||
onChange={field.onChange}
|
|
||||||
placeholder="Enter Title"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
<div className="mt-3 ">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||||
|
>
|
||||||
|
{tag}{" "}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveTag(index)}
|
||||||
|
className="remove-tag-button"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
{/* <div className="text-sm text-red-500">
|
{/* <div className="text-sm text-red-500">
|
||||||
{tags.length === 0 && "Please add at least one tag."}
|
{tags.length === 0 && "Please add at least one tag."}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
|
getEscalationDiscussion,
|
||||||
getTicketingDetail,
|
getTicketingDetail,
|
||||||
getTicketingInternalDetail,
|
getTicketingInternalDetail,
|
||||||
getTicketingInternalDiscussion,
|
getTicketingInternalDiscussion,
|
||||||
|
|
@ -28,6 +29,8 @@ import {
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { loading } from "@/lib/swal";
|
||||||
|
import { id } from "date-fns/locale";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -57,9 +60,24 @@ export default function FormDetailEscalation() {
|
||||||
const [detail, setDetail] = useState<any>();
|
const [detail, setDetail] = useState<any>();
|
||||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||||
const [replyVisible, setReplyVisible] = useState(false);
|
const [replyVisible, setReplyVisible] = useState(false);
|
||||||
const [replyMessage, setReplyMessage] = useState("");
|
const [listDiscussion, setListDiscussion] = useState();
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
const [selectedPriority, setSelectedPriority] = useState("");
|
const [selectedPriority, setSelectedPriority] = useState("");
|
||||||
const [selectedStatus, setSelectedStatus] = useState("");
|
const [replyMessage, setReplyMessage] = useState("");
|
||||||
|
const [replies, setReplies] = useState([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Mabes Polri - Approver",
|
||||||
|
message: "test",
|
||||||
|
timestamp: "2024-12-20 00:56:10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Mabes Polri - Approver",
|
||||||
|
message: "balas",
|
||||||
|
timestamp: "2025-01-18 17:42:48",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|
@ -90,53 +108,32 @@ export default function FormDetailEscalation() {
|
||||||
const handleReply = () => {
|
const handleReply = () => {
|
||||||
setReplyVisible((prev) => !prev); // Toggle visibility
|
setReplyVisible((prev) => !prev); // Toggle visibility
|
||||||
};
|
};
|
||||||
|
// useEffect(() => {
|
||||||
|
// async function initState() {
|
||||||
|
// if (id != undefined) {
|
||||||
|
// loading();
|
||||||
|
// const responseGet = await getEscalationDiscussion(id);
|
||||||
|
// close();
|
||||||
|
|
||||||
const handleSendReply = async () => {
|
// console.log("escal data", responseGet?.data);
|
||||||
if (replyMessage.trim() === "") {
|
// setListDiscussion(responseGet?.data?.data);
|
||||||
MySwal.fire({
|
// }
|
||||||
title: "Error",
|
// }
|
||||||
text: "Pesan tidak boleh kosong!",
|
// initState();
|
||||||
icon: "error",
|
// }, [id]);
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
const handleSendReply = () => {
|
||||||
ticketId: id,
|
if (replyMessage.trim() === "") return;
|
||||||
|
|
||||||
|
const newReply = {
|
||||||
|
id: replies.length + 1,
|
||||||
|
name: "Mabes Polri - Approver", // Sesuaikan dengan data dinamis jika ada
|
||||||
message: replyMessage,
|
message: replyMessage,
|
||||||
|
timestamp: new Date().toISOString().slice(0, 19).replace("T", " "),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
setReplies([...replies, newReply]);
|
||||||
const response = await saveTicketInternalReply(data);
|
setReplyMessage("");
|
||||||
|
|
||||||
// Tambahkan balasan baru ke daftar balasan
|
|
||||||
const newReply: replyDetail = {
|
|
||||||
id: response?.data?.id,
|
|
||||||
message: replyMessage,
|
|
||||||
createdAt: response?.data?.createdAt,
|
|
||||||
messageFrom: response?.data?.messageFrom,
|
|
||||||
messageTo: response?.data?.messageTo,
|
|
||||||
};
|
|
||||||
|
|
||||||
setTicketReply((prevReplies) => [newReply, ...prevReplies]);
|
|
||||||
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Sukses",
|
|
||||||
text: "Pesan berhasil dikirim.",
|
|
||||||
icon: "success",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset input dan sembunyikan form balasan
|
|
||||||
setReplyMessage("");
|
|
||||||
setReplyVisible(false);
|
|
||||||
} catch (error) {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Error",
|
|
||||||
text: "Gagal mengirim balasan.",
|
|
||||||
icon: "error",
|
|
||||||
});
|
|
||||||
console.error("Error sending reply:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -244,26 +241,46 @@ export default function FormDetailEscalation() {
|
||||||
</p>
|
</p>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 px-3 mb-3">
|
<div className="mx-3 my-3">
|
||||||
<Label htmlFor="replyMessage">Tulis Pesan</Label>
|
<h3 className="text-gray-700 font-medium">Tanggapan</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{replies.map((reply) => (
|
||||||
|
<div key={reply.id} className="border-b pb-2">
|
||||||
|
<p className="font-semibold text-gray-800">{reply.name}</p>
|
||||||
|
<p className="text-gray-600">{reply.message}</p>
|
||||||
|
<p className="text-sm text-gray-400">{reply.timestamp}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-3">
|
||||||
|
<label
|
||||||
|
htmlFor="replyMessage"
|
||||||
|
className="block text-gray-700 font-medium mb-2"
|
||||||
|
>
|
||||||
|
Tulis Tanggapan Anda
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="replyMessage"
|
id="replyMessage"
|
||||||
className="w-full h-24 border rounded-md p-2"
|
className="w-full h-24 border border-gray-300 rounded-md p-2"
|
||||||
value={replyMessage}
|
value={replyMessage}
|
||||||
onChange={(e) => setReplyMessage(e.target.value)}
|
onChange={(e) => setReplyMessage(e.target.value)}
|
||||||
placeholder="Tulis pesan di sini..."
|
placeholder="Tulis tanggapan anda di sini..."
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end gap-3 mt-2">
|
<div className="flex justify-end gap-3 mt-2 mb-3">
|
||||||
<Button
|
<button
|
||||||
onClick={() => setReplyVisible(false)}
|
onClick={() => setReplyMessage("")}
|
||||||
color="default"
|
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md"
|
||||||
variant="outline"
|
|
||||||
>
|
>
|
||||||
Batal
|
Batal
|
||||||
</Button>
|
</button>
|
||||||
<Button onClick={handleSendReply} color="primary">
|
<button
|
||||||
Kirim
|
onClick={handleSendReply}
|
||||||
</Button>
|
className="px-4 py-2 bg-blue-600 text-white rounded-md"
|
||||||
|
>
|
||||||
|
Kirim Pesan
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import {
|
||||||
saveTicketInternalReply,
|
saveTicketInternalReply,
|
||||||
} from "@/service/communication/communication";
|
} from "@/service/communication/communication";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { list } from "postcss";
|
||||||
|
import { htmlToString } from "@/utils/globals";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -72,6 +74,19 @@ export type replyDetail = {
|
||||||
fullname: string;
|
fullname: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export type internalDetail = {
|
||||||
|
id: number;
|
||||||
|
message: string;
|
||||||
|
createdAt: string;
|
||||||
|
createdBy: {
|
||||||
|
id: number;
|
||||||
|
fullname: string;
|
||||||
|
};
|
||||||
|
sendTo: {
|
||||||
|
id: number;
|
||||||
|
fullname: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default function FormDetailInternal() {
|
export default function FormDetailInternal() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
@ -79,6 +94,9 @@ export default function FormDetailInternal() {
|
||||||
|
|
||||||
const [detail, setDetail] = useState<taskDetail>();
|
const [detail, setDetail] = useState<taskDetail>();
|
||||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||||
|
const [ticketInternal, setTicketInternal] = useState<internalDetail | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [replyVisible, setReplyVisible] = useState(false);
|
const [replyVisible, setReplyVisible] = useState(false);
|
||||||
const [replyMessage, setReplyMessage] = useState("");
|
const [replyMessage, setReplyMessage] = useState("");
|
||||||
const [selectedPriority, setSelectedPriority] = useState("");
|
const [selectedPriority, setSelectedPriority] = useState("");
|
||||||
|
|
@ -96,6 +114,7 @@ export default function FormDetailInternal() {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
const response = await getTicketingInternalDetail(id);
|
const response = await getTicketingInternalDetail(id);
|
||||||
|
setTicketInternal(response?.data?.data || null);
|
||||||
setDetail(response?.data?.data);
|
setDetail(response?.data?.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,6 +222,7 @@ export default function FormDetailInternal() {
|
||||||
<p className="p-4 bg-slate-300 rounded-t-xl text-lg font-semibold">
|
<p className="p-4 bg-slate-300 rounded-t-xl text-lg font-semibold">
|
||||||
Ticket #{detail?.referenceNumber}
|
Ticket #{detail?.referenceNumber}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{ticketReply?.map((list) => (
|
{ticketReply?.map((list) => (
|
||||||
<div key={list.id} className="flex flex-col">
|
<div key={list.id} className="flex flex-col">
|
||||||
<div className="flex flex-row gap-3 bg-sky-100 p-4 items-center">
|
<div className="flex flex-row gap-3 bg-sky-100 p-4 items-center">
|
||||||
|
|
@ -229,6 +249,38 @@ export default function FormDetailInternal() {
|
||||||
<p className="p-4 bg-white text-sm">{list.message}</p>
|
<p className="p-4 bg-white text-sm">{list.message}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{ticketInternal && (
|
||||||
|
<div key={ticketInternal.id} className="flex flex-col">
|
||||||
|
<div className="flex flex-row gap-3 bg-sky-100 p-4 items-center">
|
||||||
|
<Icon icon="qlementine-icons:user-16" width={36} />
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span className="font-bold text-sm">
|
||||||
|
{ticketInternal?.createdBy?.fullname}
|
||||||
|
</span>{" "}
|
||||||
|
mengirimkan pesan untuk{" "}
|
||||||
|
<span className="font-bold text-sm">
|
||||||
|
{ticketInternal?.sendTo?.fullname}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className="text-xs">
|
||||||
|
{`${new Date(ticketInternal?.createdAt).getDate()}-${
|
||||||
|
new Date(ticketInternal?.createdAt).getMonth() + 1
|
||||||
|
}-${new Date(
|
||||||
|
ticketInternal?.createdAt
|
||||||
|
).getFullYear()} ${new Date(
|
||||||
|
ticketInternal?.createdAt
|
||||||
|
).getHours()}:${new Date(
|
||||||
|
ticketInternal?.createdAt
|
||||||
|
).getMinutes()}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="p-4 bg-white text-sm">
|
||||||
|
{htmlToString(ticketInternal.message)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{detail !== undefined && (
|
{detail !== undefined && (
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { error } from "@/lib/swal";
|
import { error } from "@/lib/swal";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,13 @@ interface PlacementData {
|
||||||
placements: string;
|
placements: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileType = {
|
||||||
|
contentId: number;
|
||||||
|
contentFile: string;
|
||||||
|
thumbnailFileUrl: string;
|
||||||
|
fileName: string;
|
||||||
|
};
|
||||||
|
|
||||||
const CustomEditor = dynamic(
|
const CustomEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/custom-editor");
|
return import("@/components/editor/custom-editor");
|
||||||
|
|
@ -122,7 +129,7 @@ export default function FormConvertSPIT() {
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [tags, setTags] = useState<any[]>([]);
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
const [detail, setDetail] = useState<Detail>();
|
const [detail, setDetail] = useState<any>();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||||
|
|
@ -150,7 +157,9 @@ export default function FormConvertSPIT() {
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const [placementLength, setPlacementLength] = useState([]);
|
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||||
|
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||||
|
const [files, setFiles] = useState<FileType[]>([]);
|
||||||
|
|
||||||
const options: Option[] = [
|
const options: Option[] = [
|
||||||
{ id: "all", label: "SEMUA" },
|
{ id: "all", label: "SEMUA" },
|
||||||
|
|
@ -201,6 +210,17 @@ export default function FormConvertSPIT() {
|
||||||
initState();
|
initState();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
userLevelId != undefined &&
|
||||||
|
roleId != undefined &&
|
||||||
|
userLevelId == "216" &&
|
||||||
|
roleId == "3"
|
||||||
|
) {
|
||||||
|
setIsUserMabesApprover(true);
|
||||||
|
}
|
||||||
|
}, [userLevelId, roleId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
userLevelId != undefined &&
|
userLevelId != undefined &&
|
||||||
|
|
@ -237,6 +257,47 @@ export default function FormConvertSPIT() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setupPlacement = (
|
||||||
|
index: number,
|
||||||
|
placement: string,
|
||||||
|
checked: boolean
|
||||||
|
) => {
|
||||||
|
let temp = [...filePlacements];
|
||||||
|
if (checked) {
|
||||||
|
if (placement === "all") {
|
||||||
|
temp[index] = ["all", "mabes", "polda", "international"];
|
||||||
|
} else {
|
||||||
|
const now = temp[index];
|
||||||
|
now.push(placement);
|
||||||
|
if (now.length === 3 && !now.includes("all")) {
|
||||||
|
now.push("all");
|
||||||
|
}
|
||||||
|
temp[index] = now;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (placement === "all") {
|
||||||
|
temp[index] = [];
|
||||||
|
} else {
|
||||||
|
const now = temp[index].filter((a) => a !== placement);
|
||||||
|
console.log("now", now);
|
||||||
|
temp[index] = now;
|
||||||
|
if (now.length === 3 && now.includes("all")) {
|
||||||
|
const newData = now.filter((b) => b !== "all");
|
||||||
|
temp[index] = newData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFilePlacements(temp);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupPlacementCheck = (length: number) => {
|
||||||
|
const temp = [];
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
temp.push([]);
|
||||||
|
}
|
||||||
|
setFilePlacements(temp);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -244,6 +305,8 @@ export default function FormConvertSPIT() {
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
|
setFiles(details?.contentList);
|
||||||
|
setupPlacementCheck(details?.contentList?.length);
|
||||||
|
|
||||||
const filesData = details.contentList || [];
|
const filesData = details.contentList || [];
|
||||||
const fileUrls = filesData.map((file: { contentFile: string }) =>
|
const fileUrls = filesData.map((file: { contentFile: string }) =>
|
||||||
|
|
@ -272,49 +335,6 @@ export default function FormConvertSPIT() {
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
const getPlacement = (): PlacementData[] => {
|
|
||||||
return tempFile
|
|
||||||
.filter((file: FileData) => (file.placement || []).length > 0) // Gunakan default array
|
|
||||||
.map((file: FileData) => ({
|
|
||||||
mediaFileId: Number(file.contentId),
|
|
||||||
placements: (file.placement || []).join(","), // Gunakan default array
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const setupPlacement = (value: string, id: number): void => {
|
|
||||||
const updatedFiles = tempFile.map((file: FileData) => {
|
|
||||||
if (file.contentId === id) {
|
|
||||||
const currentPlacement = file.placement || [];
|
|
||||||
if (currentPlacement.includes(value)) {
|
|
||||||
// Remove the placement value
|
|
||||||
file.placement = currentPlacement.filter((val) => val !== value);
|
|
||||||
} else {
|
|
||||||
// Add the placement value
|
|
||||||
file.placement =
|
|
||||||
value === "all"
|
|
||||||
? ["all", "mabes", "polda", "international"]
|
|
||||||
: [...currentPlacement, value];
|
|
||||||
if (file.placement.includes("all") && value !== "all") {
|
|
||||||
file.placement = file.placement.filter((val) => val !== "all");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
});
|
|
||||||
|
|
||||||
const placementLength = updatedFiles.reduce(
|
|
||||||
(acc: any, file: any) => acc + (file.placement?.length || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
setTempFile(
|
|
||||||
updatedFiles.sort((a: any, b: any) => a.contentId - b.contentId)
|
|
||||||
);
|
|
||||||
setPlacementLength(placementLength);
|
|
||||||
|
|
||||||
console.log("Updated Files:", updatedFiles);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckboxChangeFile = (contentId: number, value: string) => {
|
const handleCheckboxChangeFile = (contentId: number, value: string) => {
|
||||||
setTempFile((prevTempFile: any) => {
|
setTempFile((prevTempFile: any) => {
|
||||||
return prevTempFile.map((file: any) => {
|
return prevTempFile.map((file: any) => {
|
||||||
|
|
@ -359,12 +379,32 @@ export default function FormConvertSPIT() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPlacement = () => {
|
||||||
|
console.log("getPlaa", filePlacements);
|
||||||
|
const temp = [];
|
||||||
|
for (let i = 0; i < filePlacements?.length; i++) {
|
||||||
|
if (filePlacements[i].length !== 0) {
|
||||||
|
const now = filePlacements[i].filter((a) => a !== "all");
|
||||||
|
const data = {
|
||||||
|
mediaFileId: files[i].contentId,
|
||||||
|
placement: now.join(","),
|
||||||
|
};
|
||||||
|
temp.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
};
|
||||||
|
|
||||||
const save = async (data: {
|
const save = async (data: {
|
||||||
contentTitle: string;
|
contentTitle: string;
|
||||||
contentDescription: string;
|
contentDescription: string;
|
||||||
contentRewriteDescription: string;
|
contentRewriteDescription: string;
|
||||||
contentCreator: string;
|
contentCreator: string;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
|
const temp = [];
|
||||||
|
for (const element of detail.contentList) {
|
||||||
|
temp.push([]);
|
||||||
|
}
|
||||||
const description =
|
const description =
|
||||||
selectedFileType === "original"
|
selectedFileType === "original"
|
||||||
? data.contentDescription
|
? data.contentDescription
|
||||||
|
|
@ -376,15 +416,16 @@ export default function FormConvertSPIT() {
|
||||||
description,
|
description,
|
||||||
htmlDescription: description,
|
htmlDescription: description,
|
||||||
tags: "siap",
|
tags: "siap",
|
||||||
categoryId: selectedCategoryId,
|
categoryId: 1,
|
||||||
publishedFor: publishedFor.join(","),
|
publishedFor: publishedFor.join(","),
|
||||||
creator: data.contentCreator,
|
creator: data.contentCreator,
|
||||||
files: getPlacement(), // Include placement data
|
files: isUserMabesApprover ? getPlacement() : [], // Include placement data
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await convertSPIT(requestData);
|
const response = await convertSPIT(requestData);
|
||||||
console.log("Form Data Submitted:", response);
|
console.log("Form Data Submitted:", response);
|
||||||
|
setFilePlacements(temp);
|
||||||
|
setFiles(detail.files);
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Sukses",
|
||||||
text: "Data berhasil disimpan.",
|
text: "Data berhasil disimpan.",
|
||||||
|
|
@ -699,62 +740,96 @@ export default function FormConvertSPIT() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isMabesApprover ? (
|
{files?.map((file, index) => (
|
||||||
<div className="mt-5">
|
<div
|
||||||
<Label className="text-xl text-black">
|
key={file.contentId}
|
||||||
Penempatan File
|
className="flex flex-row gap-2 items-center my-3"
|
||||||
</Label>
|
>
|
||||||
{detailThumb.map((data: any) => (
|
<img
|
||||||
<div
|
src={file.contentFile}
|
||||||
key={data.contentId}
|
className="w-[150px] rounded-md"
|
||||||
className="flex items-center gap-3 mt-2"
|
/>
|
||||||
>
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<img
|
<div className="flex justify-between text-sm">
|
||||||
className="object-cover w-36 h-32"
|
{file.fileName}
|
||||||
src={data}
|
{/* <a
|
||||||
alt={`Thumbnail ${data.contentId}`}
|
onClick={() =>
|
||||||
/>
|
handleDeleteFileApproval(file.id)
|
||||||
<div className="flex flex-row gap-3 items-center">
|
}
|
||||||
{[
|
>
|
||||||
"all",
|
<Icon icon="humbleicons:times" color="red" />
|
||||||
"mabes",
|
</a> */}
|
||||||
"polda",
|
</div>
|
||||||
"satker",
|
|
||||||
"internasional",
|
<div className="flex flex-row gap-2">
|
||||||
].map((value) => (
|
<div className="flex items-center space-x-2">
|
||||||
<label
|
<Checkbox
|
||||||
key={value}
|
id="terms"
|
||||||
className="text-blue-500 cursor-pointer flex items-center gap-1"
|
value="all"
|
||||||
>
|
checked={filePlacements[index]?.includes("all")}
|
||||||
<input
|
onCheckedChange={(e) =>
|
||||||
type="checkbox"
|
setupPlacement(index, "all", Boolean(e))
|
||||||
name="placement"
|
}
|
||||||
value={value}
|
/>
|
||||||
onChange={() =>
|
<label
|
||||||
handleCheckboxChangeFile(
|
htmlFor="terms"
|
||||||
data.contentId,
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
value
|
>
|
||||||
)
|
Semua
|
||||||
}
|
</label>
|
||||||
checked={
|
</div>
|
||||||
tempFile
|
<div className="flex items-center space-x-2">
|
||||||
.find(
|
<Checkbox
|
||||||
(file: FileData) =>
|
id="terms"
|
||||||
file.contentId === data.contentId
|
checked={filePlacements[index]?.includes("mabes")}
|
||||||
)
|
onCheckedChange={(e) =>
|
||||||
?.placement?.includes(value) || false
|
setupPlacement(index, "mabes", Boolean(e))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
<label
|
||||||
</label>
|
htmlFor="terms"
|
||||||
))}
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Nasional
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={filePlacements[index]?.includes("polda")}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
setupPlacement(index, "polda", Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Wilayah
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={filePlacements[index]?.includes(
|
||||||
|
"international"
|
||||||
|
)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
setupPlacement(index, "international", Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Internasional
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
))}
|
||||||
""
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -797,14 +872,16 @@ export default function FormConvertSPIT() {
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Tag</Label>
|
<Label>Tag</Label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{detail?.contentTag?.split(",").map((tag, index) => (
|
{detail?.contentTag
|
||||||
<Badge
|
?.split(",")
|
||||||
key={index}
|
.map((tag: any, index: any) => (
|
||||||
className="border rounded-md px-2 py-2"
|
<Badge
|
||||||
>
|
key={index}
|
||||||
{tag.trim()}
|
className="border rounded-md px-2 py-2"
|
||||||
</Badge>
|
>
|
||||||
))}
|
{tag.trim()}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 ===
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { error } from "@/lib/swal";
|
import { error } from "@/lib/swal";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -683,7 +684,7 @@ export default function FormVideoDetail() {
|
||||||
<Icon icon="humbleicons:times" color="red" />
|
<Icon icon="humbleicons:times" color="red" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{isUserMabesApprover &&
|
{isUserMabesApprover && (
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
@ -760,7 +761,7 @@ export default function FormVideoDetail() {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -28,7 +28,19 @@ import MapHome from "@/components/maps/MapHome";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
import {
|
||||||
|
detailSchedule,
|
||||||
|
listScheduleNext,
|
||||||
|
listScheduleToday,
|
||||||
|
postSchedule,
|
||||||
|
} from "@/service/schedule/schedule";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { formatDate } from "@fullcalendar/core/index.js";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -56,6 +68,8 @@ export default function FormEventDetail() {
|
||||||
const [startTime, setStartTime] = useState("08:00");
|
const [startTime, setStartTime] = useState("08:00");
|
||||||
const [endTime, setEndTime] = useState("09:00");
|
const [endTime, setEndTime] = useState("09:00");
|
||||||
const [date, setDate] = useState<DateRange | undefined>();
|
const [date, setDate] = useState<DateRange | undefined>();
|
||||||
|
const [todayList, setTodayList] = useState([]);
|
||||||
|
const [nextDayList, setNextDayList] = useState([]);
|
||||||
|
|
||||||
const [detail, setDetail] = useState<Detail>();
|
const [detail, setDetail] = useState<Detail>();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
@ -72,6 +86,16 @@ export default function FormEventDetail() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getDataByDate() {
|
||||||
|
const resToday = await listScheduleToday();
|
||||||
|
const today = resToday?.data?.data;
|
||||||
|
setTodayList(today);
|
||||||
|
const resNext = await listScheduleNext();
|
||||||
|
const next = resNext?.data?.data;
|
||||||
|
|
||||||
|
setNextDayList(next);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -89,6 +113,7 @@ export default function FormEventDetail() {
|
||||||
setStartTime(details.startTime);
|
setStartTime(details.startTime);
|
||||||
setEndTime(details.endTime);
|
setEndTime(details.endTime);
|
||||||
}
|
}
|
||||||
|
getDataByDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
|
|
@ -315,7 +340,66 @@ export default function FormEventDetail() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full lg:w-3/12">
|
<Card className="w-full lg:w-3/12">
|
||||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
<Accordion type="single" collapsible>
|
||||||
|
{/* Jadwal Hari Ini */}
|
||||||
|
<AccordionItem value="today">
|
||||||
|
<AccordionTrigger className="font-semibold">
|
||||||
|
Jadwal Hari Ini
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{todayList?.length > 0 ? (
|
||||||
|
<ul className="list-disc ml-4">
|
||||||
|
{todayList.map((item: any, index) => (
|
||||||
|
<li key={index} className="py-1">
|
||||||
|
{item.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
{/* Jadwal Selanjutnya */}
|
||||||
|
<AccordionItem value="next">
|
||||||
|
<AccordionTrigger className="font-semibold">
|
||||||
|
Jadwal Selanjutnya
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{nextDayList?.length > 0 ? (
|
||||||
|
<div className="list-disc ">
|
||||||
|
{nextDayList.map((item: any, index) => (
|
||||||
|
<div key={index} className="">
|
||||||
|
<li className="text-base font-semibold">{item.title}</li>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<CalendarIcon size={20} />
|
||||||
|
{formatDate(item?.startDate)}-
|
||||||
|
{formatDate(item?.endDate)}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<Clock1 size={20} />
|
||||||
|
{item?.startTime}-{item?.endTime}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5 flex items-center my-2">
|
||||||
|
<MapPin size={50} />
|
||||||
|
{item?.address}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5">Disampaikan oleh:</p>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<User2 />
|
||||||
|
{item?.speakerTitle} {item?.speakerName}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
// <p>{item.startDate}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
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 {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -40,6 +39,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||||
import { error } from "@/lib/swal";
|
import { error } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { postSchedule } from "@/service/schedule/schedule";
|
import { postSchedule } from "@/service/schedule/schedule";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -28,7 +28,12 @@ import MapHome from "@/components/maps/MapHome";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
import {
|
||||||
|
detailSchedule,
|
||||||
|
listScheduleNext,
|
||||||
|
listScheduleToday,
|
||||||
|
postSchedule,
|
||||||
|
} from "@/service/schedule/schedule";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -36,6 +41,13 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { formatDate } from "@fullcalendar/core/index.js";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -64,6 +76,8 @@ export default function FormDetailPressRillis() {
|
||||||
const [endTime, setEndTime] = useState("09:00");
|
const [endTime, setEndTime] = useState("09:00");
|
||||||
const [date, setDate] = useState<DateRange | undefined>();
|
const [date, setDate] = useState<DateRange | undefined>();
|
||||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||||
|
const [todayList, setTodayList] = useState([]);
|
||||||
|
const [nextDayList, setNextDayList] = useState([]);
|
||||||
|
|
||||||
const [detail, setDetail] = useState<Detail>();
|
const [detail, setDetail] = useState<Detail>();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
@ -80,6 +94,16 @@ export default function FormDetailPressRillis() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getDataByDate() {
|
||||||
|
const resToday = await listScheduleToday();
|
||||||
|
const today = resToday?.data?.data;
|
||||||
|
setTodayList(today);
|
||||||
|
const resNext = await listScheduleNext();
|
||||||
|
const next = resNext?.data?.data;
|
||||||
|
|
||||||
|
setNextDayList(next);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -97,6 +121,7 @@ export default function FormDetailPressRillis() {
|
||||||
setStartTime(details.startTime);
|
setStartTime(details.startTime);
|
||||||
setEndTime(details.endTime);
|
setEndTime(details.endTime);
|
||||||
}
|
}
|
||||||
|
getDataByDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
|
|
@ -340,7 +365,66 @@ export default function FormDetailPressRillis() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full lg:w-3/12">
|
<Card className="w-full lg:w-3/12">
|
||||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
<Accordion type="single" collapsible>
|
||||||
|
{/* Jadwal Hari Ini */}
|
||||||
|
<AccordionItem value="today">
|
||||||
|
<AccordionTrigger className="font-semibold">
|
||||||
|
Jadwal Hari Ini
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{todayList?.length > 0 ? (
|
||||||
|
<ul className="list-disc ml-4">
|
||||||
|
{todayList.map((item: any, index) => (
|
||||||
|
<li key={index} className="py-1">
|
||||||
|
{item.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
{/* Jadwal Selanjutnya */}
|
||||||
|
<AccordionItem value="next">
|
||||||
|
<AccordionTrigger className="font-semibold">
|
||||||
|
Jadwal Selanjutnya
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{nextDayList?.length > 0 ? (
|
||||||
|
<div className="list-disc ">
|
||||||
|
{nextDayList.map((item: any, index) => (
|
||||||
|
<div key={index} className="">
|
||||||
|
<li className="text-base font-semibold">{item.title}</li>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<CalendarIcon size={20} />
|
||||||
|
{formatDate(item?.startDate)}-
|
||||||
|
{formatDate(item?.endDate)}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<Clock1 size={20} />
|
||||||
|
{item?.startTime}-{item?.endTime}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5 flex items-center my-2">
|
||||||
|
<MapPin size={50} />
|
||||||
|
{item?.address}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5">Disampaikan oleh:</p>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<User2 />
|
||||||
|
{item?.speakerTitle} {item?.speakerName}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
// <p>{item.startDate}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Clock1, Locate, MapPin, User2 } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -28,7 +28,20 @@ import MapHome from "@/components/maps/MapHome";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
import {
|
||||||
|
detailSchedule,
|
||||||
|
listScheduleNext,
|
||||||
|
listScheduleToday,
|
||||||
|
postSchedule,
|
||||||
|
} from "@/service/schedule/schedule";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
|
import { formatDate } from "@fullcalendar/core/index.js";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -56,6 +69,8 @@ export default function FormDetailPressConference() {
|
||||||
const [startTime, setStartTime] = useState("08:00");
|
const [startTime, setStartTime] = useState("08:00");
|
||||||
const [endTime, setEndTime] = useState("09:00");
|
const [endTime, setEndTime] = useState("09:00");
|
||||||
const [date, setDate] = useState<DateRange | undefined>();
|
const [date, setDate] = useState<DateRange | undefined>();
|
||||||
|
const [todayList, setTodayList] = useState([]);
|
||||||
|
const [nextDayList, setNextDayList] = useState([]);
|
||||||
|
|
||||||
const [detail, setDetail] = useState<Detail>();
|
const [detail, setDetail] = useState<Detail>();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
@ -72,6 +87,16 @@ export default function FormDetailPressConference() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getDataByDate() {
|
||||||
|
const resToday = await listScheduleToday();
|
||||||
|
const today = resToday?.data?.data;
|
||||||
|
setTodayList(today);
|
||||||
|
const resNext = await listScheduleNext();
|
||||||
|
const next = resNext?.data?.data;
|
||||||
|
|
||||||
|
setNextDayList(next);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -89,6 +114,7 @@ export default function FormDetailPressConference() {
|
||||||
setStartTime(details.startTime);
|
setStartTime(details.startTime);
|
||||||
setEndTime(details.endTime);
|
setEndTime(details.endTime);
|
||||||
}
|
}
|
||||||
|
getDataByDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
|
|
@ -315,7 +341,66 @@ export default function FormDetailPressConference() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full lg:w-3/12">
|
<Card className="w-full lg:w-3/12">
|
||||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
<Accordion type="single" collapsible>
|
||||||
|
{/* Jadwal Hari Ini */}
|
||||||
|
<AccordionItem value="today">
|
||||||
|
<AccordionTrigger className="font-semibold">
|
||||||
|
Jadwal Hari Ini
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{todayList?.length > 0 ? (
|
||||||
|
<ul className="list-disc ml-4">
|
||||||
|
{todayList.map((item: any, index) => (
|
||||||
|
<li key={index} className="py-1">
|
||||||
|
{item.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
{/* Jadwal Selanjutnya */}
|
||||||
|
<AccordionItem value="next">
|
||||||
|
<AccordionTrigger className="font-semibold">
|
||||||
|
Jadwal Selanjutnya
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{nextDayList?.length > 0 ? (
|
||||||
|
<div className="list-disc ">
|
||||||
|
{nextDayList.map((item: any, index) => (
|
||||||
|
<div key={index} className="">
|
||||||
|
<li className="text-base font-semibold">{item.title}</li>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<CalendarIcon size={20} />
|
||||||
|
{formatDate(item?.startDate)}-
|
||||||
|
{formatDate(item?.endDate)}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<Clock1 size={20} />
|
||||||
|
{item?.startTime}-{item?.endTime}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5 flex items-center my-2">
|
||||||
|
<MapPin size={50} />
|
||||||
|
{item?.address}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm ml-5">Disampaikan oleh:</p>
|
||||||
|
<p className="text-sm ml-5 flex my-2">
|
||||||
|
<User2 />
|
||||||
|
{item?.speakerTitle} {item?.speakerName}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
// <p>{item.startDate}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
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 { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
|
|
@ -30,6 +29,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||||
import { error } from "@/lib/swal";
|
import { error } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { postSchedule } from "@/service/schedule/schedule";
|
import { postSchedule } from "@/service/schedule/schedule";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -167,10 +167,10 @@ export default function FormPressConference() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<div className="mt-5">
|
<div className="mt-4 mb-3">
|
||||||
<Label>Live Streaming</Label>
|
<Label>Live Streaming</Label>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<p>Aktifkan fitur live streaming</p>
|
<Label>Aktifkan fitur live streaming</Label>
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={isLiveStreamingEnabled}
|
defaultChecked={isLiveStreamingEnabled}
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -184,7 +184,7 @@ export default function FormPressConference() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLiveStreamingEnabled && (
|
{isLiveStreamingEnabled && (
|
||||||
<div className="mt-1">
|
<div className="mb-2 mt-1">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="title"
|
name="title"
|
||||||
|
|
@ -206,16 +206,17 @@ export default function FormPressConference() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
<div className="flex flex-col lg:flex-row mb-4 mt-2 items-start lg:items-center justify-between">
|
||||||
<div className="flex flex-col ">
|
<div className="flex flex-col ">
|
||||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
size="md"
|
||||||
id="date"
|
id="date"
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
"w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300",
|
||||||
!date && "text-muted-foreground"
|
!date && "text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -292,7 +293,7 @@ export default function FormPressConference() {
|
||||||
{errors.location?.message}
|
{errors.location?.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
<p className="text-sm mt-4 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||||
<div className="flex flex-col ">
|
<div className="flex flex-col ">
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<Label>Nama Pangkat</Label>
|
<Label>Nama Pangkat</Label>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,16 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { ChevronDown, ChevronUp, DotSquare, TrashIcon } from "lucide-react";
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Dock,
|
||||||
|
DotSquare,
|
||||||
|
ImageIcon,
|
||||||
|
Music,
|
||||||
|
TrashIcon,
|
||||||
|
VideoIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
@ -50,6 +59,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 +175,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 +209,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,11 +243,40 @@ 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 [selectedImage, setSelectedImage] = useState<string | null>(
|
||||||
|
imageUploadedFiles?.[0]?.url || null
|
||||||
|
);
|
||||||
|
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [selectedVideo, setSelectedVideo] = useState<string | null>(
|
||||||
|
videoUploadedFiles?.[0]?.url || null
|
||||||
|
);
|
||||||
|
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [selectedText, setSelectedText] = useState<string | null>(
|
||||||
|
textUploadedFiles?.[0]?.url || null
|
||||||
|
);
|
||||||
|
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [selectedAudio, setSelectedAudio] = useState<string | null>(
|
||||||
|
audioUploadedFiles?.[0]?.url || null
|
||||||
|
);
|
||||||
|
|
||||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
mabes: false,
|
mabes: false,
|
||||||
polda: false,
|
polda: false,
|
||||||
|
satker: false,
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -283,32 +331,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(1),
|
||||||
video: outputSet.has(2),
|
video: outputSet.has(2),
|
||||||
audio: outputSet.has(4),
|
audio: outputSet.has(4),
|
||||||
image: outputSet.has(1),
|
image: outputSet.has(3),
|
||||||
text: outputSet.has(3),
|
text: outputSet.has(5),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [detail?.fileTypeOutput]);
|
}, [detail?.fileTypeOutput]);
|
||||||
|
|
@ -323,75 +399,11 @@ export default function FormTaskDetail() {
|
||||||
mabes: outputSet.has(1),
|
mabes: outputSet.has(1),
|
||||||
polda: outputSet.has(2),
|
polda: outputSet.has(2),
|
||||||
polres: outputSet.has(3),
|
polres: outputSet.has(3),
|
||||||
|
satker: outputSet.has(4),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [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 +457,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 +524,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 +726,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 +799,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 +1030,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,70 +1040,236 @@ 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
|
<div>
|
||||||
accept={{
|
{selectedVideo && (
|
||||||
"mp4/*": [],
|
<Card className="mt-2">
|
||||||
"mov/*": [],
|
<video
|
||||||
}}
|
className="object-fill h-full w-full rounded-md"
|
||||||
maxSize={100}
|
src={selectedVideo}
|
||||||
label="Upload file dengan format .mp4 atau .mov."
|
controls
|
||||||
onDrop={(files) => setImageFiles(files)}
|
title={`Video`} // Mengganti alt dengan title
|
||||||
/>
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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 cursor-pointer"
|
||||||
|
onClick={() => setSelectedVideo(file.url)}
|
||||||
|
>
|
||||||
|
<div className="file-preview">
|
||||||
|
<VideoIcon />
|
||||||
|
</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
|
<div>
|
||||||
accept={{
|
{selectedImage && (
|
||||||
"image/*": [],
|
<Card className="mt-2">
|
||||||
}}
|
<img
|
||||||
maxSize={100}
|
src={selectedImage}
|
||||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
alt="Thumbnail Gambar Utama"
|
||||||
onDrop={(files) => setImageFiles(files)}
|
className="w-full h-auto rounded-md"
|
||||||
/>
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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 cursor-pointer"
|
||||||
|
onClick={() => setSelectedImage(file.url)}
|
||||||
|
>
|
||||||
|
<div className="file-preview">
|
||||||
|
<ImageIcon />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-card-foreground">
|
||||||
|
{file.fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="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
|
<div>
|
||||||
accept={{
|
{selectedText && (
|
||||||
"pdf/*": [],
|
<Card className="mt-2">
|
||||||
}}
|
<iframe
|
||||||
maxSize={100}
|
className="w-full h-96 rounded-md"
|
||||||
label="Upload file dengan format .pdf."
|
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||||
onDrop={(files) => setTextFiles(files)}
|
selectedText
|
||||||
/>
|
)}`}
|
||||||
|
title={"Document"}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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 cursor-pointer"
|
||||||
|
onClick={() => setSelectedText(file.url)}
|
||||||
|
>
|
||||||
|
<div className="file-preview">
|
||||||
|
<Dock />
|
||||||
|
</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
|
<div>
|
||||||
onRecordingComplete={addAudioElement}
|
{selectedAudio && (
|
||||||
audioTrackConstraints={{
|
<Card className="mt-2">
|
||||||
noiseSuppression: true,
|
<div key={selectedAudio}>
|
||||||
echoCancellation: true,
|
<WavesurferPlayer
|
||||||
}}
|
height={500}
|
||||||
downloadOnSavePress={true}
|
waveColor="red"
|
||||||
downloadFileExtension="webm"
|
url={selectedAudio}
|
||||||
/>
|
onReady={onReady}
|
||||||
<FileUploader
|
onPlay={() => setIsPlaying(true)}
|
||||||
accept={{
|
onPause={() => setIsPlaying(false)}
|
||||||
"mp3/*": [],
|
/>
|
||||||
"wav/*": [],
|
</div>
|
||||||
}}
|
<Button
|
||||||
maxSize={100}
|
size="sm"
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
type="button"
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onClick={onPlayPause}
|
||||||
className="mt-2"
|
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>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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 cursor-pointer"
|
||||||
|
onClick={() => setSelectedAudio(file.url)}
|
||||||
|
>
|
||||||
|
<div className="file-preview">
|
||||||
|
<Music />
|
||||||
|
</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"
|
||||||
|
/>
|
||||||
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFile && (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
|
@ -1144,14 +1287,23 @@ 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>
|
<Label>Link Url</Label>
|
||||||
<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"
|
||||||
|
value={url}
|
||||||
|
// onChange={(e) =>
|
||||||
|
// handleLinkChange(index, e.target.value)
|
||||||
|
// }
|
||||||
|
placeholder={`Masukkan link berita ${index + 1}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1159,21 +1311,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 +1340,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 +1363,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 +1555,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>
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,22 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Dock,
|
||||||
|
ImageIcon,
|
||||||
|
Music,
|
||||||
|
VideoIcon,
|
||||||
|
} 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 +75,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 +83,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 +125,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,14 +135,30 @@ 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,
|
||||||
mabes: false,
|
mabes: false,
|
||||||
polda: false,
|
polda: false,
|
||||||
polres: false,
|
polres: false,
|
||||||
|
satker: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [links, setLinks] = useState<string[]>([""]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|
@ -165,6 +206,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 +218,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
|
||||||
|
|
@ -188,11 +254,11 @@ export default function FormTaskEdit() {
|
||||||
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)); // Membagi string ke dalam array dan mengonversi ke nomor
|
||||||
setTaskOutput({
|
setTaskOutput({
|
||||||
all: outputSet.has(0),
|
all: outputSet.has(1),
|
||||||
video: outputSet.has(2),
|
video: outputSet.has(2),
|
||||||
audio: outputSet.has(4),
|
audio: outputSet.has(4),
|
||||||
image: outputSet.has(1),
|
image: outputSet.has(3),
|
||||||
text: outputSet.has(3),
|
text: outputSet.has(5),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [detail?.fileTypeOutput]);
|
}, [detail?.fileTypeOutput]);
|
||||||
|
|
@ -207,6 +273,7 @@ export default function FormTaskEdit() {
|
||||||
mabes: outputSet.has(1),
|
mabes: outputSet.has(1),
|
||||||
polda: outputSet.has(2),
|
polda: outputSet.has(2),
|
||||||
polres: outputSet.has(3),
|
polres: outputSet.has(3),
|
||||||
|
satker: outputSet.has(4),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [detail?.fileTypeOutput]);
|
}, [detail?.fileTypeOutput]);
|
||||||
|
|
@ -231,8 +298,8 @@ export default function FormTaskEdit() {
|
||||||
const fileTypeMapping = {
|
const fileTypeMapping = {
|
||||||
all: "1",
|
all: "1",
|
||||||
video: "2",
|
video: "2",
|
||||||
audio: "3",
|
audio: "4",
|
||||||
image: "4",
|
image: "3",
|
||||||
text: "5",
|
text: "5",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -241,6 +308,7 @@ export default function FormTaskEdit() {
|
||||||
mabes: "1",
|
mabes: "1",
|
||||||
polda: "2",
|
polda: "2",
|
||||||
polres: "3",
|
polres: "3",
|
||||||
|
satker: "4",
|
||||||
};
|
};
|
||||||
const assignmentPurposeString = Object.keys(unitSelection)
|
const assignmentPurposeString = Object.keys(unitSelection)
|
||||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||||
|
|
@ -265,6 +333,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 +350,58 @@ 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: FileWithPreview, index: number) => {
|
||||||
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item, // Use .file to access the actual File object
|
||||||
|
"4",
|
||||||
|
"0" // Optional: Replace with actual duration if available
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 +427,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 +452,171 @@ 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 and add preview
|
||||||
|
const fileWithPreview: FileWithPreview = Object.assign(
|
||||||
|
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
|
||||||
|
{ preview: url }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to state
|
||||||
|
setAudioFile(fileWithPreview);
|
||||||
|
setAudioFiles((prev) => [...prev, fileWithPreview]);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 updatedLinks = [...links];
|
||||||
|
updatedLinks[index] = value;
|
||||||
|
setLinks(updatedLinks);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 +624,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 +765,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 +780,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 +794,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"
|
||||||
>
|
>
|
||||||
|
|
@ -628,8 +863,37 @@ export default function FormTaskEdit() {
|
||||||
}}
|
}}
|
||||||
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>
|
||||||
|
<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">
|
||||||
|
<VideoIcon />
|
||||||
|
</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>
|
||||||
|
|
@ -641,6 +905,35 @@ 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">
|
||||||
|
<ImageIcon />
|
||||||
|
</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>
|
||||||
|
|
@ -652,9 +945,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">
|
||||||
|
<Dock />
|
||||||
|
</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>Audio</Label>
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -671,13 +993,44 @@ export default function FormTaskEdit() {
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
label="Upload file dengan format .mp3 atau .wav."
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) =>
|
||||||
|
setAudioFiles((prev) => [...prev, ...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">
|
||||||
|
<Music />
|
||||||
|
</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">
|
||||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
<p>Voice Note</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDeleteAudio}
|
onClick={handleDeleteAudio}
|
||||||
|
|
@ -691,14 +1044,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;
|
||||||
|
|
@ -119,7 +123,9 @@ export default function FormTask() {
|
||||||
mabes: false,
|
mabes: false,
|
||||||
polda: false,
|
polda: false,
|
||||||
polres: false,
|
polres: false,
|
||||||
|
satker: false,
|
||||||
});
|
});
|
||||||
|
const [links, setLinks] = useState<string[]>([""]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|
@ -144,6 +150,7 @@ export default function FormTask() {
|
||||||
try {
|
try {
|
||||||
const response = await getUserLevelForAssignments();
|
const response = await getUserLevelForAssignments();
|
||||||
setListDest(response?.data?.data.list);
|
setListDest(response?.data?.data.list);
|
||||||
|
console.log("polda", response?.data?.data?.list);
|
||||||
const initialExpandedState = response?.data?.data.list.reduce(
|
const initialExpandedState = response?.data?.data.list.reduce(
|
||||||
(acc: any, polda: any) => {
|
(acc: any, polda: any) => {
|
||||||
acc[polda.id] = false;
|
acc[polda.id] = false;
|
||||||
|
|
@ -183,8 +190,8 @@ export default function FormTask() {
|
||||||
const fileTypeMapping = {
|
const fileTypeMapping = {
|
||||||
all: "1",
|
all: "1",
|
||||||
video: "2",
|
video: "2",
|
||||||
audio: "3",
|
audio: "4",
|
||||||
image: "4",
|
image: "3",
|
||||||
text: "5",
|
text: "5",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -193,6 +200,7 @@ export default function FormTask() {
|
||||||
mabes: "1",
|
mabes: "1",
|
||||||
polda: "2",
|
polda: "2",
|
||||||
polres: "3",
|
polres: "3",
|
||||||
|
satker: "4",
|
||||||
};
|
};
|
||||||
const assignmentPurposeString = Object.keys(unitSelection)
|
const assignmentPurposeString = Object.keys(unitSelection)
|
||||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||||
|
|
@ -217,6 +225,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 +241,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 +250,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);
|
||||||
}
|
}
|
||||||
|
|
@ -265,19 +275,15 @@ export default function FormTask() {
|
||||||
if (audioFiles?.length == 0) {
|
if (audioFiles?.length == 0) {
|
||||||
setIsAudioUploadFinish(true);
|
setIsAudioUploadFinish(true);
|
||||||
}
|
}
|
||||||
audioFiles?.map(async (item: any, index: number) => {
|
audioFiles.map(async (item: FileWithPreview, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item, // Use .file to access the actual File object
|
||||||
|
"4",
|
||||||
|
"0" // Optional: Replace with actual duration if available
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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) => {
|
||||||
|
|
@ -335,15 +341,19 @@ export default function FormTask() {
|
||||||
audio.controls = true;
|
audio.controls = true;
|
||||||
document.body.appendChild(audio);
|
document.body.appendChild(audio);
|
||||||
|
|
||||||
// Convert Blob to File
|
// Convert Blob to File and add preview
|
||||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
const fileWithPreview: FileWithPreview = Object.assign(
|
||||||
setAudioFile(file);
|
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
|
||||||
|
{ preview: url }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to state
|
||||||
|
setAudioFile(fileWithPreview);
|
||||||
|
setAudioFiles((prev) => [...prev, fileWithPreview]);
|
||||||
};
|
};
|
||||||
const handleDeleteAudio = () => {
|
|
||||||
// Remove the audio file by setting state to null
|
const handleDeleteAudio = (index: number) => {
|
||||||
setAudioFile(null);
|
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
|
||||||
const audioElements = document.querySelectorAll("audio");
|
|
||||||
audioElements.forEach((audio) => audio.remove());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function uploadResumableFile(
|
async function uploadResumableFile(
|
||||||
|
|
@ -355,20 +365,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 +397,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 +459,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 +736,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>
|
||||||
|
|
@ -728,7 +762,7 @@ export default function FormTask() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Voice Note</Label>
|
<Label>Audio</Label>
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -745,34 +779,61 @@ export default function FormTask() {
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
label="Upload file dengan format .mp3 atau .wav."
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) =>
|
||||||
|
setAudioFiles((prev) => [...prev, ...files])
|
||||||
|
}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFiles?.map((audio: any, idx: any) => (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div
|
||||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
key={idx}
|
||||||
|
className="flex flex-row justify-between items-center"
|
||||||
|
>
|
||||||
|
<p>Voice Note</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDeleteAudio}
|
onClick={() => handleDeleteAudio(idx)}
|
||||||
size="sm"
|
size="sm"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
>
|
>
|
||||||
X
|
X
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
{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>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -176,12 +176,15 @@ importers:
|
||||||
'@types/react-syntax-highlighter':
|
'@types/react-syntax-highlighter':
|
||||||
specifier: ^15.5.13
|
specifier: ^15.5.13
|
||||||
version: 15.5.13
|
version: 15.5.13
|
||||||
|
'@types/sanitize-html':
|
||||||
|
specifier: ^2.13.0
|
||||||
|
version: 2.13.0
|
||||||
'@vercel/analytics':
|
'@vercel/analytics':
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.4.1(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 1.4.1(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
'@wavesurfer/react':
|
'@wavesurfer/react':
|
||||||
specifier: ^1.0.8
|
specifier: ^1.0.8
|
||||||
version: 1.0.8(react@18.3.1)(wavesurfer.js@7.8.15)
|
version: 1.0.8(react@18.3.1)(wavesurfer.js@7.8.16)
|
||||||
apexcharts:
|
apexcharts:
|
||||||
specifier: ^3.49.2
|
specifier: ^3.49.2
|
||||||
version: 3.54.1
|
version: 3.54.1
|
||||||
|
|
@ -248,6 +251,9 @@ importers:
|
||||||
jotai:
|
jotai:
|
||||||
specifier: ^2.9.3
|
specifier: ^2.9.3
|
||||||
version: 2.11.0(@types/react@18.3.18)(react@18.3.1)
|
version: 2.11.0(@types/react@18.3.18)(react@18.3.1)
|
||||||
|
jquery:
|
||||||
|
specifier: ^3.7.1
|
||||||
|
version: 3.7.1
|
||||||
js-cookie:
|
js-cookie:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
|
@ -371,6 +377,9 @@ importers:
|
||||||
rtl-detect:
|
rtl-detect:
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
|
sanitize-html:
|
||||||
|
specifier: ^2.14.0
|
||||||
|
version: 2.14.0
|
||||||
sharp:
|
sharp:
|
||||||
specifier: ^0.33.4
|
specifier: ^0.33.4
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
|
|
@ -402,8 +411,8 @@ importers:
|
||||||
specifier: ^0.9.1
|
specifier: ^0.9.1
|
||||||
version: 0.9.9(@types/react-dom@16.9.25(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 0.9.9(@types/react-dom@16.9.25(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
wavesurfer.js:
|
wavesurfer.js:
|
||||||
specifier: ^7.8.15
|
specifier: ^7.8.16
|
||||||
version: 7.8.15
|
version: 7.8.16
|
||||||
yup:
|
yup:
|
||||||
specifier: ^1.6.1
|
specifier: ^1.6.1
|
||||||
version: 1.6.1
|
version: 1.6.1
|
||||||
|
|
@ -2246,6 +2255,9 @@ packages:
|
||||||
'@types/rtl-detect@1.0.3':
|
'@types/rtl-detect@1.0.3':
|
||||||
resolution: {integrity: sha512-qpstuHivwg/HoXxRrBo5/r/OVx5M2SkqJpVu2haasdLctt+jMGHWjqdbI0LL7Rk2wRmN/UHdHK4JZg9RUMcvKA==}
|
resolution: {integrity: sha512-qpstuHivwg/HoXxRrBo5/r/OVx5M2SkqJpVu2haasdLctt+jMGHWjqdbI0LL7Rk2wRmN/UHdHK4JZg9RUMcvKA==}
|
||||||
|
|
||||||
|
'@types/sanitize-html@2.13.0':
|
||||||
|
resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
|
||||||
|
|
||||||
'@types/scheduler@0.16.8':
|
'@types/scheduler@0.16.8':
|
||||||
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
|
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
|
||||||
|
|
||||||
|
|
@ -3568,6 +3580,9 @@ packages:
|
||||||
html-void-elements@3.0.0:
|
html-void-elements@3.0.0:
|
||||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||||
|
|
||||||
|
htmlparser2@8.0.2:
|
||||||
|
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||||
|
|
||||||
htmlparser2@9.1.0:
|
htmlparser2@9.1.0:
|
||||||
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
|
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
|
||||||
|
|
||||||
|
|
@ -3864,6 +3879,9 @@ packages:
|
||||||
jquery@2.2.4:
|
jquery@2.2.4:
|
||||||
resolution: {integrity: sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==}
|
resolution: {integrity: sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==}
|
||||||
|
|
||||||
|
jquery@3.7.1:
|
||||||
|
resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==}
|
||||||
|
|
||||||
js-base64@3.7.7:
|
js-base64@3.7.7:
|
||||||
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||||
|
|
||||||
|
|
@ -4520,6 +4538,9 @@ packages:
|
||||||
parse-path@7.0.0:
|
parse-path@7.0.0:
|
||||||
resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==}
|
resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==}
|
||||||
|
|
||||||
|
parse-srcset@1.0.2:
|
||||||
|
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
|
||||||
|
|
||||||
parse-url@8.1.0:
|
parse-url@8.1.0:
|
||||||
resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==}
|
resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==}
|
||||||
|
|
||||||
|
|
@ -5073,6 +5094,9 @@ packages:
|
||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
sanitize-html@2.14.0:
|
||||||
|
resolution: {integrity: sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g==}
|
||||||
|
|
||||||
scheduler@0.23.2:
|
scheduler@0.23.2:
|
||||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||||
|
|
||||||
|
|
@ -5666,8 +5690,8 @@ packages:
|
||||||
wavesurfer.js@4.6.0:
|
wavesurfer.js@4.6.0:
|
||||||
resolution: {integrity: sha512-+nn6VD86pTtRu9leVNXoIGOCMJyaTNsKNy9v+SfUsYo+SxLCQvEzrZZ/eKMImqspsk+BX1V1xlY4FRkHswu3fA==}
|
resolution: {integrity: sha512-+nn6VD86pTtRu9leVNXoIGOCMJyaTNsKNy9v+SfUsYo+SxLCQvEzrZZ/eKMImqspsk+BX1V1xlY4FRkHswu3fA==}
|
||||||
|
|
||||||
wavesurfer.js@7.8.15:
|
wavesurfer.js@7.8.16:
|
||||||
resolution: {integrity: sha512-fWNnQt5BEGzuoJ7HRxfvpT1rOEI1AmCGPZ/+7QDkDVN/m2vIBeLVQ+5vENRMz1YwvZ/u1No0UV492/o8G++KXQ==}
|
resolution: {integrity: sha512-lhQF42A4Wn7ug5bixaqGK53qWF2minWdXlzxPtLV+QoVH3WgvVSdsP2HBaHRbkfT2Lh67kJG6CquFdukmf95gg==}
|
||||||
|
|
||||||
web-namespaces@2.0.1:
|
web-namespaces@2.0.1:
|
||||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||||
|
|
@ -8003,6 +8027,10 @@ snapshots:
|
||||||
|
|
||||||
'@types/rtl-detect@1.0.3': {}
|
'@types/rtl-detect@1.0.3': {}
|
||||||
|
|
||||||
|
'@types/sanitize-html@2.13.0':
|
||||||
|
dependencies:
|
||||||
|
htmlparser2: 8.0.2
|
||||||
|
|
||||||
'@types/scheduler@0.16.8': {}
|
'@types/scheduler@0.16.8': {}
|
||||||
|
|
||||||
'@types/sizzle@2.3.9': {}
|
'@types/sizzle@2.3.9': {}
|
||||||
|
|
@ -8064,10 +8092,10 @@ snapshots:
|
||||||
next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
'@wavesurfer/react@1.0.8(react@18.3.1)(wavesurfer.js@7.8.15)':
|
'@wavesurfer/react@1.0.8(react@18.3.1)(wavesurfer.js@7.8.16)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
wavesurfer.js: 7.8.15
|
wavesurfer.js: 7.8.16
|
||||||
|
|
||||||
'@wojtekmaj/date-utils@1.5.1': {}
|
'@wojtekmaj/date-utils@1.5.1': {}
|
||||||
|
|
||||||
|
|
@ -9657,6 +9685,13 @@ snapshots:
|
||||||
|
|
||||||
html-void-elements@3.0.0: {}
|
html-void-elements@3.0.0: {}
|
||||||
|
|
||||||
|
htmlparser2@8.0.2:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.2.1
|
||||||
|
entities: 4.5.0
|
||||||
|
|
||||||
htmlparser2@9.1.0:
|
htmlparser2@9.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
|
|
@ -9933,6 +9968,8 @@ snapshots:
|
||||||
|
|
||||||
jquery@2.2.4: {}
|
jquery@2.2.4: {}
|
||||||
|
|
||||||
|
jquery@3.7.1: {}
|
||||||
|
|
||||||
js-base64@3.7.7: {}
|
js-base64@3.7.7: {}
|
||||||
|
|
||||||
js-cookie@3.0.5: {}
|
js-cookie@3.0.5: {}
|
||||||
|
|
@ -10900,6 +10937,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
protocols: 2.0.1
|
protocols: 2.0.1
|
||||||
|
|
||||||
|
parse-srcset@1.0.2: {}
|
||||||
|
|
||||||
parse-url@8.1.0:
|
parse-url@8.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
parse-path: 7.0.0
|
parse-path: 7.0.0
|
||||||
|
|
@ -11516,6 +11555,15 @@ snapshots:
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
|
sanitize-html@2.14.0:
|
||||||
|
dependencies:
|
||||||
|
deepmerge: 4.3.1
|
||||||
|
escape-string-regexp: 4.0.0
|
||||||
|
htmlparser2: 8.0.2
|
||||||
|
is-plain-object: 5.0.0
|
||||||
|
parse-srcset: 1.0.2
|
||||||
|
postcss: 8.4.49
|
||||||
|
|
||||||
scheduler@0.23.2:
|
scheduler@0.23.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|
@ -12227,7 +12275,7 @@ snapshots:
|
||||||
|
|
||||||
wavesurfer.js@4.6.0: {}
|
wavesurfer.js@4.6.0: {}
|
||||||
|
|
||||||
wavesurfer.js@7.8.15: {}
|
wavesurfer.js@7.8.16: {}
|
||||||
|
|
||||||
web-namespaces@2.0.1: {}
|
web-namespaces@2.0.1: {}
|
||||||
|
|
||||||
|
|
|
||||||
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,8 @@
|
||||||
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
|
import {
|
||||||
|
httpDeleteInterceptor,
|
||||||
|
httpGetInterceptor,
|
||||||
|
httpPostInterceptor,
|
||||||
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
||||||
export async function paginationBlog(
|
export async function paginationBlog(
|
||||||
size: number,
|
size: number,
|
||||||
|
|
@ -22,5 +26,13 @@ export async function getBlog(id: any) {
|
||||||
|
|
||||||
export async function uploadThumbnailBlog(id: any, data: any) {
|
export async function uploadThumbnailBlog(id: any, data: any) {
|
||||||
const url = `blog/${id}/thumbnail`;
|
const url = `blog/${id}/thumbnail`;
|
||||||
return httpPostInterceptor(url, data);
|
const headers = {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
};
|
||||||
|
return httpPostInterceptor(url, data, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteBlog(id: any) {
|
||||||
|
const url = `blog?id=${id}`;
|
||||||
|
return httpDeleteInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,8 @@ export async function deleteQuestion(id: string | number) {
|
||||||
const url = `/question?id=${id}`;
|
const url = `/question?id=${id}`;
|
||||||
return httpDeleteInterceptor(url);
|
return httpDeleteInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getEscalationDiscussion(id: any) {
|
||||||
|
const url = `ticketing/escalation/discussion?ticketId=${id}`;
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
httpDeleteInterceptor,
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
} from "../http-config/http-interceptor-service";
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
@ -56,10 +57,11 @@ export async function listDataImage(
|
||||||
source: any,
|
source: any,
|
||||||
startDate: any,
|
startDate: any,
|
||||||
endDate: any,
|
endDate: any,
|
||||||
title: string = ""
|
title: string = "",
|
||||||
|
creatorGroup: string = ""
|
||||||
) {
|
) {
|
||||||
return await httpGetInterceptor(
|
return await httpGetInterceptor(
|
||||||
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=1&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
|
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=1&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}&creatorGroupLevelName=${creatorGroup}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,10 +77,11 @@ export async function listDataVideo(
|
||||||
source: any,
|
source: any,
|
||||||
startDate: any,
|
startDate: any,
|
||||||
endDate: any,
|
endDate: any,
|
||||||
title: string = ""
|
title: string = "",
|
||||||
|
creatorGroup: string = ""
|
||||||
) {
|
) {
|
||||||
return await httpGetInterceptor(
|
return await httpGetInterceptor(
|
||||||
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=2&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
|
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=2&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}&creatorGroupLevelName=${creatorGroup}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,10 +97,11 @@ export async function listDataTeks(
|
||||||
source: any,
|
source: any,
|
||||||
startDate: any,
|
startDate: any,
|
||||||
endDate: any,
|
endDate: any,
|
||||||
title: string = ""
|
title: string = "",
|
||||||
|
creatorGroup: string = ""
|
||||||
) {
|
) {
|
||||||
return await httpGetInterceptor(
|
return await httpGetInterceptor(
|
||||||
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=3&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
|
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=3&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}&creatorGroupLevelName=${creatorGroup}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,10 +117,11 @@ export async function listDataAudio(
|
||||||
source: any,
|
source: any,
|
||||||
startDate: any,
|
startDate: any,
|
||||||
endDate: any,
|
endDate: any,
|
||||||
title: string = ""
|
title: string = "",
|
||||||
|
creatorGroup: string = ""
|
||||||
) {
|
) {
|
||||||
return await httpGetInterceptor(
|
return await httpGetInterceptor(
|
||||||
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=4&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}`
|
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&typeId=4&isForSelf=${isForSelf}&isApproval=${isApproval}&categoryId=${categoryFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&creatorUserLevelName=${source}&creatorName=${creator}&startDate=${startDate}&endDate=${endDate}&title=${title}&creatorGroupLevelName=${creatorGroup}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,3 +200,17 @@ export async function saveContentRewrite(data: any) {
|
||||||
const url = "media/rewrite";
|
const url = "media/rewrite";
|
||||||
return httpPostInterceptor(url, data);
|
return httpPostInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
export async function saveUserReports(data: any) {
|
||||||
|
const url = "public/users/reports";
|
||||||
|
return httpPostInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteMedia(data: any) {
|
||||||
|
const url = `media`;
|
||||||
|
return httpDeleteInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFile(data: any) {
|
||||||
|
const url = "media/file";
|
||||||
|
return httpDeleteInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,11 @@ export async function httpGetInterceptor(pathUrl: any) {
|
||||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
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) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
|
import {
|
||||||
|
httpGetInterceptor,
|
||||||
|
httpPostInterceptor,
|
||||||
|
} from "../http-config/http-interceptor-service";
|
||||||
import { httpGet } from "../http-config/http-base-service";
|
import { httpGet } from "../http-config/http-base-service";
|
||||||
import { any } from "zod";
|
import { any } from "zod";
|
||||||
|
|
||||||
|
|
@ -35,7 +38,18 @@ export async function listSchedule(group = null) {
|
||||||
const url = `public/schedule/list?group=${group}`;
|
const url = `public/schedule/list?group=${group}`;
|
||||||
return httpGet(url, null);
|
return httpGet(url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchSchedules(search = null) {
|
export async function searchSchedules(search = null) {
|
||||||
const url = `public/schedule/list?search=${search}`;
|
const url = `public/schedule/list?search=${search}`;
|
||||||
return httpGet(url, null);
|
return httpGet(url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listScheduleToday() {
|
||||||
|
const url = "schedule/today";
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listScheduleNext() {
|
||||||
|
const url = "schedule/next-activity";
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,16 @@
|
||||||
../typescript/bin/tsc
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||||
|
fi
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
|
|
@ -1 +1,16 @@
|
||||||
../typescript/bin/tsserver
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||||
|
fi
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
Loading…
Reference in New Issue