feat: add file ckeditor

This commit is contained in:
sabdayagra 2025-01-23 10:48:36 +07:00
commit e40c977529
59 changed files with 5193 additions and 14421 deletions

View File

@ -46,7 +46,6 @@ export type CalendarEvent = {
}; };
}; };
const INITIAL_YEAR = dayjs().format("YYYY"); const INITIAL_YEAR = dayjs().format("YYYY");
const INITIAL_MONTH = dayjs().format("M"); const INITIAL_MONTH = dayjs().format("M");
@ -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>

View File

@ -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>
);
}

View File

@ -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>

View File

@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation"; import { Link } from "@/components/navigation";
import withReactContent from "sweetalert2-react-content";
import { deleteMedia } from "@/service/content/content";
import { error } from "@/lib/swal";
import Swal from "sweetalert2";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -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>

View File

@ -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">

View File

@ -13,6 +13,14 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation"; import { Link } from "@/components/navigation";
import { useRouter } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";
import { deleteMedia } from "@/service/content/content";
import { error, loading } from "@/lib/swal";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
const MySwal = withReactContent(Swal);
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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 "
/> />

View File

@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation"; import { Link } from "@/components/navigation";
import { error } from "@/lib/swal";
import { deleteMedia } from "@/service/content/content";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -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>

View File

@ -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">

View File

@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation"; import { Link } from "@/components/navigation";
import { deleteMedia } from "@/service/content/content";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { error } from "@/lib/swal";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -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>

View File

@ -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;

View File

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

View File

@ -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>

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 && (

View File

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

View File

@ -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" }),

View File

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

View File

@ -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>

View File

@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import Image from "next/image"; import Image from "next/image";
import { error } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
polres: false, polres: false,
}); });
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
let fileTypeId = "3"; let fileTypeId = "3";
@ -164,10 +165,6 @@ export default function FormTeksUpdate() {
// } // }
// }; // };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => { const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) { if (event.target.files) {
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
setFiles(details.files); setFiles(details.files);
} }
if (details.publishedForObject) { if (details?.publishedFor) {
const publisherIds = details.publishedForObject.map( // Split string "7" to an array ["7"] if needed
(obj: any) => obj.id setPublishedFor(details.publishedFor.split(","));
); }
setSelectedPublishers(publisherIds);
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
} }
const matchingCategory = categories.find( const matchingCategory = categories.find(
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
initState(); initState();
}, [refresh, setValue]); }, [refresh, setValue]);
const handleCheckboxChange = (id: string): void => { const handleCheckboxChange = (id: string) => {
if (id === "all") { if (id === "all") {
if (publishedFor.includes("all")) { // Select all options except "all"
// Uncheck all checkboxes const allOptions = options
setPublishedFor([]); .filter((opt) => opt.id !== "all")
} else { .map((opt) => opt.id);
// Select all checkboxes setPublishedFor(
setPublishedFor( 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 ===

View File

@ -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>
)) ))

View File

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

View File

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

View File

@ -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>
); );

View File

@ -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" }),

View File

@ -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>
); );

View File

@ -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>
); );

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -37,12 +37,15 @@ import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader"; import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { error } from "@/config/swal"; import { error } from "@/config/swal";
import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/swal";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, { naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.", message: "Narasi Penugasan harus lebih dari 2 karakter.",
}), }),
// url: z.string().min(1, { message: "Judul diperlukan" }),
}); });
interface FileWithPreview extends File { interface FileWithPreview extends File {
@ -63,6 +66,7 @@ export type taskDetail = {
id: number; id: number;
name: string; name: string;
}; };
attachmentUrl: string;
taskType: string; taskType: string;
broadcastType: string; broadcastType: string;
narration: string; narration: string;
@ -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>

13052
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

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

View File

@ -1,4 +1,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);
} }

View File

@ -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);
}

View File

@ -1,4 +1,5 @@
import { import {
httpDeleteInterceptor,
httpGetInterceptor, httpGetInterceptor,
httpPostInterceptor, httpPostInterceptor,
} from "../http-config/http-interceptor-service"; } from "../http-config/http-interceptor-service";
@ -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);
}

View File

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

View File

@ -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);
}

View File

@ -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

17
vendor/ckeditor5/node_modules/.bin/tsc.cmd generated vendored Normal file
View File

@ -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" %*

28
vendor/ckeditor5/node_modules/.bin/tsc.ps1 generated vendored Normal file
View File

@ -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

View File

@ -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

17
vendor/ckeditor5/node_modules/.bin/tsserver.cmd generated vendored Normal file
View File

@ -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" %*

28
vendor/ckeditor5/node_modules/.bin/tsserver.ps1 generated vendored Normal file
View File

@ -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