This commit is contained in:
hanif salafi 2025-01-21 22:42:50 +07:00
commit 498ab94100
10345 changed files with 5034 additions and 1028232 deletions

View File

@ -46,7 +46,6 @@ export type CalendarEvent = {
};
};
const INITIAL_YEAR = dayjs().format("YYYY");
const INITIAL_MONTH = dayjs().format("M");
@ -86,7 +85,7 @@ interface MonthCardProps {
interface ListItemProps {
item: any;
text: string
text: string;
createdBy: string;
bgColor: string;
}
@ -120,7 +119,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const [selectedYear, setSelectedYear] = useState(new Date());
const [selectedMonth, setSelectedMonth] = useState(
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)),
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
);
const [dragEvents] = useState([
@ -142,7 +141,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const getCalendarEvents = async () => {
console.log("View : ", activeView);
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, selectedMonth.format("M") || INITIAL_MONTH, "");
const res = await getAgendaSettingsList(
selectedMonth?.format("YYYY") || INITIAL_YEAR,
selectedMonth.format("M") || INITIAL_MONTH,
""
);
console.log("View : API Response:", res);
if (res?.error) {
@ -175,13 +178,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
};
const getYearlyEvents = async () => {
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, '', '');
const res = await getAgendaSettingsList(
selectedMonth?.format("YYYY") || INITIAL_YEAR,
"",
""
);
if (res?.error) {
error(res.message);
return false;
}
setYearlyData(res?.data?.data);
}
};
useEffect(() => {
setSelectedCategory(categories?.map((c) => c.value));
@ -218,8 +225,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
if (response?.data && Array.isArray(response?.data)) {
// Transform API data to match CalendarEvent type
const eventsFromAPI: CalendarEvent[] = response?.data?.map(
(item) => ({
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
id: item.id.toString(),
title: item.title,
createBy: "Mabes Polri - Approver",
@ -231,8 +237,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
calendar: item.agendaType,
description: item.description,
},
})
);
}));
setApiEvents(eventsFromAPI);
} else {
@ -321,7 +326,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const renderEventContent = (eventInfo: any) => {
const { title } = eventInfo.event;
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps
const { createdByName } = eventInfo.event.extendedProps;
return (
<>
@ -340,6 +345,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
return "bg-blue-400 border-none";
} else if (arg.event.extendedProps.calendar === "polres") {
return "bg-slate-400 border-none";
} else if (arg.event.extendedProps.calendar === "satker") {
return "bg-orange-500 border-none";
} else if (arg.event.extendedProps.calendar === "international") {
return "bg-green-400 border-none";
} else {
@ -362,26 +369,26 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
};
const months: Array<{ id: keyof YearlyData; label: string }> = [
{ id: 'january', label: 'Januari' },
{ id: 'february', label: 'Februari' },
{ id: 'march', label: 'Maret' },
{ id: 'april', label: 'April' },
{ id: 'may', label: 'Mei' },
{ id: 'june', label: 'Juni' },
{ id: 'july', label: 'Juli' },
{ id: 'august', label: 'Agustus' },
{ id: 'september', label: 'September' },
{ id: 'october', label: 'Oktober' },
{ id: 'november', label: 'November' },
{ id: 'december', label: 'Desember' }
{ id: "january", label: "Januari" },
{ id: "february", label: "Februari" },
{ id: "march", label: "Maret" },
{ id: "april", label: "April" },
{ id: "may", label: "Mei" },
{ id: "june", label: "Juni" },
{ id: "july", label: "Juli" },
{ id: "august", label: "Agustus" },
{ id: "september", label: "September" },
{ id: "october", label: "Oktober" },
{ id: "november", label: "November" },
{ id: "december", label: "Desember" },
];
const getEventColor = (type: Event['type']): string => {
const colors: Record<Event['type'], string> = {
mabes: 'bg-yellow-500',
polda: 'bg-blue-400',
polres: 'bg-slate-400',
international: 'bg-green-400'
const getEventColor = (type: Event["type"]): string => {
const colors: Record<Event["type"], string> = {
mabes: "bg-yellow-500",
polda: "bg-blue-400",
polres: "bg-slate-400",
international: "bg-green-400",
};
return colors[type];
};
@ -397,12 +404,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
allDay: true,
extendedProps: {
calendar: item.agendaType,
description: item.description
}
description: item.description,
},
};
const finalEvent: any = {
event: formattedEvent
}
event: formattedEvent,
};
console.log("Event click custom : ", finalEvent);
@ -410,14 +417,20 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
setSheetOpen(true);
setApiEvents(finalEvent);
wait().then(() => (document.body.style.pointerEvents = "auto"));
}
};
const ListItem: React.FC<ListItemProps> = ({ item, text, createdBy, bgColor }) => (
<div className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`} onClick={() => handleClickListItem(item)}>
const ListItem: React.FC<ListItemProps> = ({
item,
text,
createdBy,
bgColor,
}) => (
<div
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
onClick={() => handleClickListItem(item)}
>
<p className="ml-1">{text}</p>
<p className="ml-1 text-xs text-start mt-2">
Created By: {createdBy}
</p>
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
</div>
);
@ -462,7 +475,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
>
<Card>
<CardHeader className="bg-default p-2 rounded-t-lg">
<CardTitle className="text-default-foreground text-base py-0">{label}</CardTitle>
<CardTitle className="text-default-foreground text-base py-0">
{label}
</CardTitle>
</CardHeader>
<CardContent className="p-2 text-sm">
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
@ -494,16 +509,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
<CardContent className="p-0">
<CardHeader className="border-none mb-2 pt-5">
{roleId == 11 || roleId == 12 ?
{roleId == 11 || roleId == 12 ? (
<Button
onClick={handleDateClick}
className="dark:bg-background dark:text-foreground"
>
<Plus className="w-4 h-4 me-1" />
{"Tambahkan Agenda baru"}
</Button> :
</Button>
) : (
""
}
)}
</CardHeader>
<div className="px-3">
@ -597,35 +613,53 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
handleDateChange(info.view.currentStart, info.view.currentEnd);
handleViewChange(info.view.type);
}}
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
viewClassNames={
activeView === "listYear" ? "hide-calendar-grid" : ""
}
/>
{activeView === "listYear" && (
<div className="custom-ui">
<div className="flex gap-1 mt-1">
{months.slice(0, 3).map(month => (
<MonthCard key={month.id} monthId={month.id} label={month.label} />
{months.slice(0, 3).map((month) => (
<MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))}
</div>
{/* Second Row */}
<div className="flex gap-1 mt-1">
{months.slice(3, 6).map(month => (
<MonthCard key={month.id} monthId={month.id} label={month.label} />
{months.slice(3, 6).map((month) => (
<MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))}
</div>
{/* Third Row */}
<div className="flex gap-1 mt-1">
{months.slice(6, 9).map(month => (
<MonthCard key={month.id} monthId={month.id} label={month.label} />
{months.slice(6, 9).map((month) => (
<MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))}
</div>
{/* Fourth Row */}
<div className="flex gap-1 mt-1">
{months.slice(9, 12).map(month => (
<MonthCard key={month.id} monthId={month.id} label={month.label} />
{months.slice(9, 12).map((month) => (
<MonthCard
key={month.id}
monthId={month.id}
label={month.label}
/>
))}
</div>
</div>

View File

@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useForm, Controller } from "react-hook-form";
import { cn } from "@/lib/utils";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import { format } from "date-fns";
import {
Popover,
@ -21,7 +21,13 @@ import {
import { Calendar } from "@/components/ui/calendar";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Loader2, CalendarIcon, ChevronUp, ChevronDown } from "lucide-react";
import {
Loader2,
CalendarIcon,
ChevronUp,
ChevronDown,
Music,
} from "lucide-react";
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
import { CalendarCategory } from "./data";
import {
@ -36,9 +42,12 @@ import { error, loading, success } from "@/lib/swal";
import Cookies from "js-cookie";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation";
import { postSchedule } from "@/service/schedule/schedule";
import { getAgendaSettingsById, saveAgendaSettings } from "@/service/agenda-setting/agenda-setting";
import {
deleteAgendaSettings,
getAgendaSettingsById,
saveAgendaSettings,
} from "@/service/agenda-setting/agenda-setting";
import { Checkbox } from "@/components/ui/checkbox";
import { getUserLevelForAssignments } from "@/service/task";
import { AudioRecorder } from "react-audio-voice-recorder";
@ -47,10 +56,18 @@ import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth";
import { Icon } from "@iconify/react";
import Image from "next/image";
import { UnitMapping } from "./unit-mapping";
import { useToast } from "@/components/ui/use-toast";
import { usePathname, useRouter } from "next/navigation";
import { Card } from "@/components/ui/card";
import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js";
const schema = z.object({
title: z.string().min(3, { message: "Required" }),
description: z.string().min(3, { message: "Required" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
});
interface FileWithPreview extends File {
@ -75,15 +92,17 @@ const EventModal = ({
event: any;
selectedDate: any;
}) => {
const roleId = Number(getCookiesDecrypt("urie")) || 0;
const [detail, setDetail] = useState<any>();
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const [isPending, startTransition] = React.useTransition();
const [agendaType, setAgendaType] = React.useState<any>(categories[0].value);
const [listDest, setListDest] = useState([]);
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
const MySwal = withReactContent(Swal);
const router = useRouter();
const pathname = usePathname();
const [isLoading, setIsLoading] = useState(false);
const [checkedLevels, setCheckedLevels] = useState(new Set());
const [expandedPolda, setExpandedPolda] = useState([{}]);
@ -95,20 +114,45 @@ const EventModal = ({
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>([]);
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>([]);
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>([]);
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>([]);
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
[]
);
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
[]
);
const { toast } = useToast();
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [unitData, setUnitData] = useState<string[]>([]);
const [satkerData, setSatkerData] = useState<string[]>([]);
let progressInfo: any = [];
let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const [wilayahPublish, setWilayahPublish] = React.useState({
semua: false,
nasional: false,
polda: false,
polres: false,
satker: false,
international: false,
});
const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType
const [selectedPolda, setSelectedPolda] = React.useState([]); // Untuk data Polda
const [selectedSatker, setSelectedSatker] = React.useState([]);
const [selectedPolres, setSelectedPolres] = React.useState([]);
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false);
const {
register,
@ -128,54 +172,80 @@ const EventModal = ({
const detail = res?.data?.data;
setDetailData(detail);
const description = res?.data?.data?.description;
console.log("description", res?.data?.data?.description);
// Set nilai awal description ke form control
if (description) {
setValue("description", description);
}
const attachments = detail?.attachments;
setImageUploadedFiles(attachments?.filter((file: any) => file.fileTypeId == 1));
setVideoUploadedFiles(attachments?.filter((file: any) => file.fileTypeId == 2));
setTextUploadedFiles(attachments?.filter((file: any) => file.fileTypeId == 3));
setAudioUploadedFiles(attachments?.filter((file: any) => file.fileTypeId == 4));
setImageUploadedFiles(
attachments?.filter((file: any) => file.fileTypeId == 1)
);
setVideoUploadedFiles(
attachments?.filter((file: any) => file.fileTypeId == 2)
);
setTextUploadedFiles(
attachments?.filter((file: any) => file.fileTypeId == 3)
);
setAudioUploadedFiles(
attachments?.filter((file: any) => file.fileTypeId == 4)
);
const agendaType = detail?.agendaType;
setWilayahPublish({
semua: agendaType === "all",
nasional: agendaType === "mabes",
polda: agendaType === "polda",
polres: agendaType === "polres",
satker: agendaType === "satker",
international: agendaType === "international",
});
}
fetchDetailData();
}, [event]);
}, [event, setValue]);
useEffect(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
try {
const response = await getUserLevelForAssignments();
const levelList = response?.data?.data.list;
let listFiltered = [];
if (agendaType == "polda") {
listFiltered = levelList.filter(
(level: any) => level.name != "SATKER POLRI"
);
} else if (agendaType == "polres") {
listFiltered = levelList.filter(
(level: any) => level.name != "SATKER POLRI"
);
} else if (agendaType == "satker") {
listFiltered = levelList.filter(
(level: any) => level.name == "SATKER POLRI"
);
}
setListDest(listFiltered);
const initialExpandedState = listFiltered.reduce(
(acc: any, polda: any) => {
acc[polda.id] = false;
return acc;
},
{}
);
setExpandedPolda(initialExpandedState);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
// useEffect(() => {
// async function fetchPoldaPolres() {
// setIsLoading(true);
// try {
// const response = await getUserLevelForAssignments();
// const levelList = response?.data?.data.list;
// let listFiltered = [];
// if (agendaType == "polda") {
// listFiltered = levelList.filter(
// (level: any) => level.name != "SATKER POLRI"
// );
// } else if (agendaType == "polres") {
// listFiltered = levelList.filter(
// (level: any) => level.name != "SATKER POLRI"
// );
// } else if (agendaType == "satker") {
// listFiltered = levelList.filter(
// (level: any) => level.name == "SATKER POLRI"
// );
// }
// setListDest(listFiltered);
// const initialExpandedState = listFiltered.reduce(
// (acc: any, polda: any) => {
// acc[polda.id] = false;
// return acc;
// },
// {}
// );
// setExpandedPolda(initialExpandedState);
// } catch (error) {
// console.error("Error fetching Polda/Polres data:", error);
// } finally {
// setIsLoading(false);
// }
// }
fetchPoldaPolres();
}, [agendaType]);
// fetchPoldaPolres();
// }, [agendaType]);
const handleCheckboxChange = (levelId: number) => {
setCheckedLevels((prev) => {
@ -189,14 +259,55 @@ const EventModal = ({
});
};
const toggleWilayah = (key: string) => {
setWilayahPublish((prev: any) => {
const newState = { ...prev, [key]: !prev[key] };
// Handle "semua" logic to check all options
if (key === "semua" && newState.semua) {
setAgendaType("all");
return {
semua: true,
nasional: true,
polda: true,
polres: true,
satker: true,
international: true,
};
}
// Uncheck "semua" if any other option is selected
if (key !== "semua") {
newState.semua = false;
}
// Set agendaType based on the selected checkbox
if (newState.nasional) setAgendaType("mabes");
else if (newState.polda) setAgendaType("polda");
else if (newState.polres) setAgendaType("polres");
else if (newState.satker) setAgendaType("satker");
else if (newState.international) setAgendaType("international");
else setAgendaType(""); // Reset if no checkbox is selected
return newState;
});
};
const save = async (data: any) => {
// const formData = new FormData();
// formData.append("voiceNote", audioFile);
const publishTo = [];
if (wilayahPublish.semua) publishTo.push("all");
if (wilayahPublish.nasional) publishTo.push("mabes");
if (wilayahPublish.polda) publishTo.push(...selectedPolda);
if (wilayahPublish.polres) publishTo.push(...selectedPolres);
if (wilayahPublish.satker) publishTo.push(...selectedSatker);
if (wilayahPublish.international) publishTo.push("international");
const reqData = {
id: detailData?.id,
title: data.title,
description: data.description,
agendaType: agendaType,
agendaType, // Include agendaType in request
publishTo,
startDate: format(startDate, "yyyy-MM-dd"),
endDate: format(endDate, "yyyy-MM-dd"),
};
@ -212,21 +323,21 @@ const EventModal = ({
const id = response?.data?.data.id;
loading();
if (imageFiles?.length == 0) {
if (imageFiles?.length === 0) {
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
if (videoFiles?.length == 0) {
if (videoFiles?.length === 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
if (textFiles?.length === 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
@ -236,23 +347,29 @@ const EventModal = ({
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
);
});
// Optional: Use Swal for success feedback
// MySwal.fire({
// title: "Sukses",
// text: "Data berhasil disimpan.",
// icon: "success",
// confirmButtonColor: "#3085d6",
// confirmButtonText: "OK",
// }).then(() => {
// router.push("en/contributor/agenda-setting");
// });
};
const onSubmit = (data: any) => {
if (
(wilayahPublish.polda && selectedPolda.length === 0) ||
(wilayahPublish.satker && selectedSatker.length === 0) ||
(wilayahPublish.polres && selectedPolres.length === 0)
) {
toast({
title: "Pilih ID untuk Polda/Satker",
variant: "destructive",
});
return;
}
save(data);
};
@ -324,9 +441,15 @@ const EventModal = ({
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 = () => {
@ -451,8 +574,60 @@ const EventModal = ({
);
};
const handleRemoveFile = (id: number) => {
const handleRemoveFile = (id: number) => {};
async function doDelete(id: any) {
loading();
const resDelete = await deleteAgendaSettings(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
close();
successSubmitDelete("/in/contributor/agenda-setting");
window.location.reload();
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
function successSubmitDelete(redirect: string) {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
if (redirect === window.location.pathname) {
fetch(redirect, { method: "GET", cache: "reload" }).then(() => {
console.log("Data diperbarui.");
});
} else {
window.location.href = redirect;
}
});
}
const onReady = (ws: any) => {
setWavesurfer(ws);
setIsPlaying(false);
};
const onPlayPause = () => {
wavesurfer && wavesurfer.playPause();
};
return (
@ -476,6 +651,7 @@ const EventModal = ({
{event?.title}
</DialogTitle>
</DialogHeader>
<div className="mt-6 h-full ">
<form className="h-full" onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-4 pb-5 ">
@ -575,128 +751,98 @@ const EventModal = ({
</Popover>
</div>
<div className="space-y-1.5">
<Label htmlFor="agendaType">Jenis Agenda </Label>
<Controller
name="agendaType"
control={control}
render={({ field }) => (
<Select
value={agendaType}
onValueChange={(data) => setAgendaType(data)}
>
<SelectTrigger>
<SelectValue placeholder="Label" />
</SelectTrigger>
<SelectContent>
{categories.map((category: CalendarCategory) => (
<SelectItem
value={category.value}
key={category.value}
>
{category.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</div>
{(agendaType === "polda" ||
agendaType === "polres" ||
agendaType === "satker") && (
<Label htmlFor="wilayahPublish">Jenis Agenda</Label>
<div className="flex flex-wrap items-center gap-2">
<div>
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Pilih {agendaType}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>
Daftar Wilayah Polda dan Polres
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listDest.map((polda: any) => (
<div key={polda.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
checked={checkedLevels.has(polda.id)}
onCheckedChange={() =>
handleCheckboxChange(polda.id)
}
className="mr-3"
id="semua"
checked={wilayahPublish.semua}
onCheckedChange={() => toggleWilayah("semua")}
/>
{polda.name}
<button
onClick={() => toggleExpand(polda.id)}
className="ml-2 focus:outline-none"
>
{expandedPolda[polda.id] ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</button>
</Label>
{(agendaType == "polres" ||
agendaType == "satker") &&
expandedPolda[polda.id] && (
<div className="ml-6 mt-2">
<Label className="block">
<label htmlFor="semua" className="ml-2">
Semua
</label>
</div>
<div>
<Checkbox
checked={polda?.subDestination?.every(
(polres: any) =>
checkedLevels.has(polres.id)
)}
onCheckedChange={(isChecked) => {
const updatedLevels = new Set(
checkedLevels
);
polda?.subDestination?.forEach(
(polres: any) => {
if (isChecked) {
updatedLevels.add(polres.id);
} else {
updatedLevels.delete(polres.id);
}
}
);
setCheckedLevels(updatedLevels);
}}
className="mr-2"
id="nasional"
checked={wilayahPublish.nasional}
onCheckedChange={() => toggleWilayah("nasional")}
/>
Pilih Semua Polres
</Label>
{polda?.subDestination?.map(
(polres: any) => (
<Label
key={polres.id}
className="block mt-1"
>
<label htmlFor="nasional" className="ml-2">
Nasional
</label>
</div>
<div>
<Checkbox
checked={checkedLevels.has(
polres.id
)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
className="mr-2"
id="polda"
checked={wilayahPublish.polda}
onCheckedChange={() => toggleWilayah("polda")}
/>
<label htmlFor="polda" className="mx-2">
Polda
</label>
{wilayahPublish.polda && (
<UnitMapping
unit="Polda"
isDetail={false}
sendDataToParent={(data: any) =>
setSelectedPolda(data)
}
/>
{polres.name}
</Label>
)
)}
</div>
<div>
<Checkbox
id="polres"
checked={wilayahPublish.polres}
onCheckedChange={() => toggleWilayah("polres")}
/>
<label htmlFor="polres" className="ml-2">
Polres
</label>
{wilayahPublish.polres && (
<UnitMapping
isDetail={false}
unit="Polres"
sendDataToParent={(data: any) =>
setSelectedPolres(data)
}
/>
)}
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
<div>
<Checkbox
id="satker"
checked={wilayahPublish.satker}
onCheckedChange={() => toggleWilayah("satker")}
/>
<label htmlFor="satker" className="mx-2">
Satker
</label>
{wilayahPublish.satker && (
<UnitMapping
isDetail={false}
unit="Satker"
sendDataToParent={(data: any) =>
setSelectedSatker(data)
}
/>
)}
</div>
<div>
<Checkbox
id="international"
checked={wilayahPublish.international}
onCheckedChange={() => toggleWilayah("international")}
/>
<label htmlFor="international" className="ml-2">
Internasional
</label>
</div>
</div>
</div>
<div className="space-y-1.5">
<Label htmlFor="description">Isi Agenda Setting</Label>
<Controller
@ -704,7 +850,7 @@ const EventModal = ({
name="description"
render={({ field }) => (
<Textarea
defaultValue={field.value}
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
@ -718,34 +864,12 @@ const EventModal = ({
</div>
)}
</div>
<div className="space-y-1.5">
<Label htmlFor="attachments">Lampiran</Label>
<div className="space-y-3">
<div>
<Label>Video</Label>
{videoUploadedFiles?.map((file: any, index: number) => (
<div
key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file.url)}</div>
<div>
<div className=" text-sm text-card-foreground">{file.fileName}</div>
</div>
</div>
<Button
size="icon"
color="destructive"
variant="outline"
className=" border-none rounded-full"
onClick={() => handleRemoveFile(file)}
>
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
))}
<FileUploader
accept={{
"mp4/*": [],
@ -753,20 +877,28 @@ const EventModal = ({
}}
maxSize={100}
label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setImageFiles(files)}
onDrop={(files) => setVideoFiles(files)}
/>
</div>
{videoUploadedFiles?.map((file: any, index: number) => (
<div>
<Label>Foto</Label>
{imageUploadedFiles?.map((file: any, index: number) => (
<video
className="object-fill h-full w-full rounded-md"
src={file.url}
controls
title={`Video ${file.id}`} // Mengganti alt dengan title
/>
<div
key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file.url)}</div>
<div className="file-preview">
{renderFilePreview(file.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">{file.fileName}</div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</div>
</div>
</div>
@ -780,7 +912,11 @@ const EventModal = ({
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
</div>
))}
</div>
<div>
<Label>Foto</Label>
<FileUploader
accept={{
"image/*": [],
@ -789,18 +925,27 @@ const EventModal = ({
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/>
</div>
{imageUploadedFiles?.map((file: any, index: number) => (
<div>
<Label>Teks</Label>
{textUploadedFiles?.map((file: any, index: number) => (
<Card className="mt-2">
<img
src={file.url}
alt="Thumbnail Gambar Utama"
className="w-full h-auto rounded-md"
/>
</Card>
<div
key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file.url)}</div>
<div className="file-preview">
{renderFilePreview(file.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">{file.fileName}</div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</div>
</div>
</div>
@ -814,7 +959,12 @@ const EventModal = ({
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
</div>
))}
</div>
<div>
<Label>Teks</Label>
<FileUploader
accept={{
"pdf/*": [],
@ -823,18 +973,27 @@ const EventModal = ({
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)}
/>
</div>
{textUploadedFiles?.map((file: any, index: number) => (
<div>
<Label>Voice Note</Label>
{audioUploadedFiles?.map((file: any, index: number) => (
<iframe
className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
file.url
)}`}
title={file.fileName || "Document"}
/>
<div
key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file.url)}</div>
<div className="file-preview">
{renderFilePreview(file.url)}
</div>
<div>
<div className=" text-sm text-card-foreground">{file.fileName}</div>
<div className=" text-sm text-card-foreground">
{file.fileName}
</div>
</div>
</div>
@ -848,7 +1007,11 @@ const EventModal = ({
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
</div>
))}
</div>
<div>
<Label>Audio</Label>
<AudioRecorder
onRecordingComplete={addAudioElement}
audioTrackConstraints={{
@ -865,13 +1028,79 @@ const EventModal = ({
}}
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={file.id}>
<WavesurferPlayer
height={500}
waveColor="red"
url={file.url}
onReady={onReady}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
</div>
<Button
size="sm"
type="button"
onClick={onPlayPause}
disabled={isPlaying}
className={`flex items-center gap-2 ${
isPlaying
? "bg-gray-300 cursor-not-allowed"
: "bg-primary text-white"
} p-2 rounded`}
>
{isPlaying ? "Pause" : "Play"}
<Icon
icon={
isPlaying
? "carbon:pause-outline"
: "famicons:play-sharp"
}
className="h-5 w-5"
/>
</Button>
<div
key={index}
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">
<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>
<div className="flex items-center mr-1">
{" "}
<Music /> <p>Voice Note</p>
</div>
<Button
type="button"
onClick={handleDeleteAudio}
@ -890,7 +1119,18 @@ const EventModal = ({
</div>
</div>
<div className="flex flex-wrap gap-2 mt-10">
<Button type="submit" disabled={isPending} className="flex-1">
<Button
style={
roleId > 11
? {
display: "none",
}
: {}
}
type="submit"
disabled={isPending}
className="flex-1"
>
{isPending ? (
<>
<Loader2 className="me-2 h-4 w-4 animate-spin" />
@ -902,6 +1142,20 @@ const EventModal = ({
"Simpan Agenda Setting"
)}
</Button>
<Button
style={
roleId > 11
? {
display: "none",
}
: {}
}
className="flex-1 bg-red-500 text-white"
type="button"
onClick={() => handleDelete(event?.event?.id)}
>
Delete
</Button>
{event?.length > 1 && (
<Button
type="button"

View File

@ -0,0 +1,204 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useEffect, useState } from "react";
import { getUserLevelForAssignments } from "@/service/task";
const FormSchema = z.object({
items: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
});
interface UnitType {
id: number;
name: string;
subDestination: { id: number; name: string }[] | null;
}
export function UnitMapping(props: {
unit: "Polda" | "Satker" | "Polres";
sendDataToParent: (data: string[]) => void;
isDetail: boolean;
initData?: string[];
}) {
const { unit, sendDataToParent, isDetail } = props;
const [unitList, setUnitList] = useState<UnitType[]>([]);
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
[]
);
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
[]
);
const [isOpen, setIsOpen] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
items: props.initData ? props.initData : [],
},
});
useEffect(() => {
async function initState() {
const response = await getUserLevelForAssignments();
setupUnit(response?.data?.data.list);
console.log("list", response?.data?.data.list);
}
initState();
}, []);
const unitType = form.watch("items");
const isAllUnitChecked = unitList.every((item) =>
unitType?.includes(String(item.id))
);
const isAllSatkerChecked = satkerList.every((item) =>
unitType?.includes(String(item.id))
);
const isAllPolresChecked = polresList.every((item) =>
unitType?.includes(String(item.id))
);
const setupUnit = (data: UnitType[]) => {
const temp = data.filter((a) => a.name.includes("POLDA"));
const temp2 = data.filter((a) => a.name.includes("SATKER"));
const temp3 = temp.flatMap((item) => item.subDestination || []);
setUnitList(temp);
setSatkerList(temp2[0]?.subDestination || []);
setPolresList(temp3);
};
useEffect(() => {
sendDataToParent(form.getValues("items"));
}, [unitType]);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<a
onClick={() => setIsOpen(true)}
className="text-primary cursor-pointer text-xs mr-3"
>
Pilih {unit}
</a>
</DialogTrigger>
<DialogContent size="md" className="h-[500px] overflow-y-auto">
<DialogHeader>
<DialogTitle>{unit}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Checkbox
id={`all-${unit}`}
checked={
unit === "Polda"
? isAllUnitChecked
: unit === "Satker"
? isAllSatkerChecked
: isAllPolresChecked
}
disabled={isDetail}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"items",
unit === "Polda"
? unitList.map((item) => String(item.id))
: unit === "Satker"
? satkerList.map((item) => String(item.id))
: polresList.map((item) => String(item.id))
);
} else {
form.setValue("items", []);
}
}}
/>
<label htmlFor="all" className="text-sm text-black uppercase">
SEMUA {unit}
</label>
</div>
<FormField
control={form.control}
name="items"
render={() => (
<FormItem
className={`grid grid-cols-${
unit === "Polda" ? "2" : unit === "Satker" ? "3" : "4"
}`}
>
{(unit === "Polda"
? unitList
: unit === "Satker"
? satkerList
: polresList
)?.map((item: any) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => {
return (
<FormItem
key={String(item.id)}
className="flex flex-row items-center space-x-3 space-y-0"
>
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(String(item.id))}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
String(item.id),
])
: field.onChange(
field.value?.filter(
(value) => value !== String(item.id)
)
);
}}
/>
</FormControl>
<p className="text-sm text-black">{item.name}</p>
</FormItem>
);
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View File

@ -12,7 +12,11 @@ import {
import { Button } from "@/components/ui/button";
import { 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>

View File

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

View File

@ -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,18 +239,140 @@ const TableAudio = () => {
/>
</InputGroup>
</div>
<div className="flex-none">
<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("status")?.getFilterValue() as string) ?? ""
(table
.getColumn("statusName")
?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
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">
<TableHeader>

View File

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

View File

@ -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">
<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("status")?.getFilterValue() as string) ?? ""
(table
.getColumn("statusName")
?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
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>

View File

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

View File

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

View File

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

View File

@ -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,18 +238,140 @@ const TableTeks = () => {
/>
</InputGroup>
</div>
<div className="flex-none">
<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("status")?.getFilterValue() as string) ?? ""
(table
.getColumn("statusName")
?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
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">
<TableHeader>

View File

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

View File

@ -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,18 +238,141 @@ const TableImage = () => {
/>
</InputGroup>
</div>
<div className="flex-none">
<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("status")?.getFilterValue() as string) ?? ""
(table
.getColumn("statusName")
?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
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">
<TableHeader>
@ -239,4 +424,4 @@ const TableImage = () => {
);
};
export default TableImage;
export default TableVideo;

View File

@ -13,6 +13,13 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { 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>

View File

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

View File

@ -0,0 +1,72 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Rows, Search, UploadIcon } from "lucide-react";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Link } from "@/components/navigation";
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { locale } from "dayjs";
import { useEffect, useState } from "react";
import { getListContent } from "@/service/landing/landing";
import ContestTable from "../../../../contest/components/contest-table";
import AudioSliderPage from "../../audio/audio";
import TeksSliderPage from "../../document/teks";
import ImageSliderPage from "../../image/image";
import VideoSliderPage from "../../video/audio-visual";
const AudioAllPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="my-3">
<Tabs defaultValue="giat-routine" className="w-full">
<Card className="py-3 px-2 my-4 h-20 flex items-center">
<p className="text-lg font-semibold ml-2">Konten Audio</p>
</Card>
<TabsContent value="giat-routine">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<div className="flex justify-between items-center py-4 px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
/>
</InputGroup>
</div>
</div>
<div className="ml-5 pb-3">
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Audio</Label>
</div>
<div className="px-5 my-5">
<AudioSliderPage />
</div>
</div>
</Card>
</div>
</div>
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default AudioAllPage;

View File

@ -7,6 +7,7 @@ import {
CarouselNext,
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

View File

@ -0,0 +1,119 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Rows, Search, UploadIcon } from "lucide-react";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Link } from "@/components/navigation";
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { locale } from "dayjs";
import { useEffect, useState } from "react";
import { getListContent } from "@/service/landing/landing";
import ContestTable from "../../../../contest/components/contest-table";
import AudioSliderPage from "../../audio/audio";
import TeksSliderPage from "../../document/teks";
import ImageSliderPage from "../../image/image";
import VideoSliderPage from "../../video/audio-visual";
const DocumentAllPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="my-3">
<Tabs defaultValue="giat-routine" className="w-full">
<Card className="py-3 px-2 my-4 h-20 flex items-center">
<p className="text-lg font-semibold ml-2">Konten Teks</p>
</Card>
<TabsContent value="giat-routine">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<div className="flex justify-between items-center py-4 px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
/>
</InputGroup>
</div>
</div>
<div className="ml-5 pb-3">
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Teks</Label>
</div>
<div className="px-5 my-5">
<TeksSliderPage />
</div>
</div>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="giat-penugasan">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<div className="flex justify-between items-center py-4 px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
/>
</InputGroup>
</div>
</div>
<div className="ml-5 pb-3">
<Label>Audio Visual</Label>
<div className="px-5 my-5">
<VideoSliderPage />
</div>
<Label>Audio</Label>
<div className="px-5 my-5">
<AudioSliderPage />
</div>
<Label>Foto</Label>
<div className="px-5 my-5">
<ImageSliderPage />
</div>
<Label>Teks</Label>
<div className="px-5 my-5">
<TeksSliderPage />
</div>
</div>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="contest">
<Card>
<div className="py-3">
<ContestTable />
</div>
</Card>
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default DocumentAllPage;

View File

@ -0,0 +1,72 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Rows, Search, UploadIcon } from "lucide-react";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Link } from "@/components/navigation";
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { locale } from "dayjs";
import { useEffect, useState } from "react";
import { getListContent } from "@/service/landing/landing";
import ContestTable from "../../../../contest/components/contest-table";
import AudioSliderPage from "../../audio/audio";
import TeksSliderPage from "../../document/teks";
import ImageSliderPage from "../../image/image";
import VideoSliderPage from "../../video/audio-visual";
const ImageAllPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="my-3">
<Tabs defaultValue="giat-routine" className="w-full">
<Card className="py-3 px-2 my-4 h-20 flex items-center">
<p className="text-lg font-semibold ml-2">Konten Image</p>
</Card>
<TabsContent value="giat-routine">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<div className="flex justify-between items-center py-4 px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
/>
</InputGroup>
</div>
</div>
<div className="ml-5 pb-3">
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Image</Label>
</div>
<div className="px-5 my-5">
<ImageSliderPage />
</div>
</div>
</Card>
</div>
</div>
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default ImageAllPage;

View File

@ -0,0 +1,119 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Rows, Search, UploadIcon } from "lucide-react";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Link } from "@/components/navigation";
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { locale } from "dayjs";
import { useEffect, useState } from "react";
import { getListContent } from "@/service/landing/landing";
import ContestTable from "../../../../contest/components/contest-table";
import AudioSliderPage from "../../audio/audio";
import TeksSliderPage from "../../document/teks";
import ImageSliderPage from "../../image/image";
import VideoSliderPage from "../audio-visual";
const VideoAllPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="my-3">
<Tabs defaultValue="giat-routine" className="w-full">
<Card className="py-3 px-2 my-4 h-20 flex items-center">
<p className="text-lg font-semibold ml-2">Konten Video</p>
</Card>
<TabsContent value="giat-routine">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<div className="flex justify-between items-center py-4 px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
/>
</InputGroup>
</div>
</div>
<div className="ml-5 pb-3">
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Audio Visual</Label>
</div>
<div className="px-5 my-5">
<VideoSliderPage />
</div>
</div>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="giat-penugasan">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<div className="flex justify-between items-center py-4 px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
/>
</InputGroup>
</div>
</div>
<div className="ml-5 pb-3">
<Label>Audio Visual</Label>
<div className="px-5 my-5">
<VideoSliderPage />
</div>
<Label>Audio</Label>
<div className="px-5 my-5">
<AudioSliderPage />
</div>
<Label>Foto</Label>
<div className="px-5 my-5">
<ImageSliderPage />
</div>
<Label>Teks</Label>
<div className="px-5 my-5">
<TeksSliderPage />
</div>
</div>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="contest">
<Card>
<div className="py-3">
<ContestTable />
</div>
</Card>
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default VideoAllPage;

View File

@ -28,7 +28,7 @@ const VideoSliderPage = () => {
}, [allVideoData]);
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;

View File

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

View File

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

View File

@ -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,22 +363,27 @@ 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}
/>
{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">
@ -328,6 +394,9 @@ export default function FormBlogUpdate() {
/>
</Card>
</div>
)
)}
</div>
<div className="px-3 py-3 mt-6">
<label
htmlFor="kategori"
@ -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>

View File

@ -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,9 +189,11 @@ export default function FormBlog() {
}
const blogId = response?.data?.data.id;
if (blogId && thumbnail) {
if (blogId) {
if (thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail);
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
console.log("FormMedia:", formMedia.get("file"));
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
@ -187,9 +201,21 @@ export default function FormBlog() {
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"
/>
)}
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>

View File

@ -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
setReplies([...replies, newReply]);
setReplyMessage("");
setReplyVisible(false);
} catch (error) {
MySwal.fire({
title: "Error",
text: "Gagal mengirim balasan.",
icon: "error",
});
console.error("Error sending reply:", error);
}
};
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>

View File

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

View File

@ -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,21 +463,23 @@ 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}
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-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)}</>
@ -461,7 +496,7 @@ export default function FormAudioUpdate() {
color="destructive"
variant="outline"
className="border-none rounded-full"
onClick={() => handleRemoveFile(file)}
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
>
<Icon icon="tabler:x" className="h-5 w-5" />
</Button>
@ -500,6 +535,55 @@ export default function FormAudioUpdate() {
});
};
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
@ -606,12 +690,12 @@ export default function FormAudioUpdate() {
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
<Button
{/* <Button
color="destructive"
onClick={handleRemoveAllFiles}
>
Remove All
</Button>
</Button> */}
</div>
</Fragment>
) : null}
@ -752,14 +836,34 @@ export default function FormAudioUpdate() {
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className="border rounded-md px-2 py-2"
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
>
{tag.trim()}
</Badge>
<input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))}
</div>
</div>
@ -767,38 +871,21 @@ export default function FormAudioUpdate() {
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
{options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id="5"
checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)}
id={option.id}
checked={
option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/>
<Label htmlFor="5">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="6"
checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)}
/>
<Label htmlFor="6">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
<Label htmlFor={option.id}>{option.name}</Label>
</div>
))}
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">

View File

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

View File

@ -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,15 +507,17 @@ export default function FormImageUpdate() {
setFiles([...filtered]);
};
const fileList = files.map((file) => (
const fileList = files.map((file: any) => (
<div
key={file.name}
key={file.id} // Gunakan ID file sebagai key
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<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-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)}</>
@ -476,7 +534,7 @@ export default function FormImageUpdate() {
color="destructive"
variant="outline"
className="border-none rounded-full"
onClick={() => handleRemoveFile(file)}
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
>
<Icon icon="tabler:x" className="h-5 w-5" />
</Button>
@ -515,6 +573,55 @@ export default function FormImageUpdate() {
});
};
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
@ -549,14 +656,17 @@ export default function FormImageUpdate() {
<div className="py-3 w-full">
<Label>Kategori</Label>
<Select
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
onValueChange={(id) => {
console.log("Selected Category:", id);
setSelectedTarget(id);
console.log("Selected Category ID:", id);
setSelectedTarget(id); // Perbarui ID kategori
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
<SelectValue placeholder="Pilih">
{categories.find((cat) => cat.id === selectedTarget)
?.name || "Pilih"}
</SelectValue>
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
@ -619,12 +729,12 @@ export default function FormImageUpdate() {
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
<Button
{/* <Button
color="destructive"
onClick={handleRemoveAllFiles}
onClick={() => handleDeleteFile(id)}
>
Remove All
</Button>
Remove file
</Button> */}
</div>
</Fragment>
) : null}
@ -782,24 +892,30 @@ export default function FormImageUpdate() {
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 ">
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
>
{tag}{" "}
<input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button"
className="remove-tag-button text-white"
>
×
</button>
</span>
))}
</div>
<div className="flex flex-wrap gap-2">
{/* <div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
key={index}
@ -808,44 +924,27 @@ export default function FormImageUpdate() {
{tag.trim()}
</Badge>
))}
</div>
</div> */}
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
{options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id="5"
checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)}
id={option.id}
checked={
option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/>
<Label htmlFor="5">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="6"
checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)}
/>
<Label htmlFor="6">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
<Label htmlFor={option.id}>{option.name}</Label>
</div>
))}
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">

View File

@ -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) => (
{files?.map((file, index) => (
<div
key={data.contentId}
className="flex items-center gap-3 mt-2"
key={file.contentId}
className="flex flex-row gap-2 items-center my-3"
>
<img
className="object-cover w-36 h-32"
src={data}
alt={`Thumbnail ${data.contentId}`}
src={file.contentFile}
className="w-[150px] rounded-md"
/>
<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"
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between text-sm">
{file.fileName}
{/* <a
onClick={() =>
handleDeleteFileApproval(file.id)
}
>
<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
<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))
}
/>
{value.charAt(0).toUpperCase() + value.slice(1)}
<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,7 +872,9 @@ 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) => (
{detail?.contentTag
?.split(",")
.map((tag: any, index: any) => (
<Badge
key={index}
className="border rounded-md px-2 py-2"

View File

@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react/dist/iconify.js";
import Image from "next/image";
import { error } from "@/lib/swal";
import { error, loading } from "@/lib/swal";
import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth";
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
polres: false,
});
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
let fileTypeId = "3";
@ -164,10 +165,6 @@ export default function FormTeksUpdate() {
// }
// };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const files = Array.from(event.target.files);
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
setFiles(details.files);
}
if (details.publishedForObject) {
const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id
);
setSelectedPublishers(publisherIds);
if (details?.publishedFor) {
// Split string "7" to an array ["7"] if needed
setPublishedFor(details.publishedFor.split(","));
}
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
}
const matchingCategory = categories.find(
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
initState();
}, [refresh, setValue]);
const handleCheckboxChange = (id: string): void => {
const handleCheckboxChange = (id: string) => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
// Select all options except "all"
const allOptions = options
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
publishedFor.length === allOptions.length ? [] : allOptions
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
// Toggle individual option
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
}
};
const save = async (data: TeksSchema) => {
loading();
const finalTags = tags.join(", ");
const requestData = {
...data,
id: detail?.id,
@ -293,7 +284,7 @@ export default function FormTeksUpdate() {
statusId: "1",
publishedFor: publishedFor.join(","),
creatorName: data.creatorName,
tags: "siap",
tags: finalTags,
isYoutube: false,
isInternationalMedia: false,
};
@ -535,6 +526,29 @@ export default function FormTeksUpdate() {
});
};
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
if (inputRef.current) {
inputRef.current.value = ""; // Kosongkan input
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
@ -785,7 +799,7 @@ export default function FormTeksUpdate() {
)}
</div>
</div>
<div className="mt-3 px-3">
{/* <div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label>
<Card className="mt-2">
<img
@ -794,18 +808,38 @@ export default function FormTeksUpdate() {
className="w-full h-auto rounded"
/>
</Card>
</div>
</div> */}
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className="border rounded-md px-2 py-2"
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
>
{tag.trim()}
</Badge>
<input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))}
</div>
</div>
@ -817,7 +851,6 @@ export default function FormTeksUpdate() {
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id={option.id}
value={detail?.publishedForObject.name}
checked={
option.id === "all"
? publishedFor.length ===

View File

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

View File

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

View File

@ -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,15 +488,17 @@ export default function FormVideoUpdate() {
setFiles([...filtered]);
};
const fileList = files.map((file) => (
const fileList = files.map((file: any) => (
<div
key={file.name}
key={file.id} // Gunakan ID file sebagai key
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<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-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)}</>
@ -479,7 +515,7 @@ export default function FormVideoUpdate() {
color="destructive"
variant="outline"
className="border-none rounded-full"
onClick={() => handleRemoveFile(file)}
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
>
<Icon icon="tabler:x" className="h-5 w-5" />
</Button>
@ -518,6 +554,55 @@ export default function FormVideoUpdate() {
});
};
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
@ -626,12 +711,12 @@ export default function FormVideoUpdate() {
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
<Button
{/* <Button
color="destructive"
onClick={handleRemoveAllFiles}
>
Remove All
</Button>
</Button> */}
</div>
</Fragment>
) : null}
@ -782,14 +867,34 @@ export default function FormVideoUpdate() {
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className="border rounded-md px-2 py-2"
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
>
{tag.trim()}
</Badge>
<input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))}
</div>
</div>
@ -797,38 +902,21 @@ export default function FormVideoUpdate() {
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
{options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id="5"
checked={selectedPublishers.includes(5)}
onChange={() => handleCheckboxChange(5)}
id={option.id}
checked={
option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/>
<Label htmlFor="5">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="6"
checked={selectedPublishers.includes(6)}
onChange={() => handleCheckboxChange(6)}
/>
<Label htmlFor="6">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
/>
<Label htmlFor="8">KSP</Label>
<Label htmlFor={option.id}>{option.name}</Label>
</div>
))}
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,51 +1040,226 @@ 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>
<Label>Foto</Label>
<FileUploader
accept={{
"image/*": [],
}}
maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/>
<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>
<FileUploader
accept={{
"pdf/*": [],
}}
maxSize={100}
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(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>
<Label>Voice Note</Label>
<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>
{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>
{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={{
@ -1117,16 +1269,7 @@ export default function FormTaskDetail() {
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"
/>
)} */}
</div>
{audioFile && (
<div className="flex flex-row justify-between items-center">
@ -1144,15 +1287,24 @@ 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"
<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"
placeholder="Masukkan link voice note"
value={voiceNoteLink}
onChange={(e) => setVoiceNoteLink(e.target.value)}
className="border rounded p-2 w-full"
value={url}
// onChange={(e) =>
// handleLinkChange(index, e.target.value)
// }
placeholder={`Masukkan link berita ${index + 1}`}
/>
</div>
))}
</div>
</div>
</div>
</div>
@ -1172,8 +1324,7 @@ export default function FormTaskDetail() {
</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>

View File

@ -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>
<Label>Voice Note</Label>
<div className=" text-sm text-card-foreground">
{file.fileName}
</div>
</div>
</div>
<Button
size="icon"
color="destructive"
variant="outline"
className=" border-none rounded-full"
onClick={() => handleRemoveFile(file)}
>
<Icon icon="tabler:x" className=" h-5 w-5" />
</Button>
</div>
</div>
))}
</div>
<div>
<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,15 +1044,31 @@ export default function FormTaskEdit() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */}
<div className="mt-4">
<Label htmlFor="voiceNoteLink">Link Berita</Label>
<Input
id="voiceNoteLink"
<h2 className="text-lg font-bold">Link Berita</h2>
{urlInputs.map((url: any, index: any) => (
<div
key={url.id}
className="flex items-center gap-2 mt-2"
>
<input
type="url"
placeholder="Masukkan link voice note"
value={voiceNoteLink}
onChange={(e) => setVoiceNoteLink(e.target.value)}
className="border rounded p-2 w-full"
defaultValue={url.attachmentUrl}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
placeholder={`Masukkan link berita ${index + 1}`}
/>
</div>
))}
<button
type="button"
className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
onClick={handleAddLink}
>
Tambah Link
</button>
</div>
</div>
</div>
</div>

View File

@ -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"
<h2 className="text-lg font-bold">Link Berita</h2>
{links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2">
<input
type="url"
placeholder="Masukkan link voice note"
value={voiceNoteLink}
onChange={(e) => setVoiceNoteLink(e.target.value)}
className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`}
value={link}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
/>
{links.length > 1 && (
<button
type="button"
className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)}
>
Hapus
</button>
)}
</div>
))}
<button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
>
Tambah Link
</button>
</div>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,5 +1,8 @@
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
import {
httpDeleteInterceptor,
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function getAgendaSettingsById(id: any) {
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1,16 @@
../typescript/bin/tsc
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
else
exec node "$basedir/../typescript/bin/tsc" "$@"
fi

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

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*

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

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@ -1 +1,16 @@
../typescript/bin/tsserver
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
else
exec node "$basedir/../typescript/bin/tsserver" "$@"
fi

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

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*

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

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsserver" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@ -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&nbsp;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).

View File

@ -1,17 +0,0 @@
Software License Agreement
==========================
**CKEditor&nbsp;5 text alignment feature** https://github.com/ckeditor/ckeditor5-alignment <br>
Copyright (c) 20032024, [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.

View File

@ -1,20 +0,0 @@
CKEditor&nbsp;5 text alignment feature
========================================
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-alignment.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment)
[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
[![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://app.travis-ci.com/github/ckeditor/ckeditor5)
This package implements text alignment support for CKEditor&nbsp;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&nbsp;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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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