This commit is contained in:
Sabda Yagra 2025-07-14 23:34:52 +07:00
parent cd80bd07cb
commit aea083c0ea
24 changed files with 1677 additions and 757 deletions

View File

@ -119,8 +119,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]); const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const t = useTranslations("CalendarApp"); const t = useTranslations("CalendarApp");
// Modal states
const [sheetOpen, setSheetOpen] = useState<boolean>(false); const [sheetOpen, setSheetOpen] = useState<boolean>(false);
const [date, setDate] = useState<Date>(new Date()); const [date, setDate] = useState<Date>(new Date());
const [activeView, setActiveView] = useState("listYear"); const [activeView, setActiveView] = useState("listYear");
@ -132,7 +130,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)) dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
); );
// Load events when view or month changes
useEffect(() => { useEffect(() => {
if (activeView === "listYear") { if (activeView === "listYear") {
getYearlyEvents(); getYearlyEvents();
@ -141,7 +138,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
} }
}, [activeView, selectedMonth]); }, [activeView, selectedMonth]);
// Initialize selected categories
useEffect(() => { useEffect(() => {
if (categories?.length > 0) { if (categories?.length > 0) {
setSelectedCategory(categories.map((c) => c.value)); setSelectedCategory(categories.map((c) => c.value));
@ -162,11 +158,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
return; return;
} }
// For monthly view, we need to extract events from the specific month
const monthData = res?.data?.data; const monthData = res?.data?.data;
if (monthData) { if (monthData) {
const allEvents: CalendarEvent[] = []; const allEvents: CalendarEvent[] = [];
// Map API data to the calendarEvents structure
const events = monthData?.map((event: any) => ({ const events = monthData?.map((event: any) => ({
id: event.id.toString(), id: event.id.toString(),
title: event.title, title: event.title,
@ -208,7 +202,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
return; return;
} }
// The API already returns data organized by months
const yearlyData = res?.data?.data; const yearlyData = res?.data?.data;
if (yearlyData) { if (yearlyData) {
setYearlyData(yearlyData); setYearlyData(yearlyData);
@ -221,7 +214,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
} }
}; };
// Filter events based on selected categories
const filteredEvents = calendarEvents.filter((event) => { const filteredEvents = calendarEvents.filter((event) => {
if (!selectedCategory.length) return false; if (!selectedCategory.length) return false;
console.log("Event category : ", selectedCategory); console.log("Event category : ", selectedCategory);
@ -231,7 +223,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
.map((val: string) => val.trim()); .map((val: string) => val.trim());
const allCategoryId = ["1", "2", "3", "4", "5"]; const allCategoryId = ["1", "2", "3", "4", "5"];
// Cek apakah SEMUA validTypeIds ada di typeIdsInData
const hasAllCategories = allCategoryId.every((categoryId) => const hasAllCategories = allCategoryId.every((categoryId) =>
selectedCategory.includes(categoryId) selectedCategory.includes(categoryId)
); );
@ -242,7 +233,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
); );
}); });
// Event handlers
const handleEventClick = (arg: any) => { const handleEventClick = (arg: any) => {
setSelectedEventDate(null); setSelectedEventDate(null);
setSelectedEventData(arg); setSelectedEventData(arg);
@ -307,7 +297,6 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
wait().then(() => (document.body.style.pointerEvents = "auto")); wait().then(() => (document.body.style.pointerEvents = "auto"));
}; };
// Utility functions
const getEventColor = (type: EventType): string => { const getEventColor = (type: EventType): string => {
const typeSplit = type.split(","); const typeSplit = type.split(",");
const firstType = typeSplit[0] as EventType; const firstType = typeSplit[0] as EventType;

View File

@ -234,7 +234,6 @@ const EventModal = ({
setWilayahPublish(wilayahState); setWilayahPublish(wilayahState);
// Atur unit berdasarkan agendaType
if (rawAgendaTypes.includes("2")) { if (rawAgendaTypes.includes("2")) {
setSelectedPolda(assignedToLevel); setSelectedPolda(assignedToLevel);
} }
@ -272,8 +271,6 @@ const EventModal = ({
const toggleWilayah = (key: string) => { const toggleWilayah = (key: string) => {
setWilayahPublish((prev: any) => { setWilayahPublish((prev: any) => {
let newState = { ...prev }; let newState = { ...prev };
// Jika key === semua dan sebelumnya belum aktif, aktifkan semua
if (key === "semua") { if (key === "semua") {
const newChecked = !prev.semua; const newChecked = !prev.semua;
newState = { newState = {
@ -294,11 +291,9 @@ const EventModal = ({
return newState; return newState;
} }
// Jika key bukan "semua"
newState[key] = !prev[key]; newState[key] = !prev[key];
newState.semua = false; // Uncheck "semua" jika yang dipilih adalah individu newState.semua = false;
// Hitung ulang agendaType berdasarkan pilihan
const selectedKeys = Object.entries(newState) const selectedKeys = Object.entries(newState)
.filter(([k, v]) => v && k !== "semua") .filter(([k, v]) => v && k !== "semua")
.map(([k]) => wilayahValueMap[k]); .map(([k]) => wilayahValueMap[k]);
@ -348,8 +343,8 @@ const EventModal = ({
id: detailData?.id, id: detailData?.id,
title: data.title, title: data.title,
description: data.description, description: data.description,
agendaType: agendaTypeList.join(","), // <-- ubah array jadi string agendaType: agendaTypeList.join(","),
assignedToLevel: assignedToLevelList.join(","), // <-- ubah array jadi string assignedToLevel: assignedToLevelList.join(","),
startDate: format(startDate, "yyyy-MM-dd"), startDate: format(startDate, "yyyy-MM-dd"),
endDate: format(endDate, "yyyy-MM-dd"), endDate: format(endDate, "yyyy-MM-dd"),
}; };
@ -393,9 +388,9 @@ const EventModal = ({
await uploadResumableFile( await uploadResumableFile(
index, index,
String(id), String(id),
item, // Use .file to access the actual File object item,
"4", "4",
"0" // Optional: Replace with actual duration if available "0"
); );
}); });
if (publish) { if (publish) {
@ -466,28 +461,26 @@ const EventModal = ({
const onRecordingStart = () => { const onRecordingStart = () => {
setIsRecording(true); setIsRecording(true);
// Start a timer that stops the recording after 2 minutes (120 seconds)
const countdown = setInterval(() => { const countdown = setInterval(() => {
setTimer((prevTimer) => { setTimer((prevTimer) => {
if (prevTimer <= 1) { if (prevTimer <= 1) {
clearInterval(countdown); // Stop the timer when it reaches 0 clearInterval(countdown);
return 0; return 0;
} }
return prevTimer - 1; return prevTimer - 1;
}); });
}, 1000); // Update every second }, 1000);
// Automatically stop recording after 2 minutes
setTimeout(() => { setTimeout(() => {
if (isRecording) { if (isRecording) {
handleStopRecording(); handleStopRecording();
} }
}, 120000); // Stop after 2 minutes (120,000 ms) }, 120000);
}; };
const handleStopRecording = () => { const handleStopRecording = () => {
setIsRecording(false); setIsRecording(false);
setTimer(120); // Reset the timer to 2 minutes for the next recording setTimer(120);
}; };
const addAudioElement = (blob: Blob) => { const addAudioElement = (blob: Blob) => {
@ -497,19 +490,16 @@ const EventModal = ({
audio.controls = true; audio.controls = true;
document.body.appendChild(audio); document.body.appendChild(audio);
// Convert Blob to File and add preview
const fileWithPreview: FileWithPreview = Object.assign( const fileWithPreview: FileWithPreview = Object.assign(
new File([blob], "voiceNote.webm", { type: "audio/webm" }), new File([blob], "voiceNote.webm", { type: "audio/webm" }),
{ preview: url } { preview: url }
); );
// Add to state
setAudioFile(fileWithPreview); setAudioFile(fileWithPreview);
setAudioFiles((prev) => [...prev, fileWithPreview]); setAudioFiles((prev) => [...prev, fileWithPreview]);
}; };
const handleDeleteAudio = () => { const handleDeleteAudio = () => {
// Remove the audio file by setting state to null
setAudioFile(null); setAudioFile(null);
const audioElements = document.querySelectorAll("audio"); const audioElements = document.querySelectorAll("audio");
audioElements.forEach((audio) => audio.remove()); audioElements.forEach((audio) => audio.remove());
@ -842,7 +832,7 @@ const EventModal = ({
{wilayahPublish.polda && ( {wilayahPublish.polda && (
<UnitMapping <UnitMapping
unit="Polda" unit="Polda"
isDetail={isDetailMode} // jika Anda punya kondisi detail isDetail={isDetailMode}
initData={selectedPolda} initData={selectedPolda}
sendDataToParent={(data: any) => sendDataToParent={(data: any) =>
setSelectedPolda(data) setSelectedPolda(data)

View File

@ -123,7 +123,6 @@ export function UnitMapping(props: {
? isAllSatkerChecked ? isAllSatkerChecked
: isAllPolresChecked : isAllPolresChecked
} }
disabled={isDetail}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
if (checked) { if (checked) {
form.setValue( form.setValue(
@ -139,6 +138,7 @@ export function UnitMapping(props: {
} }
}} }}
/> />
<label htmlFor="all" className="text-sm text-black uppercase"> <label htmlFor="all" className="text-sm text-black uppercase">
SEMUA {unit} SEMUA {unit}
</label> </label>
@ -170,7 +170,6 @@ export function UnitMapping(props: {
> >
<FormControl> <FormControl>
<Checkbox <Checkbox
disabled={isDetail}
checked={field.value?.includes(String(item.id))} checked={field.value?.includes(String(item.id))}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
return checked return checked

View File

@ -62,11 +62,12 @@ const BlogTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50"); const [showData, setShowData] = React.useState("10");
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: Number(showData), pageSize: 10,
}); });
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
@ -143,14 +144,12 @@ const BlogTable = () => {
} }
const handleCheckboxChange = (categoryId: number) => { const handleCheckboxChange = (categoryId: number) => {
setSelectedCategories( setSelectedCategories((prev: any) =>
(prev: any) =>
prev.includes(categoryId) prev.includes(categoryId)
? prev.filter((id: any) => id !== categoryId) // Hapus jika sudah dipilih ? prev.filter((id: any) => id !== categoryId)
: [...prev, categoryId] // Tambahkan jika belum dipilih : [...prev, categoryId]
); );
// Perbarui filter kategori
setCategoryFilter((prev) => { setCategoryFilter((prev) => {
const updatedCategories = prev.split(",").filter(Boolean).map(Number); const updatedCategories = prev.split(",").filter(Boolean).map(Number);
@ -171,8 +170,8 @@ const BlogTable = () => {
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search setSearch(e.target.value);
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel table.getColumn("judul")?.setFilterValue(e.target.value);
}; };
return ( return (
@ -181,7 +180,8 @@ const BlogTable = () => {
<CardTitle> <CardTitle>
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900"> <div className="flex-1 text-xl font-medium text-default-900">
{t("table", { defaultValue: "Table" })} {t("blog", { defaultValue: "Blog" })} {t("table", { defaultValue: "Table" })}{" "}
{t("blog", { defaultValue: "Blog" })}
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/blog/create"}> <Link href={"/contributor/blog/create"}>
@ -254,7 +254,9 @@ const BlogTable = () => {
<div className="flex flex-row justify-between my-1 mx-1"> <div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p> <p>Filter</p>
</div> </div>
<Label className="ml-2">{t("category", { defaultValue: "Category" })}</Label> <Label className="ml-2">
{t("category", { defaultValue: "Category" })}
</Label>
{categories.length > 0 ? ( {categories.length > 0 ? (
categories.map((category) => ( categories.map((category) => (
<div <div

View File

@ -7,18 +7,34 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import NewContent from "@/components/landing-page/new-content"; import NewContent from "@/components/landing-page/new-content";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { close, error, loading } from "@/config/swal"; import { close, error, loading, successCallback } from "@/config/swal";
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing"; import {
checkWishlistStatus,
deleteWishlist,
getDetail,
getPublicSuggestionList,
saveWishlist,
} from "@/service/landing/landing";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { postActivityLog } from "@/service/content/content"; import { postActivityLog } from "@/service/content/content";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useTranslations } from "next-intl";
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
const DetailInfo = () => { const DetailInfo = () => {
const [selectedSize, setSelectedSize] = useState<string>("L"); const [selectedSize, setSelectedSize] = useState<string>("L");
const [selectedTab, setSelectedTab] = useState("video"); const [emailShareList, setEmailShareList] = useState<any>();
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const params = useParams(); const params = useParams();
const slug = String(params?.slug); const slug = String(params?.slug);
const t = useTranslations("LandingPage");
const [detailDataImage, setDetailDataImage] = useState<any>(); const [detailDataImage, setDetailDataImage] = useState<any>();
const [selectedImage, setSelectedImage] = useState(0); const [selectedImage, setSelectedImage] = useState(0);
const [isSaved, setIsSaved] = useState(false); const [isSaved, setIsSaved] = useState(false);
@ -30,8 +46,14 @@ const DetailInfo = () => {
const [main, setMain] = useState<any>(); const [main, setMain] = useState<any>();
const [resolutionSelected, setResolutionSelected] = useState("720"); const [resolutionSelected, setResolutionSelected] = useState("720");
const [imageSizeSelected, setImageSizeSelected] = useState("l"); const [imageSizeSelected, setImageSizeSelected] = useState("l");
const [emailShareInput, setEmailShareInput] = useState<any>();
const [emailMessageInput, setEmailMessageInput] = useState();
const [content, setContent] = useState<any>([]);
const [listSuggestion, setListSuggestion] = useState<any>();
const [width, setWidth] = useState<any>();
const userRoleId = getCookiesDecrypt("urie");
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
let typeString = "image";
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
@ -42,7 +64,12 @@ const DetailInfo = () => {
const initFetch = async () => { const initFetch = async () => {
const response = await getDetail(String(slug)); const response = await getDetail(String(slug));
console.log("detailImage", response); console.log("detailImage", response);
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
setIsFromSPIT(response?.data?.data?.isFromSPIT); setIsFromSPIT(response?.data?.data?.isFromSPIT);
setWidth(window.innerWidth);
setContent(response?.data.data);
setListSuggestion(responseGet?.data?.data);
setMain({ setMain({
id: response?.data?.data?.files[0]?.id, id: response?.data?.data?.files[0]?.id,
type: response?.data?.data?.fileType.name, type: response?.data?.data?.fileType.name,
@ -63,6 +90,42 @@ const DetailInfo = () => {
setDetailDataImage(response?.data?.data); setDetailDataImage(response?.data?.data);
}; };
const handleEmailList = (e: any) => {
const arrayEmail: any = [];
for (let i = 0; i < emailShareList?.length; i += 1) {
arrayEmail.push(emailShareList[i]);
}
if (e.which == 13) {
if (e.target.value) {
arrayEmail.push(e.target.value);
setEmailShareList(arrayEmail);
setEmailShareInput("");
}
e.preventDefault();
}
return false;
};
async function shareToEmail() {
if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login");
} else {
const data = {
mediaUploadId: slug?.split("-")?.[0],
email: emailShareList || [emailShareInput],
message: emailMessageInput,
url: window.location.href,
};
loading();
const res = await sendMediaUploadToEmail(data);
if (res?.error) {
error(res.message);
return false;
}
close();
successCallback("Konten Telah Dikirim");
}
}
const doBookmark = async () => { const doBookmark = async () => {
if (userId) { if (userId) {
const data = { const data = {
@ -114,6 +177,26 @@ const DetailInfo = () => {
} }
}; };
const handleShare = (type: any, url: any) => {
if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login");
} else {
sendActivityLog(2);
sendActivityLog(4);
if (type == "wa" && width <= 768) {
window.open(`whatsapp://send?${url}`, "_blank");
} else if (type == "wa" && width > 768) {
window.open(
`https://web.whatsapp.com/send?${url}`,
"_blank",
"noreferrer"
);
} else {
window.open(url);
}
}
};
const sizes = [ const sizes = [
{ label: "XL", value: "3198 x 1798 px" }, { label: "XL", value: "3198 x 1798 px" },
{ label: "L", value: "2399 x 1349 px" }, { label: "L", value: "2399 x 1349 px" },
@ -136,7 +219,7 @@ const DetailInfo = () => {
const handleDownload = () => { const handleDownload = () => {
if (downloadProgress === 0) { if (downloadProgress === 0) {
if (!userId) { if (!userId) {
router.push("/auth/login"); router.push("/auth");
} else { } else {
sendActivityLog(2); sendActivityLog(2);
sendActivityLog(3); sendActivityLog(3);
@ -196,7 +279,8 @@ const DetailInfo = () => {
xhr.addEventListener("readystatechange", () => { xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream"; const contentType =
xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1]; const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`; const filename = `${name}.${extension}`;
@ -229,7 +313,11 @@ const DetailInfo = () => {
<div className="md:w-3/4"> <div className="md:w-3/4">
{/* Gambar Besar */} {/* Gambar Besar */}
<div className="relative"> <div className="relative">
<img src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" /> <img
src={detailDataImage?.files[selectedImage]?.url}
alt="Main"
className="rounded-lg w-auto h-fit"
/>
<div className="absolute top-4 left-4"></div> <div className="absolute top-4 left-4"></div>
</div> </div>
@ -237,7 +325,10 @@ const DetailInfo = () => {
<div className="py-4 flex flex-row gap-3"> <div className="py-4 flex flex-row gap-3">
{detailDataImage?.files?.map((file: any, index: number) => ( {detailDataImage?.files?.map((file: any, index: number) => (
<a onClick={() => setSelectedImage(index)} key={file?.id}> <a onClick={() => setSelectedImage(index)} key={file?.id}>
<img src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" /> <img
src={file?.url}
className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600"
/>
</a> </a>
))} ))}
</div> </div>
@ -245,65 +336,109 @@ const DetailInfo = () => {
{/* Footer Informasi */} {/* Footer Informasi */}
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4"> <div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
<div className="flex flex-row items-center mt-3 justify-between"> <div className="flex flex-row items-center mt-3 justify-between">
oleh&nbsp;<span className="font-semibold text-black">{detailDataImage?.uploadedBy?.userLevel?.name}</span>&nbsp; | &nbsp;Diupdate pada {detailDataImage?.updatedAt} WIB &nbsp;|&nbsp; oleh&nbsp;
<span className="font-semibold text-black">
{detailDataImage?.uploadedBy?.userLevel?.name}
</span>
&nbsp; | &nbsp;Diupdate pada {detailDataImage?.updatedAt} WIB
&nbsp;|&nbsp;
<Icon icon="formkit:eye" width="15" height="15" /> <Icon icon="formkit:eye" width="15" height="15" />
&nbsp; {detailDataImage?.clickCount} &nbsp; &nbsp; {detailDataImage?.clickCount} &nbsp;
<p className="flex text-end">Kreator: {detailDataImage?.creatorName}</p> <p className="flex text-end">
Kreator: {detailDataImage?.creatorName}
</p>
</div> </div>
</div> </div>
{/* Keterangan */} {/* Keterangan */}
<div className="w-full"> <div className="w-full">
<h1 className="flex flex-row font-bold text-2xl my-8">{detailDataImage?.title}</h1> <h1 className="flex flex-row font-bold text-2xl my-8">
<div className="font-light text-justify" dangerouslySetInnerHTML={{ __html: detailDataImage?.htmlDescription }} /> {detailDataImage?.title}
</h1>
<div
className="font-light text-justify"
dangerouslySetInnerHTML={{
__html: detailDataImage?.htmlDescription,
}}
/>
</div> </div>
</div> </div>
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] h-fit rounded-lg mx-4"> <div className="p-4 bg-[#f7f7f7] dark:bg-slate-600 h-fit rounded-lg mx-4">
{isSaved ? ( {isSaved ? (
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer"> <a
onClick={() => handleDeleteWishlist()}
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark" width={40} /> <Icon icon="material-symbols:bookmark" width={40} />
<p className="text-base lg:text-lg">Hapus</p> <p className="text-base lg:text-lg">
{t("delete", { defaultValue: "Delete" })}
</p>
</a> </a>
) : ( ) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer"> <a
<Icon icon="material-symbols:bookmark-outline" width={40} /> onClick={() => doBookmark()}
<p className="text-base lg:text-lg">Simpan</p> className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark-outline" width={25} />
<p className="text-base lg:text-sm">
{t("save", { defaultValue: "Save" })}
</p>
</a> </a>
)} )}
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="flex flex-col justify-center items-center gap-3">
<Link href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"> <div className="w-auto mb-3">
<Link
href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`}
className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 rounded w-auto"
>
{detailDataImage?.category?.name} {detailDataImage?.category?.name}
</Link> </Link>
</div>
<div className="flex justify-center flex-wrap gap-2 mb-4"> <div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataImage?.tags?.split(",").map((tag: string) => ( {detailDataImage?.tags?.split(",").map((tag: string) => (
<a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500"> <a
onClick={() => router.push(`/all/filter?tag=${tag}`)}
key={tag}
className="bg-gray-200 text-gray-700 text-xs px-3 py-3 font-semibold rounded-full cursor-pointer hover:bg-gray-500"
>
{tag} {tag}
</a> </a>
))} ))}
</div> </div>
</div>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
{/* Opsi Ukuran Foto */} {/* Opsi Ukuran Foto */}
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4> <h4 className="flex text-lg justify-center items-center font-semibold my-3">
{t("imageSize", { defaultValue: "Image Size" })}
</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size: any) => ( {sizes.map((size: any) => (
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer"> <div
<input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" /> key={size?.label}
<div className="text-sm">{size.label}</div> className="items-center flex flex-row gap-2 cursor-pointer"
</div> >
<div className=""> <Input
<div className="text-sm">{size.value}</div> type="radio"
name="size"
value={size?.label}
checked={selectedSize === size?.label}
onChange={(e) => setImageSizeSelected(e.target.value)}
className="text-red-600 focus:ring-red-600"
/>
<div className="text-sm">{size?.label}</div>
</div> </div>
<div className="text-sm">{size?.value}</div>
</div> </div>
))} ))}
</div> </div>
@ -311,18 +446,177 @@ const DetailInfo = () => {
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} /> <input
<span>Download Semua File?</span> type="checkbox"
className="text-red-600 focus:ring-red-600"
onChange={() => setIsDownloadAll(!isDownloadAll)}
/>
<span>
{t("downloadAll", { defaultValue: "Download All" })}
</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"> <button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> onClick={handleDownload}
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" /> className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="white"
d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"
/>
</svg> </svg>
Download {t("download", { defaultValue: "Download" })}
</button> </button>
{/* Tombol Bagikan */}
<div className="flex flex-row mt-5 justify-center">
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
<Popover>
<PopoverTrigger asChild>
<button className="w-full flex items-center gap-2">
{/* <Icon icon="oi:share" fontSize={20} /> */}
<p className="text-base font-semibold mb-2">
{t("share", { defaultValue: "Share" })}
</p>
</button>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col">
<h1 className="mb-2">
{t("shareTo", { defaultValue: "Share To" })}
</h1>
<div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1">
{t("destinationEmail", {
defaultValue: "Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter", {
defaultValue: "Press Enter",
})}
/>
</div>
<Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send", { defaultValue: "Send" })}
</Button>
</div>
</PopoverContent>
</Popover>
</div>
<a
className="ml-8 cursor-pointer"
onClick={() =>
handleShare(
"fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
)
}
>
<Icon
icon="brandico:facebook"
height="20"
className="px-auto text-red-600 text-center"
/>
</a>
<a
className="ml-5 cursor-pointer"
onClick={() =>
handleShare(
"tw",
`https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
)
}
>
<Icon
icon="mdi:twitter"
width="23"
className="text-red-600 text-center"
/>
</a>
<a
className="ml-5 cursor-pointer"
onClick={() =>
handleShare(
"wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
)
}
>
<Icon
icon="ri:whatsapp-fill"
width="23"
className="text-red-600 text-center"
/>
</a>
<Popover>
<PopoverTrigger
className="flex justify-end gap-1 cursor-pointer"
asChild
>
<a
className="ml-5 cursor-pointer"
data-toggle="dropdown"
href="#"
aria-expanded="false"
>
<Icon
icon="material-symbols-light:mail"
width="23"
className="text-red-600 text-center"
/>
</a>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col">
<h1 className="mb-2">
{t("shareTo", { defaultValue: "Share To" })}
</h1>
<div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1">
{t("destinationEmail", {
defaultValue: "Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter", {
defaultValue: "Press Enter",
})}
/>
</div>
<Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send", { defaultValue: "Send" })}
</Button>
</div>
</PopoverContent>
</Popover>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -332,13 +626,18 @@ const DetailInfo = () => {
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]"> <div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
<div className="gap-5 flex flex-col px-4 lg:px-14"> <div className="gap-5 flex flex-col px-4 lg:px-14">
<p className="flex items-start text-lg">Berikan Komentar</p> <p className="flex items-start text-lg">Berikan Komentar</p>
<Textarea placeholder="Type your comments here." className="flex w-full" /> <Textarea
<button className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">Kirim</button> placeholder="Type your comments here."
className="flex w-full"
/>
<button className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">
Kirim
</button>
</div> </div>
</div> </div>
{/* Konten Serupa */} {/* Konten Serupa */}
<div className=""> <div className="px-4 lg:px-24">
<NewContent group="satker" type={"similar"} /> <NewContent group="satker" type={"similar"} />
</div> </div>
</div> </div>

View File

@ -1,17 +1,44 @@
"use client"; "use client";
import { useParams, usePathname } from "next/navigation"; import { useParams, usePathname, useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing"; import {
checkWishlistStatus,
createPublicSuggestion,
deletePublicSuggestion,
deleteWishlist,
getDetail,
getPublicSuggestionList,
saveWishlist,
} from "@/service/landing/landing";
import VideoPlayer from "@/utils/video-player"; import VideoPlayer from "@/utils/video-player";
import NewContent from "@/components/landing-page/new-content"; import NewContent from "@/components/landing-page/new-content";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { close, error, loading } from "@/config/swal"; import { close, error, loading, successCallback, warning } from "@/config/swal";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { postActivityLog } from "@/service/content/content"; import { postActivityLog } from "@/service/content/content";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useTranslations } from "next-intl";
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
import {
checkMaliciousText,
getPublicLocaleTimestamp,
toBase64,
} from "@/utils/globals";
import Image from "next/image";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import parse from "html-react-parser";
const DetailVideo = () => { const DetailVideo = () => {
const [selectedSize, setSelectedSize] = useState<string>("L"); const [selectedSize, setSelectedSize] = useState<string>("L");
@ -20,6 +47,7 @@ const DetailVideo = () => {
const pathname = usePathname(); const pathname = usePathname();
const params = useParams(); const params = useParams();
const slug = String(params?.slug); const slug = String(params?.slug);
const searchParams = useSearchParams();
const [detailDataVideo, setDetailDataVideo] = useState<any>(); const [detailDataVideo, setDetailDataVideo] = useState<any>();
const [isSaved, setIsSaved] = useState(false); const [isSaved, setIsSaved] = useState(false);
const [wishlistId, setWishlistId] = useState(); const [wishlistId, setWishlistId] = useState();
@ -28,9 +56,21 @@ const DetailVideo = () => {
const [downloadProgress, setDownloadProgress] = useState(0); const [downloadProgress, setDownloadProgress] = useState(0);
const [isFromSPIT, setIsFromSPIT] = useState(false); const [isFromSPIT, setIsFromSPIT] = useState(false);
const [main, setMain] = useState<any>(); const [main, setMain] = useState<any>();
const [emailMessageInput, setEmailMessageInput] = useState();
const [resolutionSelected, setResolutionSelected] = useState("720"); const [resolutionSelected, setResolutionSelected] = useState("720");
const t = useTranslations("LandingPage");
const [emailShareInput, setEmailShareInput] = useState<any>();
const [emailShareList, setEmailShareList] = useState<any>();
const userRoleId = getCookiesDecrypt("urie");
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const id = searchParams?.get("id");
const [width, setWidth] = useState<any>();
const [content, setContent] = useState<any>([]);
const [listSuggestion, setListSuggestion] = useState<any>();
const [visibleInput, setVisibleInput] = useState(null);
let typeString = "video";
const MySwal = withReactContent(Swal);
const [message, setMessage] = useState("");
useEffect(() => { useEffect(() => {
initFetch(); initFetch();
@ -41,7 +81,12 @@ const DetailVideo = () => {
const initFetch = async () => { const initFetch = async () => {
const response = await getDetail(String(slug)); const response = await getDetail(String(slug));
console.log("detailVideo", response); console.log("detailVideo", response);
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
setIsFromSPIT(response?.data?.data?.isFromSPIT); setIsFromSPIT(response?.data?.data?.isFromSPIT);
setWidth(window.innerWidth);
setContent(response?.data.data);
setListSuggestion(responseGet?.data?.data);
setMain({ setMain({
id: response?.data?.data?.files[0]?.id, id: response?.data?.data?.files[0]?.id,
type: response?.data?.data?.fileType.name, type: response?.data?.data?.fileType.name,
@ -62,6 +107,40 @@ const DetailVideo = () => {
setDetailDataVideo(response?.data?.data); setDetailDataVideo(response?.data?.data);
}; };
async function sendSuggestionParent() {
if (message?.length > 3) {
loading();
const data = {
mediaUploadId: slug?.split("-")?.[0],
message,
parentId: null,
};
const response = await createPublicSuggestion(data);
console.log(response);
setMessage("");
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
console.log(responseGet?.data?.data);
setListSuggestion(responseGet?.data?.data);
// Hapus nilai semua input secara manual jika perlu
const inputs = document.querySelectorAll("input");
inputs.forEach((input) => {
input.value = "";
});
close();
}
}
const getInputValue = (e: any) => {
const message = e.target.value;
console.log(message);
setMessage(message);
};
const doBookmark = async () => { const doBookmark = async () => {
if (userId) { if (userId) {
const data = { const data = {
@ -83,6 +162,94 @@ const DetailVideo = () => {
router.push("/auth"); router.push("/auth");
} }
}; };
async function sendSuggestionChild(parentId: any) {
const inputElement = document.querySelector(
`#input-comment-${parentId}`
) as HTMLInputElement;
if (inputElement && inputElement.value.length > 3) {
loading();
const data = {
mediaUploadId: slug?.split("-")?.[0],
message: inputElement.value,
parentId,
};
console.log(data);
const response = await createPublicSuggestion(data);
console.log(response);
const responseGet: any = await getPublicSuggestionList(
slug?.split("-")?.[0]
);
console.log(responseGet?.data?.data);
setListSuggestion(responseGet?.data?.data);
// Reset input field
inputElement.value = "";
// document.querySelector("#comment-id-" + parentId)?.style.display = "none";
close();
}
}
async function deleteDataSuggestion(dataId: any) {
loading();
const response = await deletePublicSuggestion(dataId);
console.log(response);
const responseGet = await getPublicSuggestionList(slug.split("-")?.[0]);
console.log(responseGet?.data?.data);
setListSuggestion(responseGet?.data?.data);
close();
}
const postDataChild = (id: any) => {
const checkMessage = checkMaliciousText(message);
if (checkMessage == "") {
if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth");
} else {
sendSuggestionChild(id);
}
} else {
warning(checkMessage);
}
};
const handleShare = (type: any, url: any) => {
if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login");
} else {
sendActivityLog(2);
sendActivityLog(4);
if (type == "wa" && width <= 768) {
window.open(`whatsapp://send?${url}`, "_blank");
} else if (type == "wa" && width > 768) {
window.open(
`https://web.whatsapp.com/send?${url}`,
"_blank",
"noreferrer"
);
} else {
window.open(url);
}
}
};
const postData = () => {
const checkMessage = checkMaliciousText(message);
if (checkMessage == "") {
if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth");
} else {
sendSuggestionParent();
}
} else {
warning(checkMessage);
}
};
async function checkWishlist() { async function checkWishlist() {
if (userId) { if (userId) {
const res = await checkWishlistStatus(slug.split("-")?.[0]); const res = await checkWishlistStatus(slug.split("-")?.[0]);
@ -177,6 +344,28 @@ const DetailVideo = () => {
} }
}; };
const deleteData = (dataId: any) => {
MySwal.fire({
title: "Delete Comment",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Delete",
}).then((result: any) => {
if (result.isConfirmed) {
deleteDataSuggestion(dataId);
console.log(dataId);
}
});
};
const showInput = (e: any) => {
console.log(document.querySelector(`#${e}`)?.classList);
document.querySelector(`#${e}`)?.classList.toggle("none");
setVisibleInput(visibleInput === e ? null : e);
};
const downloadFile = (fileUrl: string, name: string) => { const downloadFile = (fileUrl: string, name: string) => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
@ -192,7 +381,8 @@ const DetailVideo = () => {
xhr.addEventListener("readystatechange", () => { xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream"; const contentType =
xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1]; const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`; const filename = `${name}.${extension}`;
@ -217,6 +407,61 @@ const DetailVideo = () => {
xhr.send(); xhr.send();
}; };
async function shareToEmail() {
if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login");
} else {
const data = {
mediaUploadId: id?.split("-")?.[0],
email: emailShareList || [emailShareInput],
message: emailMessageInput,
url: window.location.href,
};
loading();
const res = await sendMediaUploadToEmail(data);
if (res?.error) {
error(res.message);
return false;
}
close();
successCallback("Konten Telah Dikirim");
}
}
const handleEmailList = (e: any) => {
const arrayEmail: any = [];
for (let i = 0; i < emailShareList?.length; i += 1) {
arrayEmail.push(emailShareList[i]);
}
if (e.which == 13) {
if (e.target.value) {
arrayEmail.push(e.target.value);
setEmailShareList(arrayEmail);
setEmailShareInput("");
}
e.preventDefault();
}
return false;
};
function addDefaultProfile(ev: any) {
ev.target.src = "/assets/avatar-profile.png";
}
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
return ( return (
<> <>
<div className="px-4 md:px-24 py-4"> <div className="px-4 md:px-24 py-4">
@ -233,8 +478,11 @@ const DetailVideo = () => {
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4"> <div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
<p className="flex flex-row items-center mt-3"> <p className="flex flex-row items-center mt-3">
oleh&nbsp; oleh&nbsp;
<span className="font-semibold text-black">{detailDataVideo?.uploadedBy?.userLevel?.name}</span> <span className="font-semibold text-black">
&nbsp;|&nbsp;Diupdate pada {detailDataVideo?.updatedAt} WIB&nbsp;|&nbsp; {detailDataVideo?.uploadedBy?.userLevel?.name}
</span>
&nbsp;|&nbsp;Diupdate pada {detailDataVideo?.updatedAt}{" "}
WIB&nbsp;|&nbsp;
<Icon icon="formkit:eye" width="15" height="15" /> <Icon icon="formkit:eye" width="15" height="15" />
&nbsp; &nbsp;
{detailDataVideo?.clickCount} {detailDataVideo?.clickCount}
@ -244,7 +492,9 @@ const DetailVideo = () => {
{/* Keterangan */} {/* Keterangan */}
<div className="w-full"> <div className="w-full">
<h1 className="flex flex-row font-bold text-2xl my-8">{detailDataVideo?.title}</h1> <h1 className="flex flex-row font-bold text-2xl my-8">
{detailDataVideo?.title}
</h1>
<div <div
className="font-light text-justify" className="font-light text-justify"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -255,29 +505,46 @@ const DetailVideo = () => {
</div> </div>
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] rounded-lg mx-4 h-fit"> <div className="md:w-1/4 p-4 bg-[#f7f7f7] dark:bg-slate-600 rounded-lg mx-4 h-fit">
{isSaved ? ( {isSaved ? (
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer"> <a
onClick={() => handleDeleteWishlist()}
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark" width={40} /> <Icon icon="material-symbols:bookmark" width={40} />
<p className="text-base lg:text-lg">Hapus</p> <p className="text-base lg:text-lg">
{t("delete", { defaultValue: "Delete" })}
</p>
</a> </a>
) : ( ) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer"> <a
onClick={() => doBookmark()}
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark-outline" width={40} /> <Icon icon="material-symbols:bookmark-outline" width={40} />
<p className="text-base lg:text-lg">Simpan</p> <p className="text-base lg:text-lg">
{t("save", { defaultValue: "Save" })}
</p>
</a> </a>
)} )}
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<Link href={`/all/filter?title=polda&category=${detailDataVideo?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"> <Link
href={`/all/filter?title=polda&category=${detailDataVideo?.category.id}`}
className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"
>
{detailDataVideo?.categoryName} {detailDataVideo?.categoryName}
</Link> </Link>
<div className="flex justify-center flex-wrap gap-2 mb-4"> <div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataVideo?.tags.split(",").map((tag: string) => ( {detailDataVideo?.tags.split(",").map((tag: string) => (
<a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500 hover:text-white"> <a
onClick={() => router.push(`/all/filter?tag=${tag}`)}
key={tag}
className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500 hover:text-white"
>
{tag} {tag}
</a> </a>
))} ))}
@ -286,15 +553,27 @@ const DetailVideo = () => {
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
{/* Opsi Ukuran Foto */} {/* Opsi Ukuran Foto */}
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Audio Visual</h4> <h4 className="flex text-lg justify-center items-center font-semibold my-3">
{t("videoSize", { defaultValue: "Video Size" })}
</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size: any) => ( {sizes.map((size: any) => (
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<div key={size.label} className="items-center flex flex-row gap-2 cursor-pointer"> <div
<input type="radio" name="size" value={size.label} checked={selectedSize === size.label} onChange={() => setSelectedSize(size.label)} className="text-red-600 focus:ring-red-600" /> key={size.label}
className="items-center flex flex-row gap-2 cursor-pointer"
>
<input
type="radio"
name="size"
value={size.label}
checked={selectedSize === size.label}
onChange={() => setSelectedSize(size.label)}
className="text-red-600 focus:ring-red-600"
/>
<div className="text-sm">{size.label}</div> <div className="text-sm">{size.label}</div>
</div> </div>
<div className=""> <div className="">
@ -307,33 +586,511 @@ const DetailVideo = () => {
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} /> <input
<span>Download Semua File?</span> type="checkbox"
className="text-red-600 focus:ring-red-600"
onChange={() => setIsDownloadAll(!isDownloadAll)}
/>
<span>
{t("downloadAll", { defaultValue: "Download All" })}
</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"> <button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> onClick={handleDownload}
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" /> className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="white"
d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"
/>
</svg> </svg>
Download {t("download", { defaultValue: "Download" })}
</button> </button>
{/* Tombol Bagikan */}
<div className="flex flex-row justify-center py-3">
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
<Popover>
<PopoverTrigger asChild>
<button className="w-full flex items-center gap-2">
{/* <Icon icon="oi:share" fontSize={20} /> */}
<p className="text-base font-semibold mb-2">
{t("share", { defaultValue: "Share" })}
</p>
</button>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col">
<h1 className="mb-2">
{t("shareTo", { defaultValue: "Share To" })}
</h1>
<div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1">
{t("destinationEmail", {
defaultValue: "Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter", {
defaultValue: "Press Enter",
})}
/>
</div>
<Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send", { defaultValue: "Send" })}
</Button>
</div>
</PopoverContent>
</Popover>
</div>
<a
className="ml-8 cursor-pointer"
onClick={() =>
handleShare(
"fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
)
}
>
<Icon
icon="brandico:facebook"
height="20"
className="px-auto text-red-600 text-center"
/>
</a>
<a
className="ml-5 cursor-pointer"
onClick={() =>
handleShare(
"tw",
`https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
)
}
>
<Icon
icon="mdi:twitter"
width="23"
className="text-red-600 text-center"
/>
</a>
<a
className="ml-5 cursor-pointer"
onClick={() =>
handleShare(
"wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
)
}
>
<Icon
icon="ri:whatsapp-fill"
width="23"
className="text-red-600 text-center"
/>
</a>
<Popover>
<PopoverTrigger
className="flex justify-end gap-1 cursor-pointer"
asChild
>
<a
className="ml-5 cursor-pointer"
data-toggle="dropdown"
href="#"
aria-expanded="false"
>
<Icon
icon="material-symbols-light:mail"
width="23"
className="text-red-600 text-center"
/>
</a>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col">
<h1 className="mb-2">
{t("shareTo", { defaultValue: "Share To" })}
</h1>
<div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1">
{t("destinationEmail", {
defaultValue: "Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter", {
defaultValue: "Press Enter",
})}
/>
</div>
<Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send", { defaultValue: "Send" })}
</Button>
</div>
</PopoverContent>
</Popover>
</div>
</div> </div>
</div> </div>
</div> </div>
<div className="w-full mb-8"> <div className="w-full mb-8">
{/* Comment */} {/* Comment */}
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]"> <div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600">
<div className="gap-5 flex flex-col px-4 lg:px-14"> <div className="gap-5 flex flex-col px-4 lg:px-14">
<p className="flex items-start text-lg">Berikan Komentar</p> <p className="flex items-start text-lg">
<Textarea placeholder="Type your comments here." className="flex w-full" /> {t("comment", { defaultValue: "Comment" })}
<button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">Kirim</button> </p>
<Textarea
placeholder="Type your comments here."
className="flex w-full pb-12"
onChange={getInputValue}
/>
<button
onClick={() => postData()}
className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1"
>
{t("send", { defaultValue: "Send" })}
</button>
</div>
<div className="border-b-2 border-slate-300 mt-4 w-auto"></div>
<div>
{listSuggestion?.map((data: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:px-14">
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1080}
height={1080}
src={data?.suggestionFrom?.profilePictureUrl}
className="h-12 lg:h-16 w-12 lg:w-16 mr-2"
onError={addDefaultProfile}
alt=""
/>
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{Number(data.suggestionFrom?.roleId) == 2 ||
Number(data.suggestionFrom?.roleId) == 3 ||
Number(data.suggestionFrom?.roleId) == 4
? "HUMAS POLRI"
: data.suggestionFrom?.fullname}
{getPublicLocaleTimestamp(new Date(data.createdAt))}
</p>
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">
{data?.message}
</p>
<div>
<a
style={
Number(data.suggestionFrom?.id) == Number(userId)
? {
display: "none",
}
: {}
}
onClick={() => showInput(`comment-id-${data.id}`)}
className="mr-2"
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("reply", { defaultValue: "Reply" })}
</small>
</a>
{Number(data.suggestionFrom?.id) == Number(userId) ||
Number(userRoleId) == 2 ? (
<a onClick={() => deleteData(data.id)}>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("delete", { defaultValue: "Delete" })}
</small>
</a>
) : (
""
)}
</div>
</div>
</div>
{visibleInput === `comment-id-${data.id}` && (
<div
id={`comment-id-${data.id}`}
className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 "
>
<Textarea
id={`input-comment-${data.id}`}
className="p-4 focus:outline-none focus:border-sky-500"
placeholder={t("enterReply", {
defaultValue: "Enter Reply",
})}
/>
<div className="flex flex-row gap-3">
<a onClick={() => postDataChild(data.id)}>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">
{t("send", { defaultValue: "Send" })}
</small>
</a>
<a onClick={() => showInput(`comment-id-${data.id}`)}>
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("cancel", { defaultValue: "Cancel" })}
</small>
</a>
</div>
</div>
)}
{data.children.length > 0
? data.children?.map((child1: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1080}
height={1080}
src={child1.suggestionFrom?.profilePictureUrl}
onError={addDefaultProfile}
alt=""
className="h-10 lg:h-16 w-10 lg:w-16 mr-2"
/>
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "}
<b>
{Number(child1.suggestionFrom?.roleId) == 2 ||
Number(child1.suggestionFrom?.roleId) == 3 ||
Number(child1.suggestionFrom?.roleId) == 4
? "HUMAS POLRI"
: child1.suggestionFrom?.fullname}
</b>{" "}
{getPublicLocaleTimestamp(
new Date(child1.createdAt)
)}
</p>
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">
{parse(String(child1?.message))}
</p>
<div>
<a
style={
Number(child1.suggestionFrom?.id) ==
Number(userId)
? {
display: "none",
}
: {}
}
onClick={() =>
showInput(`comment-id-${child1.id}`)
}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("reply", { defaultValue: "Reply" })}
</small>
</a>
<a
style={
Number(child1.suggestionFrom?.id) ==
Number(userId)
? {}
: {
display: "none",
}
}
onClick={() => deleteData(child1.id)}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("delete", { defaultValue: "Delete" })}
</small>
</a>
</div>
</div>
</div>
{visibleInput === `comment-id-${child1.id}` && (
<div
id={`comment-id-${child1.id}`}
className="px-4 lg:px-14 pl-28 lg:pl-[200px]"
>
<Textarea
name=""
className="mt-2 "
id={`input-comment-${child1.id}`}
placeholder={t("enterReply", {
defaultValue: "Enter Reply",
})}
/>
<div className="flex flex-row mt-2 gap-3">
<a onClick={() => postDataChild(child1.id)}>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("send", { defaultValue: "Send" })}
</small>
</a>
<a
onClick={() =>
showInput(`comment-id-${child1.id}`)
}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("cancel", { defaultValue: "Cancel" })}
</small>
</a>
</div>
</div>
)}
{child1.children.length > 0
? child1.children?.map((child2: any) => (
<div className="">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1080}
height={1080}
src={
child2.suggestionFrom?.profilePictureUrl
}
className="h-9 lg:h-16 w-9 lg:w-16 mr-2"
onError={addDefaultProfile}
alt=""
/>
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "}
<b>
{Number(
child2.suggestionFrom?.roleId
) == 2 ||
Number(child2.suggestionFrom?.roleId) ==
3 ||
Number(child2.suggestionFrom?.roleId) ==
4
? "HUMAS POLRI"
: child2.suggestionFrom?.fullname}
</b>{" "}
{getPublicLocaleTimestamp(
new Date(child2.createdAt)
)}
</p>
<p className="text-slate-500 text-sm mb-4">
{parse(String(child2?.message))}
</p>
<div>
<a
style={
Number(child2.suggestionFrom?.id) ==
Number(userId)
? {
display: "none",
}
: {}
}
onClick={() =>
showInput(`comment-id-${child2.id}`)
}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("reply", {
defaultValue: "Reply",
})}
</small>
</a>
<a
style={
Number(child2.suggestionFrom?.id) ==
Number(userId)
? {}
: {
display: "none",
}
}
onClick={() => deleteData(child2.id)}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("delete", {
defaultValue: "Delete",
})}
</small>
</a>
</div>
</div>
</div>
{visibleInput === `comment-id-${child2.id}` && (
<div
id={`comment-id-${child2.id}`}
className="px-4 lg:px-14 pl-40 lg:pl-[265px]"
>
<Textarea
name=""
id={`input-comment-${child2.id}`}
className="my-2"
placeholder={t("enterReply", {
defaultValue: "Enter Reply",
})}
/>
<div className="flex flex-row gap-3">
<a
onClick={() => postDataChild(child2.id)}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("send", { defaultValue: "Send" })}
</small>
</a>
<a
onClick={() =>
showInput(`comment-id-${child2.id}`)
}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("cancel", {
defaultValue: "Cancel",
})}
</small>
</a>
</div>
</div>
)}
</div>
))
: ""}
</div>
))
: ""}
</div>
))}
</div> </div>
</div> </div>
{/* Konten Serupa */} {/* Konten Serupa */}
<div className=""> <div className="px-4 lg:px-24">
<NewContent group="satker" type={"similar"} /> <NewContent group="satker" type={"similar"} />
</div> </div>
</div> </div>

View File

@ -56,6 +56,7 @@ export default function FormCollaboration() {
const router = useRouter(); const router = useRouter();
const editor = useRef(null); const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>; type TaskSchema = z.infer<typeof taskSchema>;
const [isSubmitting, setIsSubmitting] = useState(false);
const [taskOutput, setTaskOutput] = useState({ const [taskOutput, setTaskOutput] = useState({
all: false, all: false,
@ -162,6 +163,18 @@ export default function FormCollaboration() {
} }
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
setIsSubmitting(true);
MySwal.fire({
title: "Menyimpan...",
text: "Mohon tunggu sebentar",
allowOutsideClick: false,
allowEscapeKey: false,
didOpen: () => {
Swal.showLoading();
},
});
const requestData = { const requestData = {
title: data.title, title: data.title,
typeId: 11, typeId: 11,
@ -179,6 +192,8 @@ export default function FormCollaboration() {
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
console.log("response", response); console.log("response", response);
Swal.close();
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
text: "Data berhasil disimpan.", text: "Data berhasil disimpan.",
@ -188,6 +203,8 @@ export default function FormCollaboration() {
}).then(() => { }).then(() => {
router.push("/in/supervisor/communications/collaboration"); router.push("/in/supervisor/communications/collaboration");
}); });
setIsSubmitting(false);
}; };
const onSubmit = (data: TaskSchema) => { const onSubmit = (data: TaskSchema) => {

View File

@ -10,8 +10,6 @@ import * as z from "zod";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
getCuratorUser, getCuratorUser,
getTicketingPriority, getTicketingPriority,
@ -19,13 +17,6 @@ import {
} from "@/service/communication/communication"; } from "@/service/communication/communication";
import makeAnimated from "react-select/animated"; import makeAnimated from "react-select/animated";
import Select from "react-select"; import Select from "react-select";
import {
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@radix-ui/react-select";
import { SelectGroup } from "@/components/ui/select";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
const taskSchema = z.object({ const taskSchema = z.object({
@ -45,39 +36,20 @@ interface Option {
} }
const CustomEditor = dynamic( const CustomEditor = dynamic(
() => { () => import("@/components/editor/custom-editor"),
return import("@/components/editor/custom-editor");
},
{ ssr: false } { ssr: false }
); );
export default function FormInternal() { export default function FormInternal() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>; type TaskSchema = z.infer<typeof taskSchema>;
// State for various form fields
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
const [assignmentType, setAssignmentType] = useState("mediahub");
const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<number>(1); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [options, setOptions] = useState<Option[]>([]); const [options, setOptions] = useState<Option[]>([]);
const [ticketPriority, setTicketPriority] = useState([]); const [selectedOption, setSelectedOption] = useState<Option | undefined>();
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
undefined
);
const animatedComponent = makeAnimated();
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [selectedTarget, setSelectedTarget] = useState<number | null>(null); const [selectedTarget, setSelectedTarget] = useState<number | null>(null);
const animatedComponent = makeAnimated();
const [isSubmitting, setIsSubmitting] = useState(false); // ⭐ NEW
const priority = [ const priority = [
{ value: 1, label: "Low" }, { value: 1, label: "Low" },
@ -93,54 +65,27 @@ export default function FormInternal() {
resolver: zodResolver(taskSchema), resolver: zodResolver(taskSchema),
}); });
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value);
setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2);
};
useEffect(() => { useEffect(() => {
getUser(); getUser();
getTicketPriority(); getTicketPriority();
}); }, []);
const handleChange = (e: any) => { const handleChange = (e: any) => {
const selected = e; setSelectedOption(e);
setSelectedOption(selected);
}; };
const formatOptionLabel = (option: Option) => (
<>
<div className="row">
<div className="col">
{option.value} | {option.fullname}
</div>
</div>
<div className="row">
<div className="col">
<b>{option.userLevel}</b>
</div>
</div>
</>
);
async function getTicketPriority() { async function getTicketPriority() {
const res = await getTicketingPriority(); const res = await getTicketingPriority();
if (res?.data !== null) { if (res?.data !== null) {
const rawData = res?.data?.data; const rawData = res?.data?.data;
setTicketPriority(rawData); // optional: setTicketPriority(rawData);
} }
} }
async function getUser() { async function getUser() {
const res = await getCuratorUser(); const res = await getCuratorUser();
if (res?.data !== null) { if (res?.data !== null) {
const rawUser = res?.data?.data?.content; const rawUser = res?.data?.data?.content;
console.log("raw user", rawUser);
const optionArr: Option[] = rawUser.map((option: any) => ({ const optionArr: Option[] = rawUser.map((option: any) => ({
id: option?.id, id: option?.id,
label: option?.username + option?.fullname + option?.userLevel?.name, label: option?.username + option?.fullname + option?.userLevel?.name,
@ -149,12 +94,23 @@ export default function FormInternal() {
userLevel: option?.userLevel?.name, userLevel: option?.userLevel?.name,
userLevelId: option?.userLevel?.id, userLevelId: option?.userLevel?.id,
})); }));
setOptions(optionArr); setOptions(optionArr);
} }
} }
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
setIsSubmitting(true); // ⭐ NEW
MySwal.fire({
title: "Menyimpan...",
text: "Mohon tunggu, data sedang diproses.",
allowOutsideClick: false,
allowEscapeKey: false,
didOpen: () => {
Swal.showLoading();
},
});
try {
const requestData = { const requestData = {
title: data.title, title: data.title,
message: data.message, message: data.message,
@ -167,6 +123,8 @@ export default function FormInternal() {
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
console.log("response", response); console.log("response", response);
Swal.close(); // ⭐ Tutup loading
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
text: "Data berhasil disimpan.", text: "Data berhasil disimpan.",
@ -176,6 +134,17 @@ export default function FormInternal() {
}).then(() => { }).then(() => {
router.push("/in/shared/communication"); router.push("/in/shared/communication");
}); });
} catch (error) {
Swal.close(); // Tutup loading jika error
MySwal.fire({
title: "Error",
text: "Terjadi kesalahan saat menyimpan data.",
icon: "error",
});
console.error("Error saat menyimpan data:", error);
} finally {
setIsSubmitting(false); // Reset state
}
}; };
const onSubmit = (data: TaskSchema) => { const onSubmit = (data: TaskSchema) => {
@ -196,11 +165,9 @@ export default function FormInternal() {
return ( return (
<Card> <Card>
<div className=" px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3"></p>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col justify-center items-center gap-5 mb-5"> <div className="flex flex-col justify-center items-center gap-5 mb-5">
{/* Input Title */}
<div className="w-6/12 space-y-2"> <div className="w-6/12 space-y-2">
<Label> <Label>
Judul<span className="text-red-500">*</span> Judul<span className="text-red-500">*</span>
@ -222,8 +189,8 @@ export default function FormInternal() {
<p className="text-red-400 text-sm">{errors.title.message}</p> <p className="text-red-400 text-sm">{errors.title.message}</p>
)} )}
</div> </div>
<div className="w-6/12"> <div className="w-6/12">
<div className="mt-5">
<Label> <Label>
Priority <span className="text-red-500">*</span> Priority <span className="text-red-500">*</span>
</Label> </Label>
@ -234,17 +201,11 @@ export default function FormInternal() {
setSelectedTarget(selectedOption?.value ?? null) setSelectedTarget(selectedOption?.value ?? null)
} }
placeholder="Pilih" placeholder="Pilih"
styles={{ styles={{ control: (base) => ({ ...base, minHeight: "40px" }) }}
control: (base) => ({
...base,
minHeight: "40px",
}),
}}
/> />
</div> </div>
</div>
<div className="w-6/12"> <div className="w-6/12">
<div className="mt-5">
<Label>Ditunjukan Untuk</Label> <Label>Ditunjukan Untuk</Label>
<Select <Select
options={options} options={options}
@ -252,11 +213,10 @@ export default function FormInternal() {
closeMenuOnSelect={false} closeMenuOnSelect={false}
components={animatedComponent} components={animatedComponent}
onChange={handleChange} onChange={handleChange}
formatOptionLabel={formatOptionLabel}
isMulti={false} isMulti={false}
/> />
</div> </div>
</div>
<div className="w-6/12 mt-5"> <div className="w-6/12 mt-5">
<Label>Narasi Penugasan</Label> <Label>Narasi Penugasan</Label>
<Controller <Controller
@ -270,12 +230,13 @@ export default function FormInternal() {
<p className="text-red-400 text-sm">{errors.message.message}</p> <p className="text-red-400 text-sm">{errors.message.message}</p>
)} )}
</div> </div>
<div className="w-6/12 flex justify-end gap-3 mt-5"> <div className="w-6/12 flex justify-end gap-3 mt-5">
<Button type="button" color="primary" variant="outline"> <Button type="button" color="primary" variant="outline">
Batal Batal
</Button> </Button>
<Button type="submit" color="primary"> <Button type="submit" color="primary" disabled={isSubmitting}>
Submit {isSubmitting ? "Menyimpan..." : "Submit"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -86,35 +86,29 @@ export default function FormAudio() {
type AudioSchema = z.infer<typeof audioSchema>; type AudioSchema = z.infer<typeof audioSchema>;
const params = useParams(); const params = useParams();
const locale = params?.locale; const locale = params?.locale;
const [selectedFileType, setSelectedFileType] = useState("original"); const [selectedFileType, setSelectedFileType] = useState("original");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const [tags, setTags] = useState<string[]>([]);
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const t = useTranslations("Form"); const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null); const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState(""); const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional"); useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor const [editorContent, setEditorContent] = useState("");
const [rewriteEditorContent, setRewriteEditorContent] = useState(""); const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>(""); const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false); const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>(""); const [articleBody, setArticleBody] = useState<string>("");
@ -142,11 +136,12 @@ export default function FormAudio() {
let uploadPersen = 0; let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false); const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [showRewriteEditor, setShowRewriteEditor] = useState(false); const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [fileError, setFileError] = useState<string | null>(null);
type FileWithPreview = File & { preview: string };
const options: Option[] = [ const options: Option[] = [
{ id: "all", label: "SEMUA" }, { id: "all", label: "SEMUA" },
@ -159,13 +154,34 @@ export default function FormAudio() {
const audioRefs = useRef<HTMLAudioElement[]>([]); const audioRefs = useRef<HTMLAudioElement[]>([]);
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
accept: {
"audio/mpeg": [".mp3"],
"audio/wav": [".wav"],
},
maxSize: 100 * 1024 * 1024,
multiple: true, multiple: true,
accept: { "audio/*": [] }, onDrop: (acceptedFiles, fileRejections) => {
onDrop: (acceptedFiles) => { setFileError(null);
if (fileRejections.length > 0) {
const messages = fileRejections
.map((rej) => rej.errors.map((e) => e.message).join(", "))
.join(", ");
setFileError(messages || "File tidak valid");
return;
}
if (acceptedFiles.length === 0) {
setFileError("Wajib upload minimal 1 file mp3 atau wav");
return;
}
const filesWithPreview = acceptedFiles.map((file) => const filesWithPreview = acceptedFiles.map((file) =>
Object.assign(file, { preview: URL.createObjectURL(file) }) Object.assign(file, { preview: URL.createObjectURL(file) })
); );
setFiles((prev) => [...prev, ...filesWithPreview]); setFiles(filesWithPreview);
setValue("files", filesWithPreview, { shouldValidate: true });
}, },
}); });
@ -184,8 +200,13 @@ export default function FormAudio() {
descriptionOri: z.string().optional(), descriptionOri: z.string().optional(),
rewriteDescription: z.string().optional(), rewriteDescription: z.string().optional(),
creatorName: z.string().min(1, { message: "Creator diperlukan" }), creatorName: z.string().min(1, { message: "Creator diperlukan" }),
category: z.string().min(1, { message: "Category diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }), files: z
.array(z.any())
.min(1, { message: "Wajib upload minimal 1 file mp3 atau wav" }),
tags: z
.array(z.string().min(1))
.min(1, { message: "Wajib isi minimal 1 tag" }),
}); });
const { const {
@ -200,6 +221,8 @@ export default function FormAudio() {
description: "", description: "",
descriptionOri: "", descriptionOri: "",
rewriteDescription: "", rewriteDescription: "",
category: "",
tags: [],
}, },
}); });
@ -398,20 +421,18 @@ export default function FormAudio() {
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) { if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault(); e.preventDefault();
const newTag = e.currentTarget.value.trim(); const newTags = [...tags, e.currentTarget.value.trim()];
if (!tags.includes(newTag)) { setTags(newTags);
setTags((prevTags) => [...prevTags, newTag]); // Add new tag setValue("tags", newTags, { shouldValidate: true });
if (inputRef.current) { e.currentTarget.value = "";
inputRef.current.value = ""; // Clear input field
}
}
} }
}; };
const handleRemoveTag = (index: number) => { const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag const newTags = tags.filter((_, i) => i !== index);
setTags(newTags);
setValue("tags", newTags, { shouldValidate: true });
}; };
const handleRemoveImage = (index: number) => { const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
}; };
@ -441,7 +462,7 @@ export default function FormAudio() {
if (findCategory) { if (findCategory) {
// setValue("categoryId", findCategory.id); // setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category setSelectedCategory(findCategory.id);
const response = await getTagsBySubCategoryId(findCategory.id); const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data?.data); setTags(response?.data?.data);
} }
@ -514,7 +535,7 @@ export default function FormAudio() {
tags: string; tags: string;
isYoutube: boolean; isYoutube: boolean;
isInternationalMedia: boolean; isInternationalMedia: boolean;
attachFromScheduleId?: number; // ✅ Tambahkan properti ini attachFromScheduleId?: number;
} = { } = {
...data, ...data,
title: finalTitle, title: finalTitle,
@ -535,7 +556,7 @@ export default function FormAudio() {
let id = Cookies.get("idCreate"); let id = Cookies.get("idCreate");
if (scheduleId !== undefined) { if (scheduleId !== undefined) {
requestData.attachFromScheduleId = Number(scheduleId); // ✅ Tambahkan nilai ini requestData.attachFromScheduleId = Number(scheduleId);
} }
if (id == undefined) { if (id == undefined) {
@ -844,9 +865,16 @@ export default function FormAudio() {
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full space-y-2"> <div className="py-3 w-full space-y-2">
<Label>{t("category", { defaultValue: "Category" })}</Label> <Label>{t("category", { defaultValue: "Category" })}</Label>
<Controller
name="category"
control={control}
rules={{ required: "Category is required" }}
render={({ field, fieldState }) => (
<>
<Select <Select
value={selectedCategory} value={field.value}
onValueChange={(id) => { onValueChange={(id) => {
field.onChange(id);
console.log("Selected Category ID:", id); console.log("Selected Category ID:", id);
setSelectedCategory(id); setSelectedCategory(id);
}} }}
@ -865,6 +893,14 @@ export default function FormAudio() {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{fieldState.error && (
<p className="text-sm text-red-500">
{fieldState.error.message}
</p>
)}
</>
)}
/>
</div> </div>
</div> </div>
<div className="flex flex-row items-center gap-3 py-2"> <div className="flex flex-row items-center gap-3 py-2">
@ -1234,7 +1270,14 @@ export default function FormAudio() {
<Label> <Label>
{t("select-file", { defaultValue: "Select File" })} {t("select-file", { defaultValue: "Select File" })}
</Label> </Label>
<Fragment> <Controller
name="files"
control={control}
rules={{
required: "Wajib upload minimal 1 file mp3 atau wav",
}}
render={({ field, fieldState }) => (
<>
<div {...getRootProps({ className: "dropzone" })}> <div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps({ multiple: true })} /> <input {...getInputProps({ multiple: true })} />
<div className="w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col"> <div className="w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
@ -1250,8 +1293,19 @@ export default function FormAudio() {
</div> </div>
</div> </div>
{/* pesan error dari validasi dropzone */}
{fileError && (
<p className="text-sm text-red-500 mt-1">{fileError}</p>
)}
{/* pesan error dari schema */}
{fieldState.error && (
<p className="text-sm text-red-500 mt-1">
{fieldState.error.message}
</p>
)}
{files.length ? ( {files.length ? (
<Fragment>
<div className="space-y-4 mt-4"> <div className="space-y-4 mt-4">
{files.map((file, idx) => ( {files.map((file, idx) => (
<div <div
@ -1263,10 +1317,7 @@ export default function FormAudio() {
</p> </p>
<audio <audio
controls controls
src={file.preview ?? URL.createObjectURL(file)} src={file.preview}
ref={(el) => {
if (el) audioRefs.current[idx] = el;
}}
onPlay={() => handlePlay(idx)} onPlay={() => handlePlay(idx)}
className="w-full rounded" className="w-full rounded"
> >
@ -1275,17 +1326,25 @@ export default function FormAudio() {
</div> </div>
))} ))}
</div> </div>
) : null}
{files.length > 0 && (
<div className="flex justify-between gap-2 mt-3"> <div className="flex justify-between gap-2 mt-3">
<Button <Button
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={() => {
setFiles([]);
setFileError(null);
setValue("files", [], { shouldValidate: true });
}}
> >
{t("remove-all", { defaultValue: "Remove All" })} {t("remove-all", { defaultValue: "Remove All" })}
</Button> </Button>
</div> </div>
</Fragment> )}
) : null} </>
</Fragment> )}
/>
</div> </div>
</div> </div>
</div> </div>
@ -1327,11 +1386,12 @@ export default function FormAudio() {
onKeyDown={handleAddTag} onKeyDown={handleAddTag}
ref={inputRef} ref={inputRef}
/> />
<div className="mt-3 ">
<div className="mt-3">
{tags.map((tag, index) => ( {tags.map((tag, index) => (
<span <span
key={index} key={index}
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans" className="px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
> >
{tag}{" "} {tag}{" "}
<button <button
@ -1344,7 +1404,14 @@ export default function FormAudio() {
</span> </span>
))} ))}
</div> </div>
{errors.tags && (
<p className="text-sm text-red-500 mt-1">
{errors.tags.message}
</p>
)}
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-3 space-y-2"> <div className="flex flex-col gap-3 space-y-2">
<Label> <Label>

View File

@ -237,7 +237,7 @@ export default function FormVideoDetail() {
format: details?.files[0]?.format, format: details?.files[0]?.format,
}); });
if (details.publishedForObject) { if (details?.publishedForObject) {
const publisherIds = details.publishedForObject.map( const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id (obj: any) => obj.id
); );
@ -453,7 +453,7 @@ export default function FormVideoDetail() {
<div className="py-3 w-full space-y-2"> <div className="py-3 w-full space-y-2">
<Label>{t("category", { defaultValue: "Category" })}</Label> <Label>{t("category", { defaultValue: "Category" })}</Label>
<Select <Select
value={detail?.category.name} // Nilai default berdasarkan detail value={detail?.category.name}
onValueChange={(id) => { onValueChange={(id) => {
console.log("Selected Category:", id); console.log("Selected Category:", id);
setSelectedTarget(id); setSelectedTarget(id);

View File

@ -92,7 +92,6 @@ export default function FormVideo() {
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original"); const [selectedFileType, setSelectedFileType] = useState("original");
const t = useTranslations("Form"); const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
@ -100,19 +99,16 @@ export default function FormVideo() {
const [thumbnail, setThumbnail] = useState<File | null>(null); const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState(""); const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional"); useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor const [editorContent, setEditorContent] = useState("");
const [rewriteEditorContent, setRewriteEditorContent] = useState(""); const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>(""); const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false); const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>(""); const [articleBody, setArticleBody] = useState<string>("");
@ -126,11 +122,9 @@ export default function FormVideo() {
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false); const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [fileError, setFileError] = useState<string | null>(null);
const [showRewriteEditor, setShowRewriteEditor] = useState(false); const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false); const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -165,21 +159,33 @@ export default function FormVideo() {
"video/mp4": [".mp4"], "video/mp4": [".mp4"],
"video/quicktime": [".mov"], "video/quicktime": [".mov"],
}, },
maxSize: MAX_FILE_SIZE, maxSize: 500 * 1024 * 1024,
onDrop: (acceptedFiles: File[]) => { multiple: true,
const filteredFiles = acceptedFiles.filter( onDrop: (acceptedFiles, fileRejections) => {
(file) => setFileError(null);
ACCEPTED_FILE_TYPES.includes(file.type) && file.size <= MAX_FILE_SIZE
if (fileRejections.length > 0) {
const messages = fileRejections
.map((rej) => rej.errors.map((e) => e.message).join(", "))
.join(", ");
setFileError(messages || "File tidak valid");
return;
}
if (acceptedFiles.length === 0) {
setFileError("Wajib upload minimal 1 file video");
return;
}
const filesWithPreview = acceptedFiles.map((file) =>
Object.assign(file, { preview: URL.createObjectURL(file) })
); );
const filesWithPreview = filteredFiles.map((file) => setFiles((prev) => {
Object.assign(file, { const updatedFiles = [...prev, ...filesWithPreview];
preview: URL.createObjectURL(file), setValue("files", updatedFiles, { shouldValidate: true });
}) return updatedFiles;
); });
setFiles(filesWithPreview);
setValue("files", filesWithPreview);
}, },
}); });

View File

@ -668,9 +668,27 @@ export default function FormContestDetail() {
<Checkbox <Checkbox
id={key} id={key}
checked={taskOutput[key as keyof typeof taskOutput]} checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) => onCheckedChange={(value) => {
setTaskOutput({ ...taskOutput, [key]: value }) if (key === "all") {
const newValue = Boolean(value);
setTaskOutput({
all: newValue,
video: newValue,
audio: newValue,
image: newValue,
text: newValue,
});
} else {
setTaskOutput((prev) => {
const updated = { ...prev, [key]: Boolean(value) };
// Update 'all' jika semua sub-checkbox true
const allChecked = Object.entries(updated)
.filter(([k]) => k !== "all")
.every(([_, v]) => v);
return { ...updated, all: allChecked };
});
} }
}}
/> />
<Label htmlFor={key}> <Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)} {key.charAt(0).toUpperCase() + key.slice(1)}
@ -687,9 +705,27 @@ export default function FormContestDetail() {
<Checkbox <Checkbox
id={key} id={key}
checked={unitSelection[key as keyof typeof unitSelection]} checked={unitSelection[key as keyof typeof unitSelection]}
onCheckedChange={(value) => onCheckedChange={(value) => {
setUnitSelection({ ...unitSelection, [key]: value }) if (key === "allUnit") {
const newValue = Boolean(value);
setUnitSelection({
allUnit: newValue,
mabes: newValue,
polda: newValue,
polres: newValue,
satker: newValue,
});
} else {
setUnitSelection((prev) => {
const updated = { ...prev, [key]: Boolean(value) };
// Update 'allUnit' jika semua sub-checkbox true
const allChecked = Object.entries(updated)
.filter(([k]) => k !== "allUnit")
.every(([_, v]) => v);
return { ...updated, allUnit: allChecked };
});
} }
}}
/> />
<Label htmlFor={key}> <Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)} {key.charAt(0).toUpperCase() + key.slice(1)}

View File

@ -41,6 +41,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { toast } from "sonner"; import { toast } from "sonner";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { useTranslations } from "next-intl";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul harus diisi" }), title: z.string().min(1, { message: "Judul harus diisi" }),
@ -59,6 +60,7 @@ export default function FormLiveReport() {
const [endTime, setEndTime] = useState("09:00"); const [endTime, setEndTime] = useState("09:00");
const [scheduleTypeId, setScheduleTypeId] = React.useState<string>("1"); const [scheduleTypeId, setScheduleTypeId] = React.useState<string>("1");
const [youtubeUrl, setYoutubeUrl] = useState(""); const [youtubeUrl, setYoutubeUrl] = useState("");
const t = useTranslations("Form");
const [date, setDate] = React.useState<DateRange | undefined>({ const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(), from: new Date(),
@ -233,7 +235,7 @@ export default function FormLiveReport() {
</div> </div>
<div className="flex flex-col lg:flex-row mb-4 mt-2 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 space-y-2"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">{t("date")}</Label>
<Popover> <Popover>
<PopoverTrigger asChild className="px-0"> <PopoverTrigger asChild className="px-0">
<Button <Button

View File

@ -254,6 +254,9 @@ export default function FormTask() {
}; };
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
try {
loading();
const fileTypeMapping = { const fileTypeMapping = {
all: "1", all: "1",
video: "2", video: "2",
@ -261,7 +264,6 @@ export default function FormTask() {
image: "3", image: "3",
text: "5", text: "5",
}; };
const unitMapping = { const unitMapping = {
allUnit: "0", allUnit: "0",
mabes: "1", mabes: "1",
@ -273,35 +275,18 @@ export default function FormTask() {
.filter((key) => unitSelection[key as keyof typeof unitSelection]) .filter((key) => unitSelection[key as keyof typeof unitSelection])
.map((key) => unitMapping[key as keyof typeof unitMapping]) .map((key) => unitMapping[key as keyof typeof unitMapping])
.join(","); .join(",");
const selectedOutputs = Object.keys(taskOutput) const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) .filter((key) => taskOutput[key as keyof typeof taskOutput])
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) .map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping])
.join(","); .join(",");
const requestData: { const requestData = {
id?: number;
title: string;
assignedToLevel: any;
assignmentPurpose: any;
assignmentTypeId: string;
fileTypeOutput: string;
narration: string;
platformType: string | null;
assignmentMainTypeId: any;
taskType: string;
assignedToRole: string;
broadcastType: string;
attachmentUrl: string[];
} = {
...data, ...data,
// assignmentType,
// assignmentCategory,
assignedToLevel: handlePoldaPolresChange(), assignedToLevel: handlePoldaPolresChange(),
assignmentPurpose: assignmentPurposeString, assignmentPurpose: assignmentPurposeString,
assignedToRole: selectedTarget, assignedToRole: selectedTarget,
taskType: taskType, taskType,
broadcastType: broadcastType, broadcastType,
assignmentMainTypeId: mainType, assignmentMainTypeId: mainType,
assignmentTypeId: type, assignmentTypeId: type,
fileTypeOutput: selectedOutputs, fileTypeOutput: selectedOutputs,
@ -312,50 +297,41 @@ export default function FormTask() {
}; };
const response = await createTask(requestData); const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
const id = response?.data?.data.id; const id = response?.data?.data.id;
loading();
if (imageFiles?.length == 0) {
setIsImageUploadFinish(true);
} else {
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
}
if (videoFiles?.length == 0) { if (imageFiles?.length === 0) setIsImageUploadFinish(true);
setIsVideoUploadFinish(true); else
} else { imageFiles.map(
videoFiles?.map(async (item: any, index: number) => { async (item, idx) =>
await uploadResumableFile(index, String(id), item, "2", "0"); await uploadResumableFile(idx, String(id), item, "1", "0")
});
}
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
} else {
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
}
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
} else {
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
); );
});
} if (videoFiles?.length === 0) setIsVideoUploadFinish(true);
else
videoFiles.map(
async (item, idx) =>
await uploadResumableFile(idx, String(id), item, "2", "0")
);
if (textFiles?.length === 0) setIsTextUploadFinish(true);
else
textFiles.map(
async (item, idx) =>
await uploadResumableFile(idx, String(id), item, "3", "0")
);
if (audioFiles?.length === 0) setIsAudioUploadFinish(true);
else
audioFiles.map(
async (item, idx) =>
await uploadResumableFile(idx, String(id), item, "4", "0")
);
} catch (err) {
console.error("Error saat kirim data:", err);
close(); close();
error("Terjadi kesalahan saat mengirim data.");
}
}; };
const onSubmit = (data: TaskSchema) => { const onSubmit = (data: TaskSchema) => {
@ -403,7 +379,7 @@ export default function FormTask() {
const handleStopRecording = () => { const handleStopRecording = () => {
setIsRecording(false); setIsRecording(false);
setTimer(120); // Reset the timer to 2 minutes for the next recording setTimer(120);
}; };
const addAudioElement = (blob: Blob) => { const addAudioElement = (blob: Blob) => {
@ -413,13 +389,11 @@ export default function FormTask() {
audio.controls = true; audio.controls = true;
document.body.appendChild(audio); document.body.appendChild(audio);
// Convert Blob to File and add preview
const fileWithPreview: FileWithPreview = Object.assign( const fileWithPreview: FileWithPreview = Object.assign(
new File([blob], "voiceNote.webm", { type: "audio/webm" }), new File([blob], "voiceNote.webm", { type: "audio/webm" }),
{ preview: url } { preview: url }
); );
// Add to state
setAudioFile(fileWithPreview); setAudioFile(fileWithPreview);
setAudioFiles((prev) => [...prev, fileWithPreview]); setAudioFiles((prev) => [...prev, fileWithPreview]);
}; };
@ -542,7 +516,6 @@ export default function FormTask() {
setLinks([...links, ""]); setLinks([...links, ""]);
}; };
// Remove a specific link row
const handleRemoveRow = (index: number) => { const handleRemoveRow = (index: number) => {
const updatedLinks = links.filter((_: any, i: any) => i !== index); const updatedLinks = links.filter((_: any, i: any) => i !== index);
setLinks(updatedLinks); setLinks(updatedLinks);
@ -551,7 +524,9 @@ export default function FormTask() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">{t("form-task", { defaultValue: "Form Task" })}</p> <p className="text-lg font-semibold mb-3">
{t("form-task", { defaultValue: "Form Task" })}
</p>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -577,7 +552,11 @@ export default function FormTask() {
</div> </div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("assignment-selection", { defaultValue: "Assignment Selection" })}</Label> <Label>
{t("assignment-selection", {
defaultValue: "Assignment Selection",
})}
</Label>
<Select onValueChange={setSelectedTarget}> <Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Choose" /> <SelectValue placeholder="Choose" />
@ -706,7 +685,9 @@ export default function FormTask() {
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("assigment-type", { defaultValue: "Assigment Type" })} </Label> <Label>
{t("assigment-type", { defaultValue: "Assigment Type" })}{" "}
</Label>
<RadioGroup <RadioGroup
value={taskType} value={taskType}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
@ -720,7 +701,9 @@ export default function FormTask() {
</div> </div>
{/* RadioGroup Assignment Category */} {/* RadioGroup Assignment Category */}
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("type-of-task", { defaultValue: "Type Of Task" })}</Label> <Label>
{t("type-of-task", { defaultValue: "Type Of Task" })}
</Label>
<RadioGroup <RadioGroup
value={type} // State yang dipetakan ke value RadioGroup value={type} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
@ -799,10 +782,14 @@ export default function FormTask() {
)} )}
</div> </div>
<div className="space-y-2.5 mt-5"> <div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">{t("attachment", { defaultValue: "Attachment" })}</Label> <Label htmlFor="attachments">
{t("attachment", { defaultValue: "Attachment" })}
</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label>{t("audio-visual", { defaultValue: "Audio Visual" })}</Label> <Label>
{t("audio-visual", { defaultValue: "Audio Visual" })}
</Label>
<FileUploader <FileUploader
accept={{ accept={{
"video/*": [], "video/*": [],
@ -876,7 +863,9 @@ export default function FormTask() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4 space-y-2"> <div className="mt-4 space-y-2">
<Label className="">{t("news-links", { defaultValue: "News Links" })}</Label> <Label className="">
{t("news-links", { defaultValue: "News Links" })}
</Label>
{links.map((link, index) => ( {links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2"> <div key={index} className="flex items-center gap-2 mt-2">
<Input <Input

View File

@ -1,4 +1,3 @@
import search from "@/app/[locale]/(protected)/app/chat/components/search";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import router from "next/router"; import router from "next/router";

View File

@ -1,4 +1,3 @@
import search from "@/app/[locale]/(protected)/app/chat/components/search";
import { import {
Select, Select,
SelectTrigger, SelectTrigger,

View File

@ -190,7 +190,7 @@ const DetailAudio = () => {
const handleDownload = () => { const handleDownload = () => {
if (downloadProgress === 0) { if (downloadProgress === 0) {
if (!userId) { if (!userId) {
router.push("/auth/login"); router.push("/auth");
} else { } else {
sendActivityLog(2); sendActivityLog(2);
sendActivityLog(3); sendActivityLog(3);

View File

@ -185,7 +185,7 @@ const DetailDocument = () => {
const handleDownload = () => { const handleDownload = () => {
if (downloadProgress === 0) { if (downloadProgress === 0) {
if (!userId) { if (!userId) {
router.push("/auth/login"); router.push("/auth");
} else { } else {
sendActivityLog(2); sendActivityLog(2);
sendActivityLog(3); sendActivityLog(3);

View File

@ -222,7 +222,7 @@ const DetailImage = (data: any) => {
const handleDownload = () => { const handleDownload = () => {
if (downloadProgress === 0) { if (downloadProgress === 0) {
if (!userId) { if (!userId) {
router.push("/auth/login"); router.push("/auth");
} else { } else {
sendActivityLog(2); sendActivityLog(2);
sendActivityLog(3); sendActivityLog(3);
@ -676,7 +676,7 @@ const DetailImage = (data: any) => {
key={size?.label} key={size?.label}
className="items-center flex flex-row gap-2 cursor-pointer" className="items-center flex flex-row gap-2 cursor-pointer"
> >
<input <Input
type="radio" type="radio"
name="size" name="size"
value={size?.label} value={size?.label}

View File

@ -182,7 +182,7 @@ const DetailVideo = () => {
const handleDownload = () => { const handleDownload = () => {
if (downloadProgress === 0) { if (downloadProgress === 0) {
if (!userId) { if (!userId) {
router.push("/auth/login"); router.push("/auth");
} else { } else {
sendActivityLog(2); sendActivityLog(2);
sendActivityLog(3); sendActivityLog(3);
@ -404,7 +404,6 @@ const DetailVideo = () => {
console.log(responseGet?.data?.data); console.log(responseGet?.data?.data);
setListSuggestion(responseGet?.data?.data); setListSuggestion(responseGet?.data?.data);
// Hapus nilai semua input secara manual jika perlu
const inputs = document.querySelectorAll("input"); const inputs = document.querySelectorAll("input");
inputs.forEach((input) => { inputs.forEach((input) => {
input.value = ""; input.value = "";
@ -465,7 +464,6 @@ const DetailVideo = () => {
return ( return (
<> <>
<div className="px-4 md:px-24 py-4"> <div className="px-4 md:px-24 py-4">
{/* Container Utama */}
<div className="rounded-md overflow-hidden md:flex"> <div className="rounded-md overflow-hidden md:flex">
{/* Bagian Kiri */} {/* Bagian Kiri */}
<div className="md:w-3/4"> <div className="md:w-3/4">

View File

@ -1,11 +1,10 @@
import React, { Component } from "react"; import React, { Component } from "react";
import Places from "./Maps"; // Pastikan ini adalah komponen Places import Places from "./Maps";
interface MapHomeProps { interface MapHomeProps {
newLat?: any; newLat?: any;
newLng?: any; newLng?: any;
draggable?: boolean; draggable?: boolean;
setLocation: (location: string) => void; // Pastikan properti ini ada setLocation: (location: string) => void;
} }
class MapHome extends Component<MapHomeProps> { class MapHome extends Component<MapHomeProps> {
@ -20,7 +19,7 @@ class MapHome extends Component<MapHomeProps> {
<Places <Places
center={{ lat: Number(lat), lng: Number(lng) }} center={{ lat: Number(lat), lng: Number(lng) }}
draggable={draggable} draggable={draggable}
onLocationChange={setLocation} // Kirimkan setLocation ke Places onLocationChange={setLocation}
/> />
</div> </div>
); );

View File

@ -14,7 +14,7 @@ Geocode.setApiKey(GoogleMapsAPI);
export default function Places(props: { export default function Places(props: {
center: { lat: number; lng: number }; center: { lat: number; lng: number };
draggable?: boolean; draggable?: boolean;
onLocationChange?: (location: string) => void; // Tambahkan onLocationChange onLocationChange?: (location: string) => void;
}) { }) {
const { isLoaded } = useLoadScript({ const { isLoaded } = useLoadScript({
googleMapsApiKey: GoogleMapsAPI, googleMapsApiKey: GoogleMapsAPI,
@ -31,7 +31,7 @@ export default function Places(props: {
lat={center.lat} lat={center.lat}
lng={center.lng} lng={center.lng}
draggable={draggable} draggable={draggable}
onLocationChange={onLocationChange} // Kirimkan properti onLocationChange onLocationChange={onLocationChange}
/> />
); );
} }
@ -40,7 +40,7 @@ interface MapProps {
lat: number; lat: number;
lng: number; lng: number;
draggable?: boolean; draggable?: boolean;
onLocationChange?: (location: string) => void; // Tambahkan properti ini onLocationChange?: (location: string) => void;
} }
function Map(props: MapProps) { function Map(props: MapProps) {
@ -73,7 +73,7 @@ function Map(props: MapProps) {
console.log(lat, lng); console.log(lat, lng);
getAddressFromLatLong(lat, lng); getAddressFromLatLong(lat, lng);
if (onLocationChange) { if (onLocationChange) {
onLocationChange(`Latitude: ${lat}, Longitude: ${lng}`); // Kirimkan lokasi ke parent melalui onLocationChange onLocationChange(`Latitude: ${lat}, Longitude: ${lng}`);
} }
}; };
@ -85,7 +85,7 @@ function Map(props: MapProps) {
Cookies.set("map_long", `${lng}`, { expires: 1 }); Cookies.set("map_long", `${lng}`, { expires: 1 });
console.log("Address:", address); console.log("Address:", address);
if (onLocationChange) { if (onLocationChange) {
onLocationChange(address); // Kirimkan alamat jika berhasil onLocationChange(address);
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@ -1 +1 @@
export const GoogleMapsAPI = "AIzaSyCOkxoeKykE60L_nM4VS1JYJqBmqy2GA0Q"; export const GoogleMapsAPI = "AIzaSyCuQHorDceMCzlSgrB9AEY5ns8KeriFsME";

345
package-lock.json generated
View File

@ -173,6 +173,7 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -1987,6 +1988,7 @@
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": { "dependencies": {
"string-width": "^5.1.2", "string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0", "string-width-cjs": "npm:string-width@^4.2.0",
@ -2003,6 +2005,7 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -2014,6 +2017,7 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },
@ -2633,166 +2637,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@mui/core-downloads-tracker": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.2.0.tgz",
"integrity": "sha512-d49s7kEgI5iX40xb2YPazANvo7Bx0BLg/MNRwv+7BVpZUzXj1DaVCKlQTDex3gy/0jsCb4w7AY2uH4t4AJvSog==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/material": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.2.0.tgz",
"integrity": "sha512-NTuyFNen5Z2QY+I242MDZzXnFIVIR6ERxo7vntFi9K1wCgSwvIl0HcAO2OOydKqqKApE6omRiYhpny1ZhGuH7Q==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/core-downloads-tracker": "^7.2.0",
"@mui/system": "^7.2.0",
"@mui/types": "^7.4.4",
"@mui/utils": "^7.2.0",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^19.1.0",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^7.2.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@mui/material-pigment-css": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/private-theming": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.2.0.tgz",
"integrity": "sha512-y6N1Yt3T5RMxVFnCh6+zeSWBuQdNDm5/UlM0EAYZzZR/1u+XKJWYQmbpx4e+F+1EpkYi3Nk8KhPiQDi83M3zIw==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/utils": "^7.2.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/styled-engine": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.2.0.tgz",
"integrity": "sha512-yq08xynbrNYcB1nBcW9Fn8/h/iniM3ewRguGJXPIAbHvxEF7Pz95kbEEOAAhwzxMX4okhzvHmk0DFuC5ayvgIQ==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/system": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.2.0.tgz",
"integrity": "sha512-PG7cm/WluU6RAs+gNND2R9vDwNh+ERWxPkqTaiXQJGIFAyJ+VxhyKfzpdZNk0z0XdmBxxi9KhFOpgxjehf/O0A==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/private-theming": "^7.2.0",
"@mui/styled-engine": "^7.2.0",
"@mui/types": "^7.4.4",
"@mui/utils": "^7.2.0",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/types": { "node_modules/@mui/types": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.4.tgz", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.4.tgz",
@ -3381,6 +3225,7 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true, "optional": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@ -5017,78 +4862,6 @@
"url": "https://github.com/sponsors/tannerlinsley" "url": "https://github.com/sponsors/tannerlinsley"
} }
}, },
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@testing-library/dom/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@testing-library/dom/node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/@testing-library/dom/node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"peer": true
},
"node_modules/@testing-library/dom/node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
"react-is": "^17.0.1"
},
"engines": {
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"node_modules/@testing-library/dom/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"peer": true
},
"node_modules/@testing-library/jest-dom": { "node_modules/@testing-library/jest-dom": {
"version": "6.6.3", "version": "6.6.3",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
@ -5266,13 +5039,6 @@
"@types/estree": "*" "@types/estree": "*"
} }
}, },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"peer": true
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -6312,12 +6078,14 @@
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true
}, },
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@ -6330,6 +6098,7 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@ -6373,7 +6142,8 @@
"node_modules/arg": { "node_modules/arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
}, },
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
@ -6824,6 +6594,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@ -7045,6 +6816,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -7172,6 +6944,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@ -7195,6 +6968,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@ -7716,6 +7490,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": { "bin": {
"cssesc": "bin/cssesc" "cssesc": "bin/cssesc"
}, },
@ -8507,7 +8282,8 @@
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
}, },
"node_modules/diff": { "node_modules/diff": {
"version": "5.2.0", "version": "5.2.0",
@ -8532,7 +8308,8 @@
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
}, },
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "3.0.0", "version": "3.0.0",
@ -8681,7 +8458,8 @@
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
}, },
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
@ -8743,16 +8521,11 @@
"url": "https://github.com/sindresorhus/emittery?sponsor=1" "url": "https://github.com/sindresorhus/emittery?sponsor=1"
} }
}, },
"node_modules/emoji-mart": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==",
"peer": true
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "2.0.0", "version": "2.0.0",
@ -9980,6 +9753,7 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1" "signal-exit": "^4.0.1"
@ -10076,6 +9850,7 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -10286,6 +10061,7 @@
"version": "10.3.10", "version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"dev": true,
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^2.3.5", "jackspeak": "^2.3.5",
@ -10307,6 +10083,7 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
}, },
@ -10318,6 +10095,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -10326,6 +10104,7 @@
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@ -11512,6 +11291,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@ -12111,6 +11891,7 @@
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"dev": true,
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
}, },
@ -12785,6 +12566,7 @@
"version": "1.21.7", "version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@ -13331,7 +13113,8 @@
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.390.0", "version": "0.390.0",
@ -13341,16 +13124,6 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@ -14982,6 +14755,7 @@
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
@ -15095,6 +14869,7 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"dependencies": { "dependencies": {
"any-promise": "^1.0.0", "any-promise": "^1.0.0",
"object-assign": "^4.0.1", "object-assign": "^4.0.1",
@ -15435,6 +15210,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -15493,6 +15269,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -15998,6 +15775,7 @@
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@ -16060,6 +15838,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -16068,6 +15847,7 @@
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -16184,6 +15964,7 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"dependencies": { "dependencies": {
"camelcase-css": "^2.0.1" "camelcase-css": "^2.0.1"
}, },
@ -16202,6 +15983,7 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -16236,6 +16018,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -16247,6 +16030,7 @@
"version": "2.8.0", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"dev": true,
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },
@ -16258,6 +16042,7 @@
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -16282,6 +16067,7 @@
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@ -16293,7 +16079,8 @@
"node_modules/postcss-value-parser": { "node_modules/postcss-value-parser": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
}, },
"node_modules/preact": { "node_modules/preact": {
"version": "10.12.1", "version": "10.12.1",
@ -17030,6 +16817,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"dependencies": { "dependencies": {
"pify": "^2.3.0" "pify": "^2.3.0"
} }
@ -17051,6 +16839,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@ -17062,6 +16851,7 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@ -18440,6 +18230,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": { "dependencies": {
"eastasianwidth": "^0.2.0", "eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2", "emoji-regex": "^9.2.2",
@ -18457,6 +18248,7 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@ -18469,12 +18261,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": { "node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}, },
"node_modules/string-width/node_modules/ansi-regex": { "node_modules/string-width/node_modules/ansi-regex": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -18486,6 +18280,7 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },
@ -18657,6 +18452,7 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
}, },
@ -18783,6 +18579,7 @@
"version": "3.35.0", "version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0", "commander": "^4.0.0",
@ -18804,6 +18601,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -18919,6 +18717,7 @@
"version": "3.4.17", "version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"dev": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@ -18963,6 +18762,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -18974,6 +18774,7 @@
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"dependencies": { "dependencies": {
"postcss-value-parser": "^4.0.0", "postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0", "read-cache": "^1.0.0",
@ -19040,6 +18841,7 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"dependencies": { "dependencies": {
"any-promise": "^1.0.0" "any-promise": "^1.0.0"
} }
@ -19048,6 +18850,7 @@
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"dependencies": { "dependencies": {
"thenify": ">= 3.1.0 < 4" "thenify": ">= 3.1.0 < 4"
}, },
@ -19297,7 +19100,8 @@
"node_modules/ts-interface-checker": { "node_modules/ts-interface-checker": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
}, },
"node_modules/ts-morph": { "node_modules/ts-morph": {
"version": "18.0.0", "version": "18.0.0",
@ -19507,7 +19311,7 @@
"version": "5.8.3", "version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -20396,6 +20200,7 @@
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^6.1.0", "ansi-styles": "^6.1.0",
"string-width": "^5.0.1", "string-width": "^5.0.1",
@ -20413,6 +20218,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
"string-width": "^4.1.0", "string-width": "^4.1.0",
@ -20428,12 +20234,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}, },
"node_modules/wrap-ansi-cjs/node_modules/string-width": { "node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@ -20447,6 +20255,7 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -20458,6 +20267,7 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -20469,6 +20279,7 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },