Merge branch 'dev-anang' of https://gitlab.com/hanifsalafi/mediahub_redesign
This commit is contained in:
commit
498ab94100
|
|
@ -46,7 +46,6 @@ export type CalendarEvent = {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
|
||||
|
|
@ -63,7 +62,7 @@ export interface AgendaSettingsAPIResponse {
|
|||
updatedAt: string;
|
||||
createdById: number | null;
|
||||
}
|
||||
|
||||
|
||||
interface YearlyData {
|
||||
january?: Event[];
|
||||
february?: Event[];
|
||||
|
|
@ -86,7 +85,7 @@ interface MonthCardProps {
|
|||
|
||||
interface ListItemProps {
|
||||
item: any;
|
||||
text: string
|
||||
text: string;
|
||||
createdBy: string;
|
||||
bgColor: string;
|
||||
}
|
||||
|
|
@ -120,7 +119,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const [selectedYear, setSelectedYear] = useState(new Date());
|
||||
const [selectedMonth, setSelectedMonth] = useState(
|
||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)),
|
||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
||||
);
|
||||
|
||||
const [dragEvents] = useState([
|
||||
|
|
@ -142,7 +141,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const getCalendarEvents = async () => {
|
||||
console.log("View : ", activeView);
|
||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, selectedMonth.format("M") || INITIAL_MONTH, "");
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
selectedMonth.format("M") || INITIAL_MONTH,
|
||||
""
|
||||
);
|
||||
console.log("View : API Response:", res);
|
||||
|
||||
if (res?.error) {
|
||||
|
|
@ -175,13 +178,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
};
|
||||
|
||||
const getYearlyEvents = async () => {
|
||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, '', '');
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
setYearlyData(res?.data?.data);
|
||||
}
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
"",
|
||||
""
|
||||
);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
setYearlyData(res?.data?.data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
|
|
@ -218,21 +225,19 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
if (response?.data && Array.isArray(response?.data)) {
|
||||
// Transform API data to match CalendarEvent type
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map(
|
||||
(item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
createBy: "Mabes Polri - Approver",
|
||||
createdByName: item.createdByName,
|
||||
start: new Date(item.startDate),
|
||||
end: new Date(item.endDate),
|
||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
},
|
||||
})
|
||||
);
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
createBy: "Mabes Polri - Approver",
|
||||
createdByName: item.createdByName,
|
||||
start: new Date(item.startDate),
|
||||
end: new Date(item.endDate),
|
||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
},
|
||||
}));
|
||||
|
||||
setApiEvents(eventsFromAPI);
|
||||
} else {
|
||||
|
|
@ -321,7 +326,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const renderEventContent = (eventInfo: any) => {
|
||||
const { title } = eventInfo.event;
|
||||
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps
|
||||
const { createdByName } = eventInfo.event.extendedProps;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -340,6 +345,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
return "bg-blue-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "polres") {
|
||||
return "bg-slate-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "satker") {
|
||||
return "bg-orange-500 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "international") {
|
||||
return "bg-green-400 border-none";
|
||||
} else {
|
||||
|
|
@ -359,29 +366,29 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
const handleViewChange = (viewType: string) => {
|
||||
console.log("Change view : ", viewType);
|
||||
setActiveView(viewType);
|
||||
};
|
||||
};
|
||||
|
||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||
{ id: 'january', label: 'Januari' },
|
||||
{ id: 'february', label: 'Februari' },
|
||||
{ id: 'march', label: 'Maret' },
|
||||
{ id: 'april', label: 'April' },
|
||||
{ id: 'may', label: 'Mei' },
|
||||
{ id: 'june', label: 'Juni' },
|
||||
{ id: 'july', label: 'Juli' },
|
||||
{ id: 'august', label: 'Agustus' },
|
||||
{ id: 'september', label: 'September' },
|
||||
{ id: 'october', label: 'Oktober' },
|
||||
{ id: 'november', label: 'November' },
|
||||
{ id: 'december', label: 'Desember' }
|
||||
{ id: "january", label: "Januari" },
|
||||
{ id: "february", label: "Februari" },
|
||||
{ id: "march", label: "Maret" },
|
||||
{ id: "april", label: "April" },
|
||||
{ id: "may", label: "Mei" },
|
||||
{ id: "june", label: "Juni" },
|
||||
{ id: "july", label: "Juli" },
|
||||
{ id: "august", label: "Agustus" },
|
||||
{ id: "september", label: "September" },
|
||||
{ id: "october", label: "Oktober" },
|
||||
{ id: "november", label: "November" },
|
||||
{ id: "december", label: "Desember" },
|
||||
];
|
||||
|
||||
const getEventColor = (type: Event['type']): string => {
|
||||
const colors: Record<Event['type'], string> = {
|
||||
mabes: 'bg-yellow-500',
|
||||
polda: 'bg-blue-400',
|
||||
polres: 'bg-slate-400',
|
||||
international: 'bg-green-400'
|
||||
const getEventColor = (type: Event["type"]): string => {
|
||||
const colors: Record<Event["type"], string> = {
|
||||
mabes: "bg-yellow-500",
|
||||
polda: "bg-blue-400",
|
||||
polres: "bg-slate-400",
|
||||
international: "bg-green-400",
|
||||
};
|
||||
return colors[type];
|
||||
};
|
||||
|
|
@ -397,32 +404,38 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
allDay: true,
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description
|
||||
}
|
||||
description: item.description,
|
||||
},
|
||||
};
|
||||
const finalEvent: any = {
|
||||
event: formattedEvent
|
||||
}
|
||||
|
||||
event: formattedEvent,
|
||||
};
|
||||
|
||||
console.log("Event click custom : ", finalEvent);
|
||||
|
||||
setSelectedEventDate(null);
|
||||
setSheetOpen(true);
|
||||
setApiEvents(finalEvent);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
}
|
||||
};
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ item, text, createdBy, bgColor }) => (
|
||||
<div className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`} onClick={() => handleClickListItem(item)}>
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
item,
|
||||
text,
|
||||
createdBy,
|
||||
bgColor,
|
||||
}) => (
|
||||
<div
|
||||
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
|
||||
onClick={() => handleClickListItem(item)}
|
||||
>
|
||||
<p className="ml-1">{text}</p>
|
||||
<p className="ml-1 text-xs text-start mt-2">
|
||||
Created By: {createdBy}
|
||||
</p>
|
||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
||||
const { monthId, label } = props;
|
||||
const { monthId, label } = props;
|
||||
const events: any = yearlyData?.[monthId];
|
||||
const displayedEvents = events?.slice(0, 3);
|
||||
const hasMoreEvents = events?.length > 3;
|
||||
|
|
@ -431,7 +444,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
<div className="py-3">
|
||||
<h4 className="font-bold text-center">{label}</h4>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="px-2">
|
||||
{events?.length === 0 ? (
|
||||
<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) => (
|
||||
<ListItem
|
||||
<ListItem
|
||||
key={index}
|
||||
item={event}
|
||||
text={event.title}
|
||||
|
|
@ -448,7 +461,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
bgColor={getEventColor(event.agendaType)}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
{hasMoreEvents && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -462,12 +475,14 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
>
|
||||
<Card>
|
||||
<CardHeader className="bg-default p-2 rounded-t-lg">
|
||||
<CardTitle className="text-default-foreground text-base py-0">{label}</CardTitle>
|
||||
<CardTitle className="text-default-foreground text-base py-0">
|
||||
{label}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-2 text-sm">
|
||||
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
|
||||
{events.map((event: any, index: number) => (
|
||||
<ListItem
|
||||
<ListItem
|
||||
key={index}
|
||||
item={event}
|
||||
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">
|
||||
<CardContent className="p-0">
|
||||
<CardHeader className="border-none mb-2 pt-5">
|
||||
{roleId == 11 || roleId == 12 ?
|
||||
{roleId == 11 || roleId == 12 ? (
|
||||
<Button
|
||||
onClick={handleDateClick}
|
||||
className="dark:bg-background dark:text-foreground"
|
||||
>
|
||||
<Plus className="w-4 h-4 me-1" />
|
||||
{"Tambahkan Agenda baru"}
|
||||
</Button> :
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
}
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<div className="px-3">
|
||||
|
|
@ -597,35 +613,53 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||
handleViewChange(info.view.type);
|
||||
}}
|
||||
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
||||
viewClassNames={
|
||||
activeView === "listYear" ? "hide-calendar-grid" : ""
|
||||
}
|
||||
/>
|
||||
|
||||
{activeView === "listYear" && (
|
||||
<div className="custom-ui">
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(0, 3).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(0, 3).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Second Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(3, 6).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(3, 6).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Third Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(6, 9).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(6, 9).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Fourth Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(9, 12).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(9, 12).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,204 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getUserLevelForAssignments } from "@/service/task";
|
||||
|
||||
const FormSchema = z.object({
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: "Required",
|
||||
}),
|
||||
});
|
||||
|
||||
interface UnitType {
|
||||
id: number;
|
||||
name: string;
|
||||
subDestination: { id: number; name: string }[] | null;
|
||||
}
|
||||
|
||||
export function UnitMapping(props: {
|
||||
unit: "Polda" | "Satker" | "Polres";
|
||||
sendDataToParent: (data: string[]) => void;
|
||||
isDetail: boolean;
|
||||
initData?: string[];
|
||||
}) {
|
||||
const { unit, sendDataToParent, isDetail } = props;
|
||||
const [unitList, setUnitList] = useState<UnitType[]>([]);
|
||||
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
items: props.initData ? props.initData : [],
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setupUnit(response?.data?.data.list);
|
||||
console.log("list", response?.data?.data.list);
|
||||
}
|
||||
|
||||
initState();
|
||||
}, []);
|
||||
|
||||
const unitType = form.watch("items");
|
||||
|
||||
const isAllUnitChecked = unitList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
const isAllSatkerChecked = satkerList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
const isAllPolresChecked = polresList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
|
||||
const setupUnit = (data: UnitType[]) => {
|
||||
const temp = data.filter((a) => a.name.includes("POLDA"));
|
||||
const temp2 = data.filter((a) => a.name.includes("SATKER"));
|
||||
const temp3 = temp.flatMap((item) => item.subDestination || []);
|
||||
setUnitList(temp);
|
||||
setSatkerList(temp2[0]?.subDestination || []);
|
||||
setPolresList(temp3);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
sendDataToParent(form.getValues("items"));
|
||||
}, [unitType]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<a
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="text-primary cursor-pointer text-xs mr-3"
|
||||
>
|
||||
Pilih {unit}
|
||||
</a>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md" className="h-[500px] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{unit}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
id={`all-${unit}`}
|
||||
checked={
|
||||
unit === "Polda"
|
||||
? isAllUnitChecked
|
||||
: unit === "Satker"
|
||||
? isAllSatkerChecked
|
||||
: isAllPolresChecked
|
||||
}
|
||||
disabled={isDetail}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
form.setValue(
|
||||
"items",
|
||||
unit === "Polda"
|
||||
? unitList.map((item) => String(item.id))
|
||||
: unit === "Satker"
|
||||
? satkerList.map((item) => String(item.id))
|
||||
: polresList.map((item) => String(item.id))
|
||||
);
|
||||
} else {
|
||||
form.setValue("items", []);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="all" className="text-sm text-black uppercase">
|
||||
SEMUA {unit}
|
||||
</label>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem
|
||||
className={`grid grid-cols-${
|
||||
unit === "Polda" ? "2" : unit === "Satker" ? "3" : "4"
|
||||
}`}
|
||||
>
|
||||
{(unit === "Polda"
|
||||
? unitList
|
||||
: unit === "Satker"
|
||||
? satkerList
|
||||
: polresList
|
||||
)?.map((item: any) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={String(item.id)}
|
||||
className="flex flex-row items-center space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
disabled={isDetail}
|
||||
checked={field.value?.includes(String(item.id))}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([
|
||||
...field.value,
|
||||
String(item.id),
|
||||
])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== String(item.id)
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<p className="text-sm text-black">{item.name}</p>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -12,7 +12,11 @@ import {
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
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>[] = [
|
||||
{
|
||||
|
|
@ -84,6 +88,48 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await 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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -108,7 +154,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => 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" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import { error } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
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",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -159,7 +210,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
|
|
@ -55,7 +57,10 @@ import {
|
|||
listDataAudio,
|
||||
listDataImage,
|
||||
listDataVideo,
|
||||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const TableAudio = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -81,13 +86,17 @@ const TableAudio = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -121,35 +130,69 @@ const TableAudio = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
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() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataAudio(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -157,11 +200,28 @@ const TableAudio = () => {
|
|||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
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 (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -179,17 +239,139 @@ const TableAudio = () => {
|
|||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<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>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +72,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
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",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -155,7 +211,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -53,12 +53,24 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataImage } from "@/service/content/content";
|
||||
import {
|
||||
deleteMedia,
|
||||
listDataImage,
|
||||
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 router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
|
|
@ -79,13 +91,17 @@ const TableImage = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -118,36 +134,71 @@ const TableImage = () => {
|
|||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Panggil fetchData saat filter kategori berubah
|
||||
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() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataImage(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -161,6 +212,22 @@ const TableImage = () => {
|
|||
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 (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -179,16 +246,110 @@ const TableImage = () => {
|
|||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ const columns: ColumnDef<any>[] = [
|
|||
<div>
|
||||
<Button
|
||||
size="sm"
|
||||
color={isPublish ? "success" : "warning"} // Hijau untuk diterima, oranye untuk menunggu review
|
||||
color={isPublish ? "success" : "warning"}
|
||||
variant="outline"
|
||||
className={`btn btn-sm ${
|
||||
isPublish ? "btn-outline-success" : "btn-outline-warning"
|
||||
|
|
|
|||
|
|
@ -170,10 +170,10 @@ const TableSPIT = () => {
|
|||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
(table.getColumn("isPublish")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
table.getColumn("isPublish")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { error } from "@/lib/swal";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
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",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -160,7 +211,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
|
|
@ -51,7 +53,13 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataImage, 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 router = useRouter();
|
||||
|
|
@ -77,13 +85,17 @@ const TableTeks = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -117,35 +129,69 @@ const TableTeks = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
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() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataTeks(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -159,6 +205,22 @@ const TableTeks = () => {
|
|||
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 (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -176,17 +238,139 @@ const TableTeks = () => {
|
|||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<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>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { error } from "@/lib/swal";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
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",
|
||||
};
|
||||
|
||||
// Mengambil `statusName` dari data API
|
||||
const status = row.getValue("statusName") as string;
|
||||
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
|
||||
|
||||
// Gunakan `statusName` untuk pencocokan
|
||||
const statusName = status?.toLocaleLowerCase();
|
||||
const statusStyles =
|
||||
statusColors[statusName] || "bg-gray-100 text-gray-600";
|
||||
statusColors[statusName] || "bg-red-200 text-red-600";
|
||||
|
||||
return (
|
||||
<Badge
|
||||
|
|
@ -136,6 +139,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -160,7 +208,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
|
|
@ -51,9 +53,15 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataImage, 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 searchParams = useSearchParams();
|
||||
|
||||
|
|
@ -77,13 +85,17 @@ const TableImage = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -117,35 +129,69 @@ const TableImage = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
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() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataVideo(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -159,6 +205,22 @@ const TableImage = () => {
|
|||
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 (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -176,17 +238,140 @@ const TableImage = () => {
|
|||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<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>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
@ -239,4 +424,4 @@ const TableImage = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default TableImage;
|
||||
export default TableVideo;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { deleteCategory } from "@/service/settings/settings";
|
||||
import { deleteTask } from "@/service/task";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -115,6 +122,49 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await deleteTask(id);
|
||||
|
||||
if (resDelete?.error) {
|
||||
error(resDelete.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const TaskDelete = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteProcess(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -139,7 +189,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => TaskDelete(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ import { Button } from "@/components/ui/button";
|
|||
import InternalTable from "./internal/components/internal-table";
|
||||
|
||||
const CommunicationPage = () => {
|
||||
const [tab, setTab] = useState("Komunikasi");
|
||||
const [tab, setTab] = useState("Pertanyaan Internal");
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||
<div className="flex justify-between py-3">
|
||||
<p className="text-lg">{tab}</p>
|
||||
{tab === "Komunikasi" && (
|
||||
{tab === "Pertanyaan Internal" && (
|
||||
<Link href="/shared/communication/internal/create">
|
||||
<Button color="primary" size="md">
|
||||
<PlusIcon />
|
||||
|
|
@ -39,15 +39,15 @@ const CommunicationPage = () => {
|
|||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||
<Button
|
||||
rounded="md"
|
||||
onClick={() => setTab("Komunikasi")}
|
||||
onClick={() => setTab("Pertanyaan Internal")}
|
||||
className={` hover:text-white
|
||||
${
|
||||
tab === "Komunikasi"
|
||||
tab === "Pertanyaan Internal"
|
||||
? "bg-black text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
Komunikasi
|
||||
Pertanyaan Internal
|
||||
</Button>
|
||||
<Button
|
||||
rounded="md"
|
||||
|
|
@ -74,7 +74,7 @@ const CommunicationPage = () => {
|
|||
Kolaborasi
|
||||
</Button>
|
||||
</div>
|
||||
{tab === "Komunikasi" && <InternalTable />}
|
||||
{tab === "Pertanyaan Internal" && <InternalTable />}
|
||||
{tab === "Eskalasi" && <EscalationTable />}
|
||||
{tab === "Kolaborasi" && <CollaborationTable />}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
import AudioSliderPage from "../../audio/audio";
|
||||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../../video/audio-visual";
|
||||
|
||||
const AudioAllPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Audio</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioAllPage;
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import {
|
||||
formatDateToIndonesian,
|
||||
|
|
@ -21,10 +22,12 @@ const AudioSliderPage = () => {
|
|||
const [audioData, setAudioData] = useState<any>();
|
||||
const [displayAudio, setDisplayAudio] = useState<any[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, []);
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (audioData?.length > 0) {
|
||||
|
|
@ -35,14 +38,12 @@ const AudioSliderPage = () => {
|
|||
}, [audioData]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await getListContent({
|
||||
page: page - 1,
|
||||
size: 12,
|
||||
sortBy: "createdAt",
|
||||
contentTypeId: "4",
|
||||
});
|
||||
const response = await listCuratedContent(search, limit, page - 1, 4, "1");
|
||||
console.log(response);
|
||||
setAudioData(response?.data?.data?.content);
|
||||
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setAudioData(contentData);
|
||||
};
|
||||
|
||||
const shuffleAndSetVideos = () => {
|
||||
|
|
@ -65,7 +66,7 @@ const AudioSliderPage = () => {
|
|||
<Link
|
||||
href={`/shared/curated-content//giat-routine/audio/detail/${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">
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
import AudioSliderPage from "../../audio/audio";
|
||||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../../video/audio-visual";
|
||||
|
||||
const DocumentAllPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Teks</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Teks</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="giat-penugasan">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<Label>Audio Visual</Label>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<Label>Audio</Label>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<Label>Foto</Label>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<Label>Teks</Label>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="contest">
|
||||
<Card>
|
||||
<div className="py-3">
|
||||
<ContestTable />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentAllPage;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
import AudioSliderPage from "../../audio/audio";
|
||||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../../video/audio-visual";
|
||||
|
||||
const ImageAllPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Image</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Image</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageAllPage;
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
import AudioSliderPage from "../../audio/audio";
|
||||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../audio-visual";
|
||||
|
||||
const VideoAllPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Video</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio Visual</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="giat-penugasan">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<Label>Audio Visual</Label>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<Label>Audio</Label>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<Label>Foto</Label>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<Label>Teks</Label>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="contest">
|
||||
<Card>
|
||||
<div className="py-3">
|
||||
<ContestTable />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoAllPage;
|
||||
|
|
@ -28,7 +28,7 @@ const VideoSliderPage = () => {
|
|||
}, [allVideoData]);
|
||||
|
||||
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);
|
||||
|
||||
const data = response?.data?.data;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
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 { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -73,19 +73,69 @@ const CuratedContentPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<VideoSliderPage />
|
||||
</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">
|
||||
<AudioSliderPage />
|
||||
</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">
|
||||
<ImageSliderPage />
|
||||
</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">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from "@/service/content/content";
|
||||
import { getBlog, postBlog } from "@/service/blog/blog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const taskSchema = z.object({
|
||||
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() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -236,12 +244,7 @@ export default function FormBlogDetail() {
|
|||
control={control}
|
||||
name="narration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail.narration}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<ViewEditor initialData={detail?.narration} />
|
||||
)}
|
||||
/>
|
||||
{errors.narration?.message && (
|
||||
|
|
@ -300,7 +303,7 @@ export default function FormBlogDetail() {
|
|||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<Card className=" h-[650px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ import {
|
|||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
} 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 dynamic from "next/dynamic";
|
||||
import { loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -38,7 +40,7 @@ const taskSchema = z.object({
|
|||
narration: z
|
||||
.string()
|
||||
.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 = {
|
||||
|
|
@ -77,6 +79,13 @@ const initialCategories: Category[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormBlogUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -98,6 +107,9 @@ export default function FormBlogUpdate() {
|
|||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
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({
|
||||
allUnit: false,
|
||||
|
|
@ -117,18 +129,6 @@ export default function FormBlogUpdate() {
|
|||
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) => {
|
||||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
|
@ -141,15 +141,20 @@ export default function FormBlogUpdate() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
// Set categoryId dari API ke form dan Select
|
||||
setValue("categoryName", details?.categoryName);
|
||||
setSelectedTarget(details?.categoryId); // Untuk dropdown
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
setValue("categoryId", details?.categoryName);
|
||||
setSelectedTarget(details?.categoryId);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -158,7 +163,7 @@ export default function FormBlogUpdate() {
|
|||
categoryId: selectedTarget,
|
||||
slug: data.slug,
|
||||
metadata: data.metadata,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isDraft,
|
||||
};
|
||||
|
||||
|
|
@ -166,6 +171,30 @@ export default function FormBlogUpdate() {
|
|||
console.log("Form Data Submitted:", requestData);
|
||||
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({
|
||||
title: "Sukses",
|
||||
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 = () => {
|
||||
setIsDraft(false);
|
||||
};
|
||||
|
|
@ -201,6 +241,29 @@ export default function FormBlogUpdate() {
|
|||
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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -238,11 +301,9 @@ export default function FormBlogUpdate() {
|
|||
control={control}
|
||||
name="narration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail.narration}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.narration || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -302,31 +363,39 @@ export default function FormBlogUpdate() {
|
|||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<Card className=" h-[700px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
<Input
|
||||
<input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
// onChange={(e) => {
|
||||
// const file = e.target.files[0];
|
||||
// if (file) {
|
||||
// console.log("Selected File:", file);
|
||||
// // Tambahkan logika jika diperlukan, misalnya upload file ke server
|
||||
// }
|
||||
// }}
|
||||
className=""
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{preview ? (
|
||||
// Menampilkan pratinjau gambar yang baru dipilih
|
||||
<div className="mt-3 px-3">
|
||||
<img
|
||||
src={preview}
|
||||
alt="Thumbnail Preview"
|
||||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</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 className="px-3 py-3 mt-6">
|
||||
<label
|
||||
|
|
@ -336,7 +405,7 @@ export default function FormBlogUpdate() {
|
|||
Kategori
|
||||
</label>
|
||||
<Controller
|
||||
name="categoryName"
|
||||
name="categoryId"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
|
|
@ -350,12 +419,9 @@ export default function FormBlogUpdate() {
|
|||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.categoryName}
|
||||
>
|
||||
{category.id}
|
||||
{categories.map((category: any) => (
|
||||
<SelectItem key={category.id} value={category.id}>
|
||||
{category.categoryName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
@ -366,14 +432,34 @@ export default function FormBlogUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ import {
|
|||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
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({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -37,7 +40,6 @@ const taskSchema = z.object({
|
|||
narration: z
|
||||
.string()
|
||||
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||
tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
type Category = {
|
||||
|
|
@ -64,6 +66,13 @@ const initialCategories: Category[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormBlog() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -82,6 +91,7 @@ export default function FormBlog() {
|
|||
const [isDraft, setIsDraft] = useState(false);
|
||||
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -156,6 +166,8 @@ export default function FormBlog() {
|
|||
// };
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
title: data.title,
|
||||
|
|
@ -163,7 +175,7 @@ export default function FormBlog() {
|
|||
categoryId: selectedTarget,
|
||||
slug: data.slug,
|
||||
metadata: data.meta,
|
||||
tags: data.tags,
|
||||
tags: finalTags,
|
||||
isDraft,
|
||||
};
|
||||
|
||||
|
|
@ -177,19 +189,33 @@ export default function FormBlog() {
|
|||
}
|
||||
|
||||
const blogId = response?.data?.data.id;
|
||||
if (blogId && thumbnail) {
|
||||
const formMedia = new FormData();
|
||||
formMedia.append("file", thumbnail);
|
||||
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);
|
||||
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
||||
|
||||
if (responseThumbnail?.error) {
|
||||
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||
return;
|
||||
if (responseThumbnail?.error) {
|
||||
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||
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) => {
|
||||
|
|
@ -198,8 +224,9 @@ export default function FormBlog() {
|
|||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
cancelButtonText: "Batal",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
|
|
@ -210,11 +237,11 @@ export default function FormBlog() {
|
|||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setThumbnail(file);
|
||||
setThumbnail(file); // Simpan file ke state
|
||||
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
|
||||
console.log("Selected Thumbnail:", file);
|
||||
}
|
||||
if (file) {
|
||||
setPreview(URL.createObjectURL(file));
|
||||
} else {
|
||||
console.log("No file selected");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -226,6 +253,19 @@ export default function FormBlog() {
|
|||
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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
|
|
@ -260,12 +300,7 @@ export default function FormBlog() {
|
|||
control={control}
|
||||
name="narration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<CustomEditor onChange={onChange} initialData={value} />
|
||||
)}
|
||||
/>
|
||||
{errors.narration?.message && (
|
||||
|
|
@ -324,10 +359,10 @@ export default function FormBlog() {
|
|||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[550px]">
|
||||
<Card className=" h-[600px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
<Input id="fileInput" type="file" onChange={handleImageChange} />
|
||||
<label htmlFor="fileInput">Gambar Utama</label>
|
||||
<input id="fileInput" type="file" onChange={handleImageChange} />
|
||||
</div>
|
||||
{preview && (
|
||||
<div className="mt-3 px-3">
|
||||
|
|
@ -366,19 +401,30 @@ export default function FormBlog() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<Label>Tags</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="tags"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<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">
|
||||
{tags.length === 0 && "Please add at least one tag."}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
getEscalationDiscussion,
|
||||
getTicketingDetail,
|
||||
getTicketingInternalDetail,
|
||||
getTicketingInternalDiscussion,
|
||||
|
|
@ -28,6 +29,8 @@ import {
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { id } from "date-fns/locale";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -57,9 +60,24 @@ export default function FormDetailEscalation() {
|
|||
const [detail, setDetail] = useState<any>();
|
||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||
const [replyVisible, setReplyVisible] = useState(false);
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [listDiscussion, setListDiscussion] = useState();
|
||||
const [message, setMessage] = 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 {
|
||||
control,
|
||||
|
|
@ -90,53 +108,32 @@ export default function FormDetailEscalation() {
|
|||
const handleReply = () => {
|
||||
setReplyVisible((prev) => !prev); // Toggle visibility
|
||||
};
|
||||
// useEffect(() => {
|
||||
// async function initState() {
|
||||
// if (id != undefined) {
|
||||
// loading();
|
||||
// const responseGet = await getEscalationDiscussion(id);
|
||||
// close();
|
||||
|
||||
const handleSendReply = async () => {
|
||||
if (replyMessage.trim() === "") {
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Pesan tidak boleh kosong!",
|
||||
icon: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// console.log("escal data", responseGet?.data);
|
||||
// setListDiscussion(responseGet?.data?.data);
|
||||
// }
|
||||
// }
|
||||
// initState();
|
||||
// }, [id]);
|
||||
|
||||
const data = {
|
||||
ticketId: id,
|
||||
const handleSendReply = () => {
|
||||
if (replyMessage.trim() === "") return;
|
||||
|
||||
const newReply = {
|
||||
id: replies.length + 1,
|
||||
name: "Mabes Polri - Approver", // Sesuaikan dengan data dinamis jika ada
|
||||
message: replyMessage,
|
||||
timestamp: new Date().toISOString().slice(0, 19).replace("T", " "),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await saveTicketInternalReply(data);
|
||||
|
||||
// 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);
|
||||
}
|
||||
setReplies([...replies, newReply]);
|
||||
setReplyMessage("");
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -244,26 +241,46 @@ export default function FormDetailEscalation() {
|
|||
</p>
|
||||
)} */}
|
||||
</div>
|
||||
<div className="mt-4 px-3 mb-3">
|
||||
<Label htmlFor="replyMessage">Tulis Pesan</Label>
|
||||
<div className="mx-3 my-3">
|
||||
<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
|
||||
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}
|
||||
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">
|
||||
<Button
|
||||
onClick={() => setReplyVisible(false)}
|
||||
color="default"
|
||||
variant="outline"
|
||||
<div className="flex justify-end gap-3 mt-2 mb-3">
|
||||
<button
|
||||
onClick={() => setReplyMessage("")}
|
||||
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md"
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
<Button onClick={handleSendReply} color="primary">
|
||||
Kirim
|
||||
</Button>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSendReply}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md"
|
||||
>
|
||||
Kirim Pesan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import {
|
|||
saveTicketInternalReply,
|
||||
} from "@/service/communication/communication";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { list } from "postcss";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -72,6 +74,19 @@ export type replyDetail = {
|
|||
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() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
|
@ -79,6 +94,9 @@ export default function FormDetailInternal() {
|
|||
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||
const [ticketInternal, setTicketInternal] = useState<internalDetail | null>(
|
||||
null
|
||||
);
|
||||
const [replyVisible, setReplyVisible] = useState(false);
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [selectedPriority, setSelectedPriority] = useState("");
|
||||
|
|
@ -96,6 +114,7 @@ export default function FormDetailInternal() {
|
|||
async function initState() {
|
||||
if (id) {
|
||||
const response = await getTicketingInternalDetail(id);
|
||||
setTicketInternal(response?.data?.data || null);
|
||||
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">
|
||||
Ticket #{detail?.referenceNumber}
|
||||
</p>
|
||||
|
||||
{ticketReply?.map((list) => (
|
||||
<div key={list.id} className="flex flex-col">
|
||||
<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>
|
||||
</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>
|
||||
{detail !== undefined && (
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -81,6 +82,11 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function FormAudioUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -111,10 +117,20 @@ export default function FormAudioUpdate() {
|
|||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
{ id: "5", name: "UMUM" },
|
||||
{ id: "6", name: "JOURNALIS" },
|
||||
{ id: "7", name: "POLRI" },
|
||||
{ id: "8", name: "KSP" },
|
||||
];
|
||||
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
|
|
@ -139,21 +155,6 @@ export default function FormAudioUpdate() {
|
|||
resolver: zodResolver(audioSchema),
|
||||
});
|
||||
|
||||
// const handleKeyDown = (e: any) => {
|
||||
// const newTag = e.target.value.trim(); // Ambil nilai input
|
||||
// if (e.key === "Enter" && newTag) {
|
||||
// e.preventDefault(); // Hentikan submit form
|
||||
// if (!tags.includes(newTag)) {
|
||||
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
||||
// setValue("tags", ""); // Kosongkan input
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -166,12 +167,6 @@ export default function FormAudioUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
getCategories();
|
||||
|
|
@ -205,6 +200,29 @@ export default function FormAudioUpdate() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -217,11 +235,13 @@ export default function FormAudioUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -244,7 +264,25 @@ export default function FormAudioUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: AudioSchema) => {
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -256,9 +294,9 @@ export default function FormAudioUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -286,12 +324,7 @@ export default function FormAudioUpdate() {
|
|||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
|
|
@ -430,22 +463,24 @@ export default function FormAudioUpdate() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
// const handleRemoveFile = (file: FileWithPreview) => {
|
||||
// const uploadedFiles = files;
|
||||
// const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
// setFiles([...filtered]);
|
||||
// };
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-foreground">
|
||||
{file.fileName || file.name}
|
||||
</div>
|
||||
<div className="text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -460,10 +495,10 @@ export default function FormAudioUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
|
@ -500,6 +535,55 @@ export default function FormAudioUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteFile = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus file",
|
||||
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDelete(id: number) {
|
||||
const data = { id };
|
||||
|
||||
try {
|
||||
const response = await deleteFile(data);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Jika berhasil, hapus file dari state lokal
|
||||
setFiles((prevFiles: any) =>
|
||||
prevFiles.filter((file: any) => file.id !== id)
|
||||
);
|
||||
success();
|
||||
} catch (err) {
|
||||
error("Terjadi kesalahan saat menghapus file");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -606,12 +690,12 @@ export default function FormAudioUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -752,14 +836,34 @@ export default function FormAudioUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -767,38 +871,21 @@ export default function FormAudioUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt) => opt.id !== "all").length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
deleteMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -42,7 +44,7 @@ import dynamic from "next/dynamic";
|
|||
import { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
|
||||
|
|
@ -65,7 +67,13 @@ type Detail = {
|
|||
title: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
categoryId: {
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
publishedFor: string;
|
||||
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
|
@ -75,6 +83,11 @@ type Detail = {
|
|||
tags: string;
|
||||
};
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
|
|
@ -122,7 +135,17 @@ export default function FormImageUpdate() {
|
|||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
{ id: "5", name: "UMUM" },
|
||||
{ id: "6", name: "JOURNALIS" },
|
||||
{ id: "7", name: "POLRI" },
|
||||
{ id: "8", name: "KSP" },
|
||||
];
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||
detail?.category.id
|
||||
);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
|
|
@ -158,10 +181,6 @@ export default function FormImageUpdate() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -174,11 +193,11 @@ export default function FormImageUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
|
|
@ -193,14 +212,24 @@ export default function FormImageUpdate() {
|
|||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Clear input field
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
|
|
@ -238,11 +267,13 @@ export default function FormImageUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -259,7 +290,32 @@ export default function FormImageUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: ImageSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -271,9 +327,9 @@ export default function FormImageUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -451,16 +507,18 @@ export default function FormImageUpdate() {
|
|||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-foreground">
|
||||
{file.fileName || file.name}
|
||||
</div>
|
||||
<div className="text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -475,10 +533,10 @@ export default function FormImageUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
|
@ -515,6 +573,55 @@ export default function FormImageUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteFile = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus file",
|
||||
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDelete(id: number) {
|
||||
const data = { id };
|
||||
|
||||
try {
|
||||
const response = await deleteFile(data);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Jika berhasil, hapus file dari state lokal
|
||||
setFiles((prevFiles: any) =>
|
||||
prevFiles.filter((file: any) => file.id !== id)
|
||||
);
|
||||
success();
|
||||
} catch (err) {
|
||||
error("Terjadi kesalahan saat menghapus file");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -549,14 +656,17 @@ export default function FormImageUpdate() {
|
|||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<Select
|
||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
||||
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category:", id);
|
||||
setSelectedTarget(id);
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedTarget(id); // Perbarui ID kategori
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
<SelectValue placeholder="Pilih">
|
||||
{categories.find((cat) => cat.id === selectedTarget)
|
||||
?.name || "Pilih"}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
|
|
@ -619,12 +729,12 @@ export default function FormImageUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
onClick={() => handleDeleteFile(id)}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
Remove file
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -782,24 +892,30 @@ export default function FormImageUpdate() {
|
|||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 ">
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag}{" "}
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* <div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
|
|
@ -808,44 +924,27 @@ export default function FormImageUpdate() {
|
|||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt) => opt.id !== "all").length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -96,6 +96,13 @@ interface PlacementData {
|
|||
placements: string;
|
||||
}
|
||||
|
||||
type FileType = {
|
||||
contentId: number;
|
||||
contentFile: string;
|
||||
thumbnailFileUrl: string;
|
||||
fileName: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
|
|
@ -122,7 +129,7 @@ export default function FormConvertSPIT() {
|
|||
null
|
||||
);
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
const [detail, setDetail] = useState<any>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
|
|
@ -150,7 +157,9 @@ export default function FormConvertSPIT() {
|
|||
polres: false,
|
||||
});
|
||||
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[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
|
|
@ -201,6 +210,17 @@ export default function FormConvertSPIT() {
|
|||
initState();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
userLevelId != undefined &&
|
||||
roleId != undefined &&
|
||||
userLevelId == "216" &&
|
||||
roleId == "3"
|
||||
) {
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
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(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -244,6 +305,8 @@ export default function FormConvertSPIT() {
|
|||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setFiles(details?.contentList);
|
||||
setupPlacementCheck(details?.contentList?.length);
|
||||
|
||||
const filesData = details.contentList || [];
|
||||
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) => {
|
||||
setTempFile((prevTempFile: 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: {
|
||||
contentTitle: string;
|
||||
contentDescription: string;
|
||||
contentRewriteDescription: string;
|
||||
contentCreator: string;
|
||||
}): Promise<void> => {
|
||||
const temp = [];
|
||||
for (const element of detail.contentList) {
|
||||
temp.push([]);
|
||||
}
|
||||
const description =
|
||||
selectedFileType === "original"
|
||||
? data.contentDescription
|
||||
|
|
@ -376,15 +416,16 @@ export default function FormConvertSPIT() {
|
|||
description,
|
||||
htmlDescription: description,
|
||||
tags: "siap",
|
||||
categoryId: selectedCategoryId,
|
||||
categoryId: 1,
|
||||
publishedFor: publishedFor.join(","),
|
||||
creator: data.contentCreator,
|
||||
files: getPlacement(), // Include placement data
|
||||
files: isUserMabesApprover ? getPlacement() : [], // Include placement data
|
||||
};
|
||||
|
||||
const response = await convertSPIT(requestData);
|
||||
console.log("Form Data Submitted:", response);
|
||||
|
||||
setFilePlacements(temp);
|
||||
setFiles(detail.files);
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
|
|
@ -699,62 +740,96 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isMabesApprover ? (
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">
|
||||
Penempatan File
|
||||
</Label>
|
||||
{detailThumb.map((data: any) => (
|
||||
<div
|
||||
key={data.contentId}
|
||||
className="flex items-center gap-3 mt-2"
|
||||
>
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
alt={`Thumbnail ${data.contentId}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
{[
|
||||
"all",
|
||||
"mabes",
|
||||
"polda",
|
||||
"satker",
|
||||
"internasional",
|
||||
].map((value) => (
|
||||
<label
|
||||
key={value}
|
||||
className="text-blue-500 cursor-pointer flex items-center gap-1"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value={value}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeFile(
|
||||
data.contentId,
|
||||
value
|
||||
)
|
||||
}
|
||||
checked={
|
||||
tempFile
|
||||
.find(
|
||||
(file: FileData) =>
|
||||
file.contentId === data.contentId
|
||||
)
|
||||
?.placement?.includes(value) || false
|
||||
}
|
||||
/>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</label>
|
||||
))}
|
||||
{files?.map((file, index) => (
|
||||
<div
|
||||
key={file.contentId}
|
||||
className="flex flex-row gap-2 items-center my-3"
|
||||
>
|
||||
<img
|
||||
src={file.contentFile}
|
||||
className="w-[150px] rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
{/* <a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a> */}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
value="all"
|
||||
checked={filePlacements[index]?.includes("all")}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "all", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes("mabes")}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "mabes", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<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>
|
||||
</Card>
|
||||
|
|
@ -797,14 +872,16 @@ export default function FormConvertSPIT() {
|
|||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.contentTag?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
{detail?.contentTag
|
||||
?.split(",")
|
||||
.map((tag: any, index: any) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
|
|||
import { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
|
||||
|
|
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
|
|||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
let fileTypeId = "3";
|
||||
|
||||
|
|
@ -164,10 +165,6 @@ export default function FormTeksUpdate() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string): void => {
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
.map((opt: any) => opt.id)
|
||||
);
|
||||
}
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
const updatedPublishedFor = publishedFor.includes(id)
|
||||
? publishedFor.filter((item) => item !== id)
|
||||
: [...publishedFor, id];
|
||||
|
||||
// Remove "all" if any checkbox is unchecked
|
||||
if (publishedFor.includes("all") && id !== "all") {
|
||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||
} else {
|
||||
setPublishedFor(updatedPublishedFor);
|
||||
}
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: TeksSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -293,7 +284,7 @@ export default function FormTeksUpdate() {
|
|||
statusId: "1",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -535,6 +526,29 @@ export default function FormTeksUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -785,7 +799,7 @@ export default function FormTeksUpdate() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
{/* <div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
|
|
@ -794,18 +808,38 @@ export default function FormTeksUpdate() {
|
|||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -817,7 +851,6 @@ export default function FormTeksUpdate() {
|
|||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
value={detail?.publishedForObject.name}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -683,7 +684,7 @@ export default function FormVideoDetail() {
|
|||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a>
|
||||
</div>
|
||||
{isUserMabesApprover &&
|
||||
{isUserMabesApprover && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
|
|
@ -760,7 +761,7 @@ export default function FormVideoDetail() {
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ export default function FormVideo() {
|
|||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
|
|
@ -544,11 +544,11 @@ export default function FormVideo() {
|
|||
filename: file.name,
|
||||
filetype: file.type,
|
||||
duration,
|
||||
isWatermark: "false", // hardcode
|
||||
isWatermark: "true", // hardcode
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject()
|
||||
xhr.withCredentials = true
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -53,7 +54,7 @@ import Image from "next/image";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
|
||||
const videoSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -88,6 +89,11 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function FormVideoUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -126,11 +132,20 @@ export default function FormVideoUpdate() {
|
|||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
{ id: "5", name: "UMUM" },
|
||||
{ id: "6", name: "JOURNALIS" },
|
||||
{ id: "7", name: "POLRI" },
|
||||
{ id: "8", name: "KSP" },
|
||||
];
|
||||
|
||||
let fileTypeId = "2";
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
|
|
@ -159,10 +174,6 @@ export default function FormVideoUpdate() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -175,12 +186,6 @@ export default function FormVideoUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
getCategories();
|
||||
|
|
@ -189,6 +194,29 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, []);
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
|
|
@ -226,11 +254,13 @@ export default function FormVideoUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -253,7 +283,26 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: VideoSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -265,9 +314,9 @@ export default function FormVideoUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -295,32 +344,33 @@ export default function FormVideoUpdate() {
|
|||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/content/image");
|
||||
});
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// text: "Data berhasil disimpan.",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then(() => {
|
||||
// router.push("/en/contributor/content/video");
|
||||
// });
|
||||
};
|
||||
|
||||
const onSubmit = (data: VideoSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/content/video");
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -385,22 +435,6 @@ export default function FormVideoUpdate() {
|
|||
upload.start();
|
||||
}
|
||||
|
||||
const onSubmit = (data: VideoSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const successSubmit = (redirect: string) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -454,16 +488,18 @@ export default function FormVideoUpdate() {
|
|||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-foreground">
|
||||
{file.fileName || file.name}
|
||||
</div>
|
||||
<div className="text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -478,10 +514,10 @@ export default function FormVideoUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
|
@ -518,6 +554,55 @@ export default function FormVideoUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteFile = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus file",
|
||||
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDelete(id: number) {
|
||||
const data = { id };
|
||||
|
||||
try {
|
||||
const response = await deleteFile(data);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Jika berhasil, hapus file dari state lokal
|
||||
setFiles((prevFiles: any) =>
|
||||
prevFiles.filter((file: any) => file.id !== id)
|
||||
);
|
||||
success();
|
||||
} catch (err) {
|
||||
error("Terjadi kesalahan saat menghapus file");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -626,12 +711,12 @@ export default function FormVideoUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -782,14 +867,34 @@ export default function FormVideoUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -797,38 +902,21 @@ export default function FormVideoUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt) => opt.id !== "all").length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
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 { addDays, format, parseISO, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -28,7 +28,19 @@ import MapHome from "@/components/maps/MapHome";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
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({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -56,6 +68,8 @@ export default function FormEventDetail() {
|
|||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
const [date, setDate] = useState<DateRange | undefined>();
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
const [nextDayList, setNextDayList] = useState([]);
|
||||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
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(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -89,6 +113,7 @@ export default function FormEventDetail() {
|
|||
setStartTime(details.startTime);
|
||||
setEndTime(details.endTime);
|
||||
}
|
||||
getDataByDate();
|
||||
}
|
||||
}
|
||||
initState();
|
||||
|
|
@ -315,7 +340,66 @@ export default function FormEventDetail() {
|
|||
</div>
|
||||
</Card>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -40,6 +39,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import { error } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { postSchedule } from "@/service/schedule/schedule";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
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 { addDays, format, parseISO, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -28,7 +28,12 @@ import MapHome from "@/components/maps/MapHome";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
||||
import {
|
||||
detailSchedule,
|
||||
listScheduleNext,
|
||||
listScheduleToday,
|
||||
postSchedule,
|
||||
} from "@/service/schedule/schedule";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -36,6 +41,13 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -64,6 +76,8 @@ export default function FormDetailPressRillis() {
|
|||
const [endTime, setEndTime] = useState("09:00");
|
||||
const [date, setDate] = useState<DateRange | undefined>();
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
const [nextDayList, setNextDayList] = useState([]);
|
||||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
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(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -97,6 +121,7 @@ export default function FormDetailPressRillis() {
|
|||
setStartTime(details.startTime);
|
||||
setEndTime(details.endTime);
|
||||
}
|
||||
getDataByDate();
|
||||
}
|
||||
}
|
||||
initState();
|
||||
|
|
@ -340,7 +365,66 @@ export default function FormDetailPressRillis() {
|
|||
</div>
|
||||
</Card>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
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 { addDays, format, parseISO, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -28,7 +28,20 @@ import MapHome from "@/components/maps/MapHome";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
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({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -56,6 +69,8 @@ export default function FormDetailPressConference() {
|
|||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
const [date, setDate] = useState<DateRange | undefined>();
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
const [nextDayList, setNextDayList] = useState([]);
|
||||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
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(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -89,6 +114,7 @@ export default function FormDetailPressConference() {
|
|||
setStartTime(details.startTime);
|
||||
setEndTime(details.endTime);
|
||||
}
|
||||
getDataByDate();
|
||||
}
|
||||
}
|
||||
initState();
|
||||
|
|
@ -315,7 +341,66 @@ export default function FormDetailPressConference() {
|
|||
</div>
|
||||
</Card>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Popover,
|
||||
|
|
@ -30,6 +29,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import { error } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { postSchedule } from "@/service/schedule/schedule";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -167,10 +167,10 @@ export default function FormPressConference() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="mt-5">
|
||||
<div className="mt-4 mb-3">
|
||||
<Label>Live Streaming</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<p>Aktifkan fitur live streaming</p>
|
||||
<Label>Aktifkan fitur live streaming</Label>
|
||||
<Switch
|
||||
defaultChecked={isLiveStreamingEnabled}
|
||||
color="primary"
|
||||
|
|
@ -184,7 +184,7 @@ export default function FormPressConference() {
|
|||
</div>
|
||||
|
||||
{isLiveStreamingEnabled && (
|
||||
<div className="mt-1">
|
||||
<div className="mb-2 mt-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -206,16 +206,17 @@ export default function FormPressConference() {
|
|||
</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 ">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="md"
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
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"
|
||||
)}
|
||||
>
|
||||
|
|
@ -292,7 +293,7 @@ export default function FormPressConference() {
|
|||
{errors.location?.message}
|
||||
</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="mt-1">
|
||||
<Label>Nama Pangkat</Label>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,16 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} 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 { Link } from "@/components/navigation";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
|
@ -50,6 +59,10 @@ import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
|||
import { successCallback } from "@/config/swal";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||
import Image from "next/image";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
|
||||
const taskSchema = z.object({
|
||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -162,6 +175,11 @@ interface UploadResult {
|
|||
creatorName: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function FormTaskDetail() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -191,6 +209,7 @@ export default function FormTaskDetail() {
|
|||
const [type, setType] = useState<string>("1");
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [urlInputs, setUrlInputs] = useState<string[]>([]);
|
||||
const [refresh] = useState(false);
|
||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
|
|
@ -224,11 +243,40 @@ export default function FormTaskDetail() {
|
|||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [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 [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
satker: false,
|
||||
polres: false,
|
||||
});
|
||||
|
||||
|
|
@ -283,32 +331,60 @@ export default function FormTaskDetail() {
|
|||
setUploadResults(details.uploadResults || []);
|
||||
}
|
||||
|
||||
if (details?.urls) {
|
||||
// Extract attachmentUrl from each object in urls
|
||||
const urls = details.urls.map(
|
||||
(urlObj: any) => urlObj.attachmentUrl || ""
|
||||
);
|
||||
setUrlInputs(urls); // Save the URLs to state
|
||||
}
|
||||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
);
|
||||
setCheckedLevels(levels);
|
||||
}
|
||||
|
||||
const attachment = details?.files;
|
||||
setImageUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||
);
|
||||
setVideoUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 2)
|
||||
);
|
||||
setTextUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 3)
|
||||
);
|
||||
setAudioUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 4)
|
||||
);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleUrlChange = (index: number, newUrl: string) => {
|
||||
setUrlInputs((prev: any) =>
|
||||
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (detail?.broadcastType) {
|
||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
||||
setBroadcastType(detail.broadcastType);
|
||||
}
|
||||
}, [detail?.broadcastType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (detail?.fileTypeOutput) {
|
||||
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
|
||||
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number));
|
||||
setTaskOutput({
|
||||
all: outputSet.has(0),
|
||||
all: outputSet.has(1),
|
||||
video: outputSet.has(2),
|
||||
audio: outputSet.has(4),
|
||||
image: outputSet.has(1),
|
||||
text: outputSet.has(3),
|
||||
image: outputSet.has(3),
|
||||
text: outputSet.has(5),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -323,75 +399,11 @@ export default function FormTaskDetail() {
|
|||
mabes: outputSet.has(1),
|
||||
polda: outputSet.has(2),
|
||||
polres: outputSet.has(3),
|
||||
satker: outputSet.has(4),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "3",
|
||||
image: "4",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
const selectedOutputs = Object.keys(taskOutput)
|
||||
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
|
||||
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
|
||||
.join(",");
|
||||
|
||||
const requestData = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
// assignmentCategory,
|
||||
target: selectedTarget,
|
||||
unitSelection,
|
||||
assignedToRole: "3",
|
||||
taskType: taskType,
|
||||
broadcastType: broadcastType,
|
||||
assignmentMainTypeId: mainType,
|
||||
assignmentPurpose: "1",
|
||||
assignmentTypeId: type,
|
||||
fileTypeOutput: selectedOutputs,
|
||||
id: null,
|
||||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
};
|
||||
|
||||
const response = await createTask(requestData);
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
console.log("response", response);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/task");
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const successConfirm = () => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -445,21 +457,6 @@ export default function FormTaskDetail() {
|
|||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
// const handleSubmitResponse = (): void => {
|
||||
// if (response.trim()) {
|
||||
// const newResponse: Response = {
|
||||
// id: Date.now(), // Unique ID for each response
|
||||
// name: "Mabes Polri - Approver",
|
||||
// content: response,
|
||||
// timestamp: new Date().toLocaleString("id-ID"), // Format timestamp for Indonesia locale
|
||||
// };
|
||||
|
||||
// setResponses([newResponse, ...responses]); // Add new response to the top
|
||||
// setResponse(""); // Reset textarea
|
||||
// setShowInput(false); // Hide input
|
||||
// }
|
||||
// };
|
||||
|
||||
const postData = () => {
|
||||
sendSuggestionParent();
|
||||
};
|
||||
|
|
@ -527,29 +524,6 @@ export default function FormTaskDetail() {
|
|||
console.log(dataId);
|
||||
};
|
||||
|
||||
// async function sendSuggestionChild(parentId: any) {
|
||||
// const msg = document.querySelectorAll(`#input-comment-${parentId}`)[0]
|
||||
// .value;
|
||||
|
||||
// if (msg?.length > 1) {
|
||||
// loading();
|
||||
// const data = {
|
||||
// assignmentId: id,
|
||||
// message: msg,
|
||||
// parentId,
|
||||
// };
|
||||
|
||||
// console.log(data);
|
||||
// const response = await createAssignmentResponse(data);
|
||||
// console.log(response);
|
||||
// const responseGet = await getAssignmentResponseList(id);
|
||||
// console.log(responseGet?.data?.data);
|
||||
// setListData(responseGet?.data?.data);
|
||||
// // $(":input").val("");
|
||||
// close();
|
||||
// }
|
||||
// }
|
||||
|
||||
async function getDataAcceptance() {
|
||||
const response = await getAcceptanceAssignmentStatus(id);
|
||||
setStatusAcceptance(response?.data?.data?.isAccept);
|
||||
|
|
@ -752,6 +726,29 @@ export default function FormTaskDetail() {
|
|||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||
};
|
||||
|
||||
const renderFilePreview = (url: string) => {
|
||||
return (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={"file preview"}
|
||||
src={url}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onReady = (ws: any) => {
|
||||
setWavesurfer(ws);
|
||||
setIsPlaying(false);
|
||||
};
|
||||
|
||||
const onPlayPause = () => {
|
||||
wavesurfer && wavesurfer.playPause();
|
||||
};
|
||||
|
||||
const handleRemoveFile = (id: number) => {};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -802,7 +799,7 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2">
|
||||
<Label>Kode Unik</Label>
|
||||
|
|
@ -1033,27 +1030,7 @@ export default function FormTaskDetail() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="mt-6">
|
||||
<Label>Broadcast </Label>
|
||||
<RadioGroup
|
||||
value={broadcastType}
|
||||
onValueChange={(value) => setBroadcastType(value)}
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="all" id="all" />
|
||||
<Label htmlFor="all">Semua</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="email" id="email" />
|
||||
<Label htmlFor="email">Email Blast</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="whatsapp" id="whatsapp" />
|
||||
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div> */}
|
||||
|
||||
<div className="mt-6">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<Controller
|
||||
|
|
@ -1063,70 +1040,236 @@ export default function FormTaskDetail() {
|
|||
<ViewEditor initialData={detail?.narration} />
|
||||
)}
|
||||
/>
|
||||
{errors.naration?.message && (
|
||||
{/* {errors.naration?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.naration.message}
|
||||
</p>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<Label htmlFor="attachment">Lampiran</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
"mov/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
||||
<div>
|
||||
{selectedVideo && (
|
||||
<Card className="mt-2">
|
||||
<video
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={selectedVideo}
|
||||
controls
|
||||
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>
|
||||
<Label>Foto</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
|
||||
<div>
|
||||
{selectedImage && (
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={selectedImage}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
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>
|
||||
<Label>Teks</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
|
||||
<div>
|
||||
{selectedText && (
|
||||
<Card className="mt-2">
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||
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>
|
||||
<Label>Voice Note</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
}}
|
||||
downloadOnSavePress={true}
|
||||
downloadFileExtension="webm"
|
||||
/>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp3/*": [],
|
||||
"wav/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
className="mt-2"
|
||||
/>
|
||||
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
|
||||
<div>
|
||||
{selectedAudio && (
|
||||
<Card className="mt-2">
|
||||
<div key={selectedAudio}>
|
||||
<WavesurferPlayer
|
||||
height={500}
|
||||
waveColor="red"
|
||||
url={selectedAudio}
|
||||
onReady={onReady}
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onPause={() => setIsPlaying(false)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onPlayPause}
|
||||
disabled={isPlaying}
|
||||
className={`flex items-center gap-2 ${
|
||||
isPlaying
|
||||
? "bg-gray-300 cursor-not-allowed"
|
||||
: "bg-primary text-white"
|
||||
} p-2 rounded`}
|
||||
>
|
||||
{isPlaying ? "Pause" : "Play"}
|
||||
<Icon
|
||||
icon={
|
||||
isPlaying
|
||||
? "carbon:pause-outline"
|
||||
: "famicons:play-sharp"
|
||||
}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</Button>
|
||||
</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>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
|
|
@ -1144,14 +1287,23 @@ export default function FormTaskDetail() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
<Label>Link Url</Label>
|
||||
{urlInputs.map((url: any, index: any) => (
|
||||
<div
|
||||
key={url.id}
|
||||
className="flex items-center gap-2 mt-2"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -1159,21 +1311,20 @@ export default function FormTaskDetail() {
|
|||
<div className="flex flex-row justify-between gap-3 my-3">
|
||||
<div className="px-1">
|
||||
{detail?.isDone !== true &&
|
||||
(Number(userLevelNumber) !== 3 ||
|
||||
Number(userLevelNumber) == 2) ? (
|
||||
<Button
|
||||
color="primary"
|
||||
variant={"outline"}
|
||||
type="button"
|
||||
className="btn btn-outline-primary ml-3"
|
||||
onClick={() => handleForward()}
|
||||
>
|
||||
Forward
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
}
|
||||
(Number(userLevelNumber) !== 3 ||
|
||||
Number(userLevelNumber) == 2) ? (
|
||||
<Button
|
||||
color="primary"
|
||||
variant={"outline"}
|
||||
type="button"
|
||||
className="btn btn-outline-primary ml-3"
|
||||
onClick={() => handleForward()}
|
||||
>
|
||||
Forward
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="">
|
||||
|
|
@ -1189,7 +1340,8 @@ export default function FormTaskDetail() {
|
|||
<Button
|
||||
className="btn btn-primary ml-3 mr-3"
|
||||
style={
|
||||
statusAcceptance || detail?.createdBy?.id !== Number(userId)
|
||||
statusAcceptance ||
|
||||
detail?.createdBy?.id !== Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
|
|
@ -1211,6 +1363,7 @@ export default function FormTaskDetail() {
|
|||
// }
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
variant={"default"}
|
||||
onClick={() => setIsTableResult(!isTableResult)}
|
||||
|
|
@ -1402,10 +1555,14 @@ export default function FormTaskDetail() {
|
|||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child2.id)}
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,9 +32,22 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} 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 { AudioRecorder } from "react-audio-voice-recorder";
|
||||
import Image from "next/image";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -62,6 +75,7 @@ export type taskDetail = {
|
|||
taskType: string;
|
||||
broadcastType: string;
|
||||
narration: string;
|
||||
attachmentUrl: string;
|
||||
is_active: string;
|
||||
};
|
||||
|
||||
|
|
@ -69,6 +83,16 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Url = {
|
||||
id: number;
|
||||
attachmentUrl: string;
|
||||
};
|
||||
|
||||
export default function FormTaskEdit() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -101,6 +125,7 @@ export default function FormTaskEdit() {
|
|||
const [type, setType] = useState<string>("1");
|
||||
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
|
||||
const [refresh] = useState(false);
|
||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
|
|
@ -110,14 +135,30 @@ export default function FormTaskEdit() {
|
|||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
|
||||
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: false,
|
||||
});
|
||||
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
|
|
@ -165,6 +206,11 @@ export default function FormTaskEdit() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
if (details?.urls) {
|
||||
// Save the URLs as objects
|
||||
setUrlInputs(details.urls);
|
||||
}
|
||||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
|
|
@ -172,12 +218,32 @@ export default function FormTaskEdit() {
|
|||
setCheckedLevels(levels);
|
||||
}
|
||||
|
||||
const attachment = details?.files;
|
||||
setImageUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||
);
|
||||
setVideoUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 2)
|
||||
);
|
||||
setTextUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 3)
|
||||
);
|
||||
setAudioUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 4)
|
||||
);
|
||||
|
||||
// Add more state setting here based on other fields like broadcastType, taskOutput, etc.
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleUrlChange = (index: number, newUrl: string) => {
|
||||
setUrlInputs((prev: any) =>
|
||||
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (detail?.broadcastType) {
|
||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
||||
|
|
@ -188,11 +254,11 @@ export default function FormTaskEdit() {
|
|||
if (detail?.fileTypeOutput) {
|
||||
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
|
||||
setTaskOutput({
|
||||
all: outputSet.has(0),
|
||||
all: outputSet.has(1),
|
||||
video: outputSet.has(2),
|
||||
audio: outputSet.has(4),
|
||||
image: outputSet.has(1),
|
||||
text: outputSet.has(3),
|
||||
image: outputSet.has(3),
|
||||
text: outputSet.has(5),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -207,6 +273,7 @@ export default function FormTaskEdit() {
|
|||
mabes: outputSet.has(1),
|
||||
polda: outputSet.has(2),
|
||||
polres: outputSet.has(3),
|
||||
satker: outputSet.has(4),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -231,8 +298,8 @@ export default function FormTaskEdit() {
|
|||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "3",
|
||||
image: "4",
|
||||
audio: "4",
|
||||
image: "3",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
|
|
@ -241,6 +308,7 @@ export default function FormTaskEdit() {
|
|||
mabes: "1",
|
||||
polda: "2",
|
||||
polres: "3",
|
||||
satker: "4",
|
||||
};
|
||||
const assignmentPurposeString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
|
|
@ -265,6 +333,7 @@ export default function FormTaskEdit() {
|
|||
taskType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
|
|
@ -281,22 +350,58 @@ export default function FormTaskEdit() {
|
|||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
||||
const response = await createTask(requestData);
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
console.log("response", response);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/task");
|
||||
const id = response?.data?.data.id;
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
imageFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "1", "0");
|
||||
});
|
||||
|
||||
if (videoFiles?.length == 0) {
|
||||
setIsVideoUploadFinish(true);
|
||||
}
|
||||
videoFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "2", "0");
|
||||
});
|
||||
|
||||
if (textFiles?.length == 0) {
|
||||
setIsTextUploadFinish(true);
|
||||
}
|
||||
textFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "3", "0");
|
||||
});
|
||||
|
||||
if (audioFiles?.length == 0) {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
audioFiles.map(async (item: 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) => {
|
||||
|
|
@ -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 = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
|
|
@ -365,6 +452,171 @@ export default function FormTaskEdit() {
|
|||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||
};
|
||||
|
||||
const addAudioElement = (blob: Blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const audio = document.createElement("audio");
|
||||
audio.src = url;
|
||||
audio.controls = true;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
// Convert Blob to File 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 (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -372,23 +624,6 @@ export default function FormTaskEdit() {
|
|||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* <div className="space-y-2">
|
||||
<Label>Kode Unik</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="uniqueCode"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={detail?.uniqueCode}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter uniqueCode"
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="space-y-2 mt-6">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
|
|
@ -530,7 +765,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setMainType(value)}
|
||||
// value={String(mainType)}
|
||||
// onValueChange={(value) => setMainType(Number(value))}
|
||||
|
|
@ -545,7 +780,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Jenis Tugas </Label>
|
||||
<RadioGroup
|
||||
value={detail.taskType.toString()}
|
||||
defaultValue={detail.taskType.toString()}
|
||||
onValueChange={(value) => setTaskType(String(value))}
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
|
|
@ -559,7 +794,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
|
|
@ -628,8 +863,37 @@ export default function FormTaskEdit() {
|
|||
}}
|
||||
maxSize={100}
|
||||
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>
|
||||
<Label>Foto</Label>
|
||||
|
|
@ -641,6 +905,35 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<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>
|
||||
<Label>Teks</Label>
|
||||
|
|
@ -652,9 +945,38 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
{textUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<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>
|
||||
<Label>Voice Note</Label>
|
||||
<Label>Audio</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -671,13 +993,44 @@ export default function FormTaskEdit() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
onDrop={(files) =>
|
||||
setAudioFiles((prev) => [...prev, ...files])
|
||||
}
|
||||
className="mt-2"
|
||||
/>
|
||||
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<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>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
||||
<p>Voice Note</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDeleteAudio}
|
||||
|
|
@ -691,14 +1044,30 @@ export default function FormTaskEdit() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
{urlInputs.map((url: any, index: any) => (
|
||||
<div
|
||||
key={url.id}
|
||||
className="flex items-center gap-2 mt-2"
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -37,12 +37,15 @@ import { AudioRecorder } from "react-audio-voice-recorder";
|
|||
import FileUploader from "@/components/form/shared/file-uploader";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { error } from "@/config/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
// url: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
|
|
@ -63,6 +66,7 @@ export type taskDetail = {
|
|||
id: number;
|
||||
name: string;
|
||||
};
|
||||
attachmentUrl: string;
|
||||
taskType: string;
|
||||
broadcastType: string;
|
||||
narration: string;
|
||||
|
|
@ -119,7 +123,9 @@ export default function FormTask() {
|
|||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: false,
|
||||
});
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
register,
|
||||
|
|
@ -144,6 +150,7 @@ export default function FormTask() {
|
|||
try {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setListDest(response?.data?.data.list);
|
||||
console.log("polda", response?.data?.data?.list);
|
||||
const initialExpandedState = response?.data?.data.list.reduce(
|
||||
(acc: any, polda: any) => {
|
||||
acc[polda.id] = false;
|
||||
|
|
@ -183,8 +190,8 @@ export default function FormTask() {
|
|||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "3",
|
||||
image: "4",
|
||||
audio: "4",
|
||||
image: "3",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
|
|
@ -193,6 +200,7 @@ export default function FormTask() {
|
|||
mabes: "1",
|
||||
polda: "2",
|
||||
polres: "3",
|
||||
satker: "4",
|
||||
};
|
||||
const assignmentPurposeString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
|
|
@ -217,6 +225,7 @@ export default function FormTask() {
|
|||
taskType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
|
|
@ -232,6 +241,7 @@ export default function FormTask() {
|
|||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
||||
const response = await createTask(requestData);
|
||||
|
|
@ -240,7 +250,7 @@ export default function FormTask() {
|
|||
console.log("response", response);
|
||||
|
||||
const id = response?.data?.data.id;
|
||||
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
|
|
@ -265,19 +275,15 @@ export default function FormTask() {
|
|||
if (audioFiles?.length == 0) {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
audioFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
||||
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) => {
|
||||
|
|
@ -335,15 +341,19 @@ export default function FormTask() {
|
|||
audio.controls = true;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
// Convert Blob to File
|
||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
||||
setAudioFile(file);
|
||||
// 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());
|
||||
|
||||
const handleDeleteAudio = (index: number) => {
|
||||
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
|
||||
};
|
||||
|
||||
async function uploadResumableFile(
|
||||
|
|
@ -355,20 +365,28 @@ export default function FormTask() {
|
|||
) {
|
||||
console.log(idx, id, file, fileTypeId, duration);
|
||||
|
||||
// const placements = getPlacement(file.placements);
|
||||
// console.log("Placementttt: : ", placements);
|
||||
const resCsrf = await getCsrfToken();
|
||||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
|
||||
headers: headers,
|
||||
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||
chunkSize: 20_000,
|
||||
metadata: {
|
||||
assignmentid: id,
|
||||
assignmentId: id,
|
||||
filename: file.name,
|
||||
filetype: file.type,
|
||||
contentType: file.type,
|
||||
fileTypeId: fileTypeId,
|
||||
duration: "",
|
||||
isWatermark: "true", // hardcode
|
||||
duration,
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
@ -379,7 +397,7 @@ export default function FormTask() {
|
|||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
) => {
|
||||
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// progressInfo[idx].percentage = uploadPersen;
|
||||
// counterUpdateProgress++;
|
||||
// console.log(counterUpdateProgress);
|
||||
|
|
@ -441,6 +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 (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -702,7 +736,7 @@ export default function FormTask() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -728,7 +762,7 @@ export default function FormTask() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
<Label>Audio</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -745,34 +779,61 @@ export default function FormTask() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
onDrop={(files) =>
|
||||
setAudioFiles((prev) => [...prev, ...files])
|
||||
}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
||||
{audioFiles?.map((audio: any, idx: any) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-row justify-between items-center"
|
||||
>
|
||||
<p>Voice Note</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDeleteAudio}
|
||||
onClick={() => handleDeleteAudio(idx)}
|
||||
size="sm"
|
||||
color="destructive"
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
{links.map((link, index) => (
|
||||
<div key={index} className="flex items-center gap-2 mt-2">
|
||||
<input
|
||||
type="url"
|
||||
className="border rounded p-2 w-full"
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
value={link}
|
||||
onChange={(e) =>
|
||||
handleLinkChange(index, e.target.value)
|
||||
}
|
||||
/>
|
||||
{links.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="bg-red-500 text-white px-3 py-1 rounded"
|
||||
onClick={() => handleRemoveRow(index)}
|
||||
>
|
||||
Hapus
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onClick={handleAddRow}
|
||||
>
|
||||
Tambah Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
|
|
@ -1,5 +1,8 @@
|
|||
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
|
||||
|
||||
import {
|
||||
httpDeleteInterceptor,
|
||||
httpGetInterceptor,
|
||||
httpPostInterceptor,
|
||||
} from "../http-config/http-interceptor-service";
|
||||
|
||||
export async function getAgendaSettingsById(id: any) {
|
||||
const url = `agenda-settings?id=${id}`;
|
||||
|
|
@ -84,3 +87,8 @@ export async function getPlanningDailyMedsosByPlatform(
|
|||
const url = `planning/pagination/daily?enablePage=1&size=${size}&page=${page}&date=${date}&typeId=2&platformTypeId=${platformTypeId}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function deleteAgendaSettings(id: any) {
|
||||
const url = `agenda-settings?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
|
||||
import {
|
||||
httpDeleteInterceptor,
|
||||
httpGetInterceptor,
|
||||
httpPostInterceptor,
|
||||
} from "../http-config/http-interceptor-service";
|
||||
|
||||
export async function paginationBlog(
|
||||
size: number,
|
||||
|
|
@ -22,5 +26,13 @@ export async function getBlog(id: any) {
|
|||
|
||||
export async function uploadThumbnailBlog(id: any, data: any) {
|
||||
const url = `blog/${id}/thumbnail`;
|
||||
return httpPostInterceptor(url, data);
|
||||
const headers = {
|
||||
"Content-Type": "multipart/form-data",
|
||||
};
|
||||
return httpPostInterceptor(url, data, headers);
|
||||
}
|
||||
|
||||
export async function deleteBlog(id: any) {
|
||||
const url = `blog?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,3 +115,8 @@ export async function deleteQuestion(id: string | number) {
|
|||
const url = `/question?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getEscalationDiscussion(id: any) {
|
||||
const url = `ticketing/escalation/discussion?ticketId=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
httpDeleteInterceptor,
|
||||
httpGetInterceptor,
|
||||
httpPostInterceptor,
|
||||
} from "../http-config/http-interceptor-service";
|
||||
|
|
@ -56,10 +57,11 @@ export async function listDataImage(
|
|||
source: any,
|
||||
startDate: any,
|
||||
endDate: any,
|
||||
title: string = ""
|
||||
title: string = "",
|
||||
creatorGroup: string = ""
|
||||
) {
|
||||
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,
|
||||
startDate: any,
|
||||
endDate: any,
|
||||
title: string = ""
|
||||
title: string = "",
|
||||
creatorGroup: string = ""
|
||||
) {
|
||||
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,
|
||||
startDate: any,
|
||||
endDate: any,
|
||||
title: string = ""
|
||||
title: string = "",
|
||||
creatorGroup: string = ""
|
||||
) {
|
||||
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,
|
||||
startDate: any,
|
||||
endDate: any,
|
||||
title: string = ""
|
||||
title: string = "",
|
||||
creatorGroup: string = ""
|
||||
) {
|
||||
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";
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
export async function saveUserReports(data: any) {
|
||||
const url = "public/users/reports";
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
||||
export async function deleteMedia(data: any) {
|
||||
const url = `media`;
|
||||
return httpDeleteInterceptor(url, data);
|
||||
}
|
||||
|
||||
export async function deleteFile(data: any) {
|
||||
const url = "media/file";
|
||||
return httpDeleteInterceptor(url, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@ export async function httpGetInterceptor(pathUrl: any) {
|
|||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||
Cookies.remove(cookieName);
|
||||
});
|
||||
if (pathname?.includes("/contributor/") || pathname?.includes("/admin/") || pathname?.includes("/supervisor/")) {
|
||||
if (
|
||||
pathname?.includes("/contributor/") ||
|
||||
pathname?.includes("/admin/") ||
|
||||
pathname?.includes("/supervisor/")
|
||||
) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
} else {
|
||||
|
|
@ -113,7 +117,7 @@ export async function httpPutInterceptor(
|
|||
}
|
||||
}
|
||||
|
||||
export async function httpDeleteInterceptor(pathUrl: any) {
|
||||
export async function httpDeleteInterceptor(pathUrl: any, data?: any) {
|
||||
const resCsrf = await getCsrfToken();
|
||||
const csrfToken = resCsrf?.data?.token;
|
||||
|
||||
|
|
@ -126,7 +130,7 @@ export async function httpDeleteInterceptor(pathUrl: any) {
|
|||
};
|
||||
|
||||
const response = await axiosInterceptorInstance
|
||||
.delete(pathUrl, { headers: mergedHeaders})
|
||||
.delete(pathUrl, { headers: mergedHeaders, data })
|
||||
.catch((error) => error.response);
|
||||
console.log("Response interceptor : ", response);
|
||||
if (response?.status == 200 || response?.status == 201) {
|
||||
|
|
|
|||
|
|
@ -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 { any } from "zod";
|
||||
|
||||
|
|
@ -35,7 +38,18 @@ export async function listSchedule(group = null) {
|
|||
const url = `public/schedule/list?group=${group}`;
|
||||
return httpGet(url, null);
|
||||
}
|
||||
|
||||
export async function searchSchedules(search = null) {
|
||||
const url = `public/schedule/list?search=${search}`;
|
||||
return httpGet(url, null);
|
||||
}
|
||||
|
||||
export async function listScheduleToday() {
|
||||
const url = "schedule/today";
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function listScheduleNext() {
|
||||
const url = "schedule/next-activity";
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,16 @@
|
|||
../typescript/bin/tsc
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
|
|
@ -1 +1,16 @@
|
|||
../typescript/bin/tsserver
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
All changes in the package are documented in the main repository. See: https://github.com/ckeditor/ckeditor5/blob/master/CHANGELOG.md.
|
||||
|
||||
Changes for the past releases are available below.
|
||||
|
||||
## [19.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v18.0.0...v19.0.0) (April 29, 2020)
|
||||
|
||||
Internal changes only (updated dependencies, documentation, etc.).
|
||||
|
||||
|
||||
## [18.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v17.0.0...v18.0.0) (March 19, 2020)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([f1beaaa](https://github.com/ckeditor/ckeditor5-alignment/commit/f1beaaa))
|
||||
|
||||
|
||||
## [17.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v16.0.0...v17.0.0) (February 18, 2020)
|
||||
|
||||
### MAJOR BREAKING CHANGES
|
||||
|
||||
* The `align-left`, `align-right`, `align-center`, and `align-justify` icons have been moved to `@ckeditor/ckeditor5-core`.
|
||||
|
||||
### Other changes
|
||||
|
||||
* Moved alignment icons to `@ckeditor/ckeditor5-core` (see [ckeditor/ckeditor5-table#227](https://github.com/ckeditor/ckeditor5-table/issues/227)). ([410e279](https://github.com/ckeditor/ckeditor5-alignment/commit/410e279))
|
||||
* Updated translations. ([288672f](https://github.com/ckeditor/ckeditor5-alignment/commit/288672f))
|
||||
|
||||
|
||||
## [16.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v15.0.0...v16.0.0) (December 4, 2019)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([9085f7b](https://github.com/ckeditor/ckeditor5-alignment/commit/9085f7b))
|
||||
|
||||
|
||||
## [15.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v11.2.0...v15.0.0) (October 23, 2019)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([a719974](https://github.com/ckeditor/ckeditor5-alignment/commit/a719974)) ([2fed077](https://github.com/ckeditor/ckeditor5-alignment/commit/2fed077))
|
||||
* Added `pluginName` to the editor plugin part of the feature. ([3b42798](https://github.com/ckeditor/ckeditor5-alignment/commit/3b42798))
|
||||
|
||||
|
||||
## [11.2.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v11.1.3...v11.2.0) (August 26, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
* Integrated the text alignment feature with different editor content directions (LTR and RTL). See [ckeditor/ckeditor5#1151](https://github.com/ckeditor/ckeditor5/issues/1151). ([edc7d8b](https://github.com/ckeditor/ckeditor5-alignment/commit/edc7d8b))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* The UI buttons should be marked as toggleable for better assistive technologies support (see [ckeditor/ckeditor5#1403](https://github.com/ckeditor/ckeditor5/issues/1403)). ([599ea01](https://github.com/ckeditor/ckeditor5-alignment/commit/599ea01))
|
||||
|
||||
### Other changes
|
||||
|
||||
* The issue tracker for this package was moved to https://github.com/ckeditor/ckeditor5/issues. See [ckeditor/ckeditor5#1988](https://github.com/ckeditor/ckeditor5/issues/1988). ([54f81b3](https://github.com/ckeditor/ckeditor5-alignment/commit/54f81b3))
|
||||
* The text alignment toolbar should have a proper `aria-label` attribute (see [ckeditor/ckeditor5#1404](https://github.com/ckeditor/ckeditor5/issues/1404)). ([3ed81de](https://github.com/ckeditor/ckeditor5-alignment/commit/3ed81de))
|
||||
* Updated translations. ([feb4ab3](https://github.com/ckeditor/ckeditor5-alignment/commit/feb4ab3))
|
||||
|
||||
|
||||
## [11.1.3](https://github.com/ckeditor/ckeditor5-alignment/compare/v11.1.2...v11.1.3) (July 10, 2019)
|
||||
|
||||
Internal changes only (updated dependencies, documentation, etc.).
|
||||
|
||||
|
||||
## [11.1.2](https://github.com/ckeditor/ckeditor5-alignment/compare/v11.1.1...v11.1.2) (July 4, 2019)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([bb7f494](https://github.com/ckeditor/ckeditor5-alignment/commit/bb7f494))
|
||||
|
||||
|
||||
## [11.1.1](https://github.com/ckeditor/ckeditor5-alignment/compare/v11.1.0...v11.1.1) (June 6, 2019)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([32c32c1](https://github.com/ckeditor/ckeditor5-alignment/commit/32c32c1))
|
||||
|
||||
|
||||
## [11.1.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v11.0.0...v11.1.0) (April 4, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
* Marked alignment as a formatting attribute using the `AttributeProperties#isFormatting` property. Closes [ckeditor/ckeditor5#1664](https://github.com/ckeditor/ckeditor5/issues/1664). ([6358e08](https://github.com/ckeditor/ckeditor5-alignment/commit/6358e08))
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([78bfc40](https://github.com/ckeditor/ckeditor5-alignment/commit/78bfc40))
|
||||
|
||||
|
||||
## [11.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v10.0.4...v11.0.0) (February 28, 2019)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([45e8dd5](https://github.com/ckeditor/ckeditor5-alignment/commit/45e8dd5)) ([a92c37b](https://github.com/ckeditor/ckeditor5-alignment/commit/a92c37b)) ([ef68e54](https://github.com/ckeditor/ckeditor5-alignment/commit/ef68e54))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Upgraded minimal versions of Node to `8.0.0` and npm to `5.7.1`. See: [ckeditor/ckeditor5#1507](https://github.com/ckeditor/ckeditor5/issues/1507). ([612ea3c](https://github.com/ckeditor/ckeditor5-cloud-services/commit/612ea3c))
|
||||
|
||||
|
||||
## [10.0.4](https://github.com/ckeditor/ckeditor5-alignment/compare/v10.0.3...v10.0.4) (December 5, 2018)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Improved SVG icons size. See [ckeditor/ckeditor5-theme-lark#206](https://github.com/ckeditor/ckeditor5-theme-lark/issues/206). ([1d71d33](https://github.com/ckeditor/ckeditor5-alignment/commit/1d71d33))
|
||||
* Updated translations. ([547f8d8](https://github.com/ckeditor/ckeditor5-alignment/commit/547f8d8)) ([43d8225](https://github.com/ckeditor/ckeditor5-alignment/commit/43d8225))
|
||||
|
||||
|
||||
## [10.0.3](https://github.com/ckeditor/ckeditor5-alignment/compare/v10.0.2...v10.0.3) (October 8, 2018)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([5b30202](https://github.com/ckeditor/ckeditor5-alignment/commit/5b30202))
|
||||
|
||||
|
||||
## [10.0.2](https://github.com/ckeditor/ckeditor5-alignment/compare/v10.0.1...v10.0.2) (July 18, 2018)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([33c281c](https://github.com/ckeditor/ckeditor5-alignment/commit/33c281c))
|
||||
|
||||
|
||||
## [10.0.1](https://github.com/ckeditor/ckeditor5-alignment/compare/v10.0.0...v10.0.1) (June 21, 2018)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations.
|
||||
|
||||
|
||||
## [10.0.0](https://github.com/ckeditor/ckeditor5-alignment/compare/v1.0.0-beta.4...v10.0.0) (April 25, 2018)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Changed the license to GPL2+ only. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991). ([eed1029](https://github.com/ckeditor/ckeditor5-alignment/commit/eed1029))
|
||||
* Updated translations. ([baa1fbe](https://github.com/ckeditor/ckeditor5-alignment/commit/baa1fbe))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* The license under which CKEditor 5 is released has been changed from a triple GPL, LGPL and MPL license to a GPL2+ only. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991) for more information.
|
||||
|
||||
|
||||
## [1.0.0-beta.4](https://github.com/ckeditor/ckeditor5-alignment/compare/v1.0.0-beta.2...v1.0.0-beta.4) (April 19, 2018)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Updated translations. ([586ae62](https://github.com/ckeditor/ckeditor5-alignment/commit/586ae62))
|
||||
|
||||
|
||||
## [1.0.0-beta.2](https://github.com/ckeditor/ckeditor5-alignment/compare/v1.0.0-beta.1...v1.0.0-beta.2) (April 10, 2018)
|
||||
|
||||
Internal changes only (updated dependencies, documentation, etc.).
|
||||
|
||||
|
||||
## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-alignment/compare/v0.0.1...v1.0.0-beta.1) (March 15, 2018)
|
||||
|
||||
### Features
|
||||
|
||||
* Initial implementation. Closes [#2](https://github.com/ckeditor/ckeditor5-alignment/issues/2).
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
Software License Agreement
|
||||
==========================
|
||||
|
||||
**CKEditor 5 text alignment feature** – https://github.com/ckeditor/ckeditor5-alignment <br>
|
||||
Copyright (c) 2003–2024, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
|
||||
|
||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
|
||||
|
||||
Sources of Intellectual Property Included in CKEditor
|
||||
-----------------------------------------------------
|
||||
|
||||
Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
|
||||
|
||||
Trademarks
|
||||
----------
|
||||
|
||||
**CKEditor** is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com) All other brand and product names are trademarks, registered trademarks, or service marks of their respective holders.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
CKEditor 5 text alignment feature
|
||||
========================================
|
||||
|
||||
[](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment)
|
||||
[](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
|
||||
[](https://app.travis-ci.com/github/ckeditor/ckeditor5)
|
||||
|
||||
This package implements text alignment support for CKEditor 5.
|
||||
|
||||
## Demo
|
||||
|
||||
Check out the [demo in the text alignment feature guide](https://ckeditor.com/docs/ckeditor5/latest/features/text-alignment.html#demo).
|
||||
|
||||
## Documentation
|
||||
|
||||
See the [`@ckeditor/ckeditor5-alignment` package](https://ckeditor.com/docs/ckeditor5/latest/api/alignment.html) page in [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license).
|
||||
File diff suppressed because one or more lines are too long
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/af.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/af.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const e=n.af=n.af||{};e.dictionary=Object.assign(e.dictionary||{},{"Align center":"Belyn in die middel","Align left":"Belyn links","Align right":"Belyn regs",Justify:"Belyn beide kante","Text alignment":"Teksbelyning","Text alignment toolbar":"Teksbelyning nutsbank"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ar.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ar.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.ar=n.ar||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"محاذاة في المنتصف","Align left":"محاذاة لليسار","Align right":"محاذاة لليمين",Justify:"ضبط","Text alignment":"محاذاة النص","Text alignment toolbar":"شريط أدوات محاذاة النص"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/az.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/az.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.az=n.az||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Mərkəzə düzləndir","Align left":"Soldan düzləndir","Align right":"Sağdan düzləndir",Justify:"Eninə görə","Text alignment":"Mətn düzləndirməsi","Text alignment toolbar":"Mətnin düzləndirmə paneli"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/bg.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/bg.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.bg=n.bg||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Централно подравняване","Align left":"Ляво подравняване","Align right":"Дясно подравняване",Justify:"Разпредели по равно","Text alignment":"Подравняване на текста","Text alignment toolbar":"Лента за подравняване на текст"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/bn.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/bn.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.bn=n.bn||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"কেন্দ্র সারিবদ্ধ করুন","Align left":"বামে সারিবদ্ধ করুন","Align right":"ডানদিকে সারিবদ্ধ করুন",Justify:"জাস্টিফাই","Text alignment":"টেক্সট সারিবদ্ধকরণ","Text alignment toolbar":"টেক্সট শ্রেণীবিন্যাস টুলবার"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/bs.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/bs.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const a=n.bs=n.bs||{};a.dictionary=Object.assign(a.dictionary||{},{"Align center":"Centrirati","Align left":"Lijevo poravnanje","Align right":"Desno poravnanje",Justify:"","Text alignment":"Poravnanje teksta","Text alignment toolbar":"Traka za poravnanje teksta"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ca.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ca.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const e=i.ca=i.ca||{};e.dictionary=Object.assign(e.dictionary||{},{"Align center":"Alineació centre","Align left":"Alineació esquerra","Align right":"Alineació dreta",Justify:"Justificar","Text alignment":"Alineació text","Text alignment toolbar":"Barra d'eines d'alineació de text"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/cs.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/cs.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const t=n.cs=n.cs||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Zarovnat na střed","Align left":"Zarovnat vlevo","Align right":"Zarovnat vpravo",Justify:"Zarovnat do bloku","Text alignment":"Zarovnání textu","Text alignment toolbar":"Panel nástrojů zarovnání textu"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/da.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/da.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(t){const n=t.da=t.da||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Justér center","Align left":"Justér venstre","Align right":"Justér højre",Justify:"Justér","Text alignment":"Tekstjustering","Text alignment toolbar":"Tekstjustering værktøjslinje"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
|
|
@ -1 +0,0 @@
|
|||
!function(t){const i=t["de-ch"]=t["de-ch"]||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Zentriert","Align left":"Linksbündig","Align right":"Rechtsbündig",Justify:"Blocksatz","Text alignment":"Textausrichtung","Text alignment toolbar":"Textausrichtung Werkzeugleiste"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/de.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/de.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const t=n.de=n.de||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Zentriert","Align left":"Linksbündig","Align right":"Rechtsbündig",Justify:"Blocksatz","Text alignment":"Textausrichtung","Text alignment toolbar":"Text-Ausrichtung Toolbar"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/el.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/el.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.el=n.el||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Στοίχιση στο κέντρο","Align left":"Στοίχιση αριστερά","Align right":"Στοίχιση δεξιά",Justify:"Πλήρης στοίχηση","Text alignment":"Στοίχιση κειμένου","Text alignment toolbar":"Γραμμή εργαλείων στοίχισης κειμένου"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
|
|
@ -1 +0,0 @@
|
|||
!function(n){const t=n["en-au"]=n["en-au"]||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Align centre","Align left":"Align left","Align right":"Align right",Justify:"Justify","Text alignment":"Text alignment","Text alignment toolbar":"Text alignment toolbar"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n["en-gb"]=n["en-gb"]||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Align center","Align left":"Align left","Align right":"Align right",Justify:"Justify","Text alignment":"Text alignment","Text alignment toolbar":""})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
|
|
@ -1 +0,0 @@
|
|||
!function(i){const e=i["es-co"]=i["es-co"]||{};e.dictionary=Object.assign(e.dictionary||{},{"Align center":"Centrar","Align left":"Alinear a la izquierda","Align right":"Alinear a la derecha",Justify:"Justificar","Text alignment":"Alineación de texto","Text alignment toolbar":"Herramientas de alineación de texto"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/es.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/es.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(e){const i=e.es=e.es||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Centrar","Align left":"Alinear a la izquierda","Align right":"Alinear a la derecha",Justify:"Justificar","Text alignment":"Alineación del texto","Text alignment toolbar":"Barra de herramientas de alineación del texto"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/et.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/et.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.et=n.et||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Keskjoondus","Align left":"Vasakjoondus","Align right":"Paremjoondus",Justify:"Rööpjoondus","Text alignment":"Teksti joondamine","Text alignment toolbar":"Teksti joonduse tööriistariba"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/fa.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/fa.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.fa=n.fa||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"تراز وسط","Align left":"تراز چپ","Align right":"تراز راست",Justify:"هم تراز کردن","Text alignment":"تراز متن","Text alignment toolbar":"نوار ابزار ترازبندی متن"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/fi.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/fi.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(a){const i=a.fi=a.fi||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Tasaa keskelle","Align left":"Tasaa vasemmalle","Align right":"Tasaa oikealle",Justify:"Tasaa molemmat reunat","Text alignment":"Tekstin tasaus","Text alignment toolbar":"Tekstin suuntauksen työkalupalkki"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/fr.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/fr.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(e){const t=e.fr=e.fr||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Centrer","Align left":"Aligner à gauche","Align right":"Aligner à droite",Justify:"Justifier","Text alignment":"Alignement du texte","Text alignment toolbar":"Barre d'outils d'alignement du texte"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/gl.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/gl.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(t){const e=t.gl=t.gl||{};e.dictionary=Object.assign(e.dictionary||{},{"Align center":"Centrar horizontalmente","Align left":"Aliñar á esquerda","Align right":"Aliñar á dereita",Justify:"Xustificado","Text alignment":"Aliñamento do texto","Text alignment toolbar":"Barra de ferramentas de aliñamento de textos"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/he.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/he.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.he=n.he||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"יישור באמצע","Align left":"יישור לשמאל","Align right":"יישור לימין",Justify:"מרכוז גבולות","Text alignment":"יישור טקסט","Text alignment toolbar":"סרגל כלים יישור טקסט"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/hi.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/hi.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const n=i.hi=i.hi||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Align center","Align left":"Align left","Align right":"Align right",Justify:"Justify","Text alignment":"Text alignment","Text alignment toolbar":"Text alignment toolbar"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/hr.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/hr.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const a=n.hr=n.hr||{};a.dictionary=Object.assign(a.dictionary||{},{"Align center":"Poravnaj po sredini","Align left":"Poravnaj ulijevo","Align right":"Poravnaj udesno",Justify:"Razvuci","Text alignment":"Poravnanje teksta","Text alignment toolbar":"Traka za poravnanje"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/hu.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/hu.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const t=i.hu=i.hu||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Középre igazítás","Align left":"Balra igazítás","Align right":"Jobbra igazítás",Justify:"Sorkizárt","Text alignment":"Szöveg igazítása","Text alignment toolbar":"Szöveg igazítás eszköztár"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/id.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/id.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(a){const t=a.id=a.id||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Rata tengah","Align left":"Rata kiri","Align right":"Rata kanan",Justify:"Rata kanan-kiri","Text alignment":"Perataan teks","Text alignment toolbar":"Alat perataan teks"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/it.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/it.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const n=i.it=i.it||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Allinea al centro","Align left":"Allinea a sinistra","Align right":"Allinea a destra",Justify:"Giustifica","Text alignment":"Allineamento del testo","Text alignment toolbar":"Barra degli strumenti dell'allineamento"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ja.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ja.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.ja=n.ja||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"中央揃え","Align left":"左揃え","Align right":"右揃え",Justify:"両端揃え","Text alignment":"文字揃え","Text alignment toolbar":"テキストの整列"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/jv.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/jv.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const t=n.jv=n.jv||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Rata tengah","Align left":"Rata kiwa","Align right":"Rata tengen",Justify:"Rata kiwa tengen","Text alignment":"Perataan seratan","Text alignment toolbar":""})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/kk.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/kk.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.kk=n.kk||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"Ортадан туралау","Align left":"Солға туралау","Align right":"Оңға туралау",Justify:"","Text alignment":"Мәтінді туралау","Text alignment toolbar":"Мәтінді туралау құралдар тақтасы"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/km.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/km.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.km=n.km||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"តម្រឹមកណ្ដាល","Align left":"តម្រឹមឆ្វេង","Align right":"តម្រឹមស្ដាំ",Justify:"តម្រឹមសងខាង","Text alignment":"ការតម្រឹមអក្សរ","Text alignment toolbar":"របារឧបករណ៍តម្រឹមអក្សរ"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ko.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ko.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.ko=n.ko||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"가운데 정렬","Align left":"왼쪽 정렬","Align right":"오른쪽 정렬",Justify:"양쪽 정렬","Text alignment":"텍스트 정렬","Text alignment toolbar":"텍스트 정렬 툴바"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ku.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ku.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.ku=n.ku||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"بەهێڵکردنی ناورەڕاست","Align left":"بەهێڵکردنی چەپ","Align right":"بەهێڵکردنی ڕاست",Justify:"هاوستوونی","Text alignment":"ڕیززکردنی تێکست","Text alignment toolbar":"تووڵامرازی ڕیززکردنی تێکست"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/lt.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/lt.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const t=i.lt=i.lt||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Centruoti","Align left":"Lygiuoti į kairę","Align right":"Lygiuoti į dešinę",Justify:"Lygiuoti per visą plotį","Text alignment":"Teksto lygiavimas","Text alignment toolbar":"Teksto lygiavimo įrankių juosta"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/lv.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/lv.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const n=i.lv=i.lv||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Centrēt","Align left":"Pa kreisi","Align right":"Pa labi",Justify:"Izlīdzināt abas malas","Text alignment":"Teksta izlīdzināšana","Text alignment toolbar":"Teksta līdzināšanas rīkjosla"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ms.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ms.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(a){const n=a.ms=a.ms||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Jajarkan tengah","Align left":"Jajarkan kiri","Align right":"Jajarkan kiri",Justify:"Imbang","Text alignment":"Jajaran teks","Text alignment toolbar":"Bar alat capaian jajaran teks"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/nb.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/nb.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(t){const n=t.nb=t.nb||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Midstill","Align left":"Venstrejuster","Align right":"Høyrejuster",Justify:"Blokkjuster","Text alignment":"Tekstjustering","Text alignment toolbar":""})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ne.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/ne.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(n){const i=n.ne=n.ne||{};i.dictionary=Object.assign(i.dictionary||{},{"Align center":"केन्द्र पङ्क्तिबद्ध गर्नुहोस्","Align left":"बायाँ पङ्क्तिबद्ध गर्नुहोस्","Align right":"दायाँ पङ्क्तिबद्ध गर्नुहोस्",Justify:"जस्टिफाइ गर्नुहोस्","Text alignment":"पाठ संरेखण","Text alignment toolbar":""})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/nl.js
generated
vendored
1
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-alignment/build/translations/nl.js
generated
vendored
|
|
@ -1 +0,0 @@
|
|||
!function(i){const n=i.nl=i.nl||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Midden uitlijnen","Align left":"Links uitlijnen","Align right":"Rechts uitlijnen",Justify:"Volledig uitlijnen","Text alignment":"Tekst uitlijning","Text alignment toolbar":"Tekst uitlijning werkbalk"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue