feat:add form schedule:press conference,event,press release, form penugasan
This commit is contained in:
parent
046a9ee5ca
commit
56e5098c34
|
|
@ -0,0 +1,18 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
import FormPressConference from "@/components/form/schedule/press-conference-form";
|
||||
import FormEvent from "@/components/form/schedule/event-form";
|
||||
|
||||
const EventCreatePage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormEvent />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventCreatePage;
|
||||
|
|
@ -4,6 +4,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
|||
import { UploadIcon } from "lucide-react";
|
||||
import PressConferenceTable from "../press-conference/table-presscon/presscon-table";
|
||||
import EventTable from "./table-event/event-table";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
const EventPage = async () => {
|
||||
return (
|
||||
|
|
@ -16,10 +17,12 @@ const EventPage = async () => {
|
|||
Jadwal Event
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Jadwal
|
||||
</Button>
|
||||
<Link href={"/schedule/event/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Jadwal
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
import FormPressConference from "@/components/form/schedule/press-conference-form";
|
||||
|
||||
const PressConCreatePage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormPressConference />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PressConCreatePage;
|
||||
|
|
@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import PressConferenceTable from "./table-presscon/presscon-table";
|
||||
import { UploadIcon } from "lucide-react";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
const PressConferencePage = async () => {
|
||||
return (
|
||||
|
|
@ -15,10 +16,12 @@ const PressConferencePage = async () => {
|
|||
Jadwal Konferensi Pers
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Jadwal
|
||||
</Button>
|
||||
<Link href={"/schedule/press-conference/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Jadwal
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
import FormPressConference from "@/components/form/schedule/press-conference-form";
|
||||
import FormPressRelease from "@/components/form/schedule/pers-release-form";
|
||||
|
||||
const PressReleaseCreatePage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormPressRelease />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PressReleaseCreatePage;
|
||||
|
|
@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { UploadIcon } from "lucide-react";
|
||||
import PressReleaseTable from "../press-conference/table-presscon/presscon-table";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
const PressReleasePage = async () => {
|
||||
return (
|
||||
|
|
@ -15,10 +16,12 @@ const PressReleasePage = async () => {
|
|||
Jadwal Pers Rilis
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Jadwal
|
||||
</Button>
|
||||
<Link href={"/schedule/press-release/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Jadwal
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
|
||||
const TaskCreatePage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormTask />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskCreatePage;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
|
||||
const TaskDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormTask />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDetailPage;
|
||||
|
|
@ -3,6 +3,7 @@ import TaskTable from "./table-task/task-table";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { UploadIcon } from "lucide-react";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
const TaskPage = async () => {
|
||||
return (
|
||||
|
|
@ -14,6 +15,14 @@ const TaskPage = async () => {
|
|||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
Table Penugasan
|
||||
</div>
|
||||
<div>
|
||||
<Link href={"/task/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Penugasan
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export type CompanyData = {
|
|||
import { data } from "./data";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
export const columns: ColumnDef<CompanyData>[] = [
|
||||
{
|
||||
|
|
@ -134,14 +135,12 @@ export const columns: ColumnDef<CompanyData>[] = [
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<a href="/en/task/detail/[id]">
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
</a>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
"use client";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import JoditEditor from "jodit-react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { addDays, format, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import TimePicker from "react-time-picker";
|
||||
import "react-time-picker/dist/TimePicker.css";
|
||||
import "react-clock/dist/Clock.css";
|
||||
import { register } from "module";
|
||||
import MapHome from "@/components/maps/MapHome";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
rankName: z.string().min(1, { message: "Nama Pangkat diperlukan" }),
|
||||
fullName: z.string().min(1, { message: "Nama Lengkap diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
location: z.string().min(1, { message: "Nama Lengkap diperlukan" }),
|
||||
});
|
||||
|
||||
export default function FormEvent() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const editor = useRef(null);
|
||||
const [isLiveStreamingEnabled, setIsLiveStreamingEnabled] = useState(false);
|
||||
type TaskSchema = z.infer<typeof taskSchema>;
|
||||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2024, 0, 1),
|
||||
});
|
||||
|
||||
const handleStartTime = (e: any) => {
|
||||
setStartTime(e.target.value);
|
||||
};
|
||||
|
||||
const handleEndTime = (e: any) => {
|
||||
setEndTime(e.target.value);
|
||||
};
|
||||
// State for various form fields
|
||||
const [output, setOutput] = useState({
|
||||
all: false,
|
||||
video: false,
|
||||
audio: false,
|
||||
image: false,
|
||||
text: false,
|
||||
});
|
||||
|
||||
const [assignmentType, setAssignmentType] = useState("mediahub");
|
||||
const [assignmentCategory, setAssignmentCategory] = useState("publication");
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<TaskSchema>({
|
||||
resolver: zodResolver(taskSchema),
|
||||
defaultValues: {
|
||||
location: "",
|
||||
},
|
||||
});
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const requestData = {
|
||||
...data,
|
||||
output,
|
||||
assignmentType,
|
||||
assignmentCategory,
|
||||
target: selectedTarget,
|
||||
unitSelection,
|
||||
};
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/task");
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
<Card className="w-9/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konferensi Pers</p>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className=" gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul Kegiatan</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Judul"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="mt-5">
|
||||
<Label>Live Streaming</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<p>Aktifkan fitur live streaming</p>
|
||||
<Switch
|
||||
defaultChecked={isLiveStreamingEnabled}
|
||||
color="primary"
|
||||
id="c2"
|
||||
onCheckedChange={(checked) =>
|
||||
setIsLiveStreamingEnabled(checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLiveStreamingEnabled && (
|
||||
<div className="mt-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan ID youtube"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.title.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row mt-3 items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[300px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={1}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div className="">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="col-6">
|
||||
<Input
|
||||
defaultValue="08:00"
|
||||
type="time"
|
||||
onChange={(e) => handleStartTime(e)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<Input
|
||||
defaultValue="09:00"
|
||||
type="time"
|
||||
onChange={(e) => handleEndTime(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{/* Kirim setValue ke MapHome */}
|
||||
<MapHome
|
||||
draggable
|
||||
setLocation={(location) => setValue("location", location)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="location"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
rows={3}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan lokasi"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="invalid-feedback">
|
||||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rankName"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Pangkat"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.rankName?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.rankName.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="fullName"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Lengkap"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.fullName?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.fullName.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="w-3/12">
|
||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
"use client";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import JoditEditor from "jodit-react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { addDays, format, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import TimePicker from "react-time-picker";
|
||||
import "react-time-picker/dist/TimePicker.css";
|
||||
import "react-clock/dist/Clock.css";
|
||||
import { register } from "module";
|
||||
import MapHome from "@/components/maps/MapHome";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
rankName: z.string().min(1, { message: "Nama Pangkat diperlukan" }),
|
||||
fullName: z.string().min(1, { message: "Nama Lengkap diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
location: z.string().min(1, { message: "Nama Lengkap diperlukan" }),
|
||||
});
|
||||
|
||||
export default function FormPressRelease() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const editor = useRef(null);
|
||||
const [isLiveStreamingEnabled, setIsLiveStreamingEnabled] = useState(false);
|
||||
type TaskSchema = z.infer<typeof taskSchema>;
|
||||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2024, 0, 1),
|
||||
});
|
||||
|
||||
const handleStartTime = (e: any) => {
|
||||
setStartTime(e.target.value);
|
||||
};
|
||||
|
||||
const handleEndTime = (e: any) => {
|
||||
setEndTime(e.target.value);
|
||||
};
|
||||
// State for various form fields
|
||||
const [output, setOutput] = useState({
|
||||
all: false,
|
||||
video: false,
|
||||
audio: false,
|
||||
image: false,
|
||||
text: false,
|
||||
});
|
||||
|
||||
const [assignmentType, setAssignmentType] = useState("mediahub");
|
||||
const [assignmentCategory, setAssignmentCategory] = useState("publication");
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<TaskSchema>({
|
||||
resolver: zodResolver(taskSchema),
|
||||
defaultValues: {
|
||||
location: "",
|
||||
},
|
||||
});
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const requestData = {
|
||||
...data,
|
||||
output,
|
||||
assignmentType,
|
||||
assignmentCategory,
|
||||
target: selectedTarget,
|
||||
unitSelection,
|
||||
};
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/task");
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
<Card className="w-9/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konferensi Pers</p>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className=" gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul Kegiatan</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Judul"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="mt-5">
|
||||
<Label>Live Streaming</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<p>Aktifkan fitur live streaming</p>
|
||||
<Switch
|
||||
defaultChecked={isLiveStreamingEnabled}
|
||||
color="primary"
|
||||
id="c2"
|
||||
onCheckedChange={(checked) =>
|
||||
setIsLiveStreamingEnabled(checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLiveStreamingEnabled && (
|
||||
<div className="mt-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan ID youtube"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.title.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row mt-3 items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[300px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={1}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div className="">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="col-6">
|
||||
<Input
|
||||
defaultValue="08:00"
|
||||
type="time"
|
||||
onChange={(e) => handleStartTime(e)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<Input
|
||||
defaultValue="09:00"
|
||||
type="time"
|
||||
onChange={(e) => handleEndTime(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{/* Kirim setValue ke MapHome */}
|
||||
<MapHome
|
||||
draggable
|
||||
setLocation={(location) => setValue("location", location)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="location"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
rows={3}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan lokasi"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="invalid-feedback">
|
||||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Invitation</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="kompas">
|
||||
PT. kompas Cyber Media{" "}
|
||||
</SelectItem>
|
||||
<SelectItem value="trans">
|
||||
PT. Trans Digital Media
|
||||
</SelectItem>
|
||||
<SelectItem value="mnc">MNC</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rankName"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Pangkat"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.rankName?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.rankName.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="fullName"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Lengkap"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.fullName?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.fullName.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="w-3/12">
|
||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
"use client";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import JoditEditor from "jodit-react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { addDays, format, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import TimePicker from "react-time-picker";
|
||||
import "react-time-picker/dist/TimePicker.css";
|
||||
import "react-clock/dist/Clock.css";
|
||||
import { register } from "module";
|
||||
import MapHome from "@/components/maps/MapHome";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
rankName: z.string().min(1, { message: "Nama Pangkat diperlukan" }),
|
||||
fullName: z.string().min(1, { message: "Nama Lengkap diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
location: z.string().min(1, { message: "Nama Lengkap diperlukan" }),
|
||||
});
|
||||
|
||||
export default function FormPressConference() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const editor = useRef(null);
|
||||
const [isLiveStreamingEnabled, setIsLiveStreamingEnabled] = useState(false);
|
||||
type TaskSchema = z.infer<typeof taskSchema>;
|
||||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2024, 0, 1),
|
||||
});
|
||||
|
||||
const handleStartTime = (e: any) => {
|
||||
setStartTime(e.target.value);
|
||||
};
|
||||
|
||||
const handleEndTime = (e: any) => {
|
||||
setEndTime(e.target.value);
|
||||
};
|
||||
// State for various form fields
|
||||
const [output, setOutput] = useState({
|
||||
all: false,
|
||||
video: false,
|
||||
audio: false,
|
||||
image: false,
|
||||
text: false,
|
||||
});
|
||||
|
||||
const [assignmentType, setAssignmentType] = useState("mediahub");
|
||||
const [assignmentCategory, setAssignmentCategory] = useState("publication");
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<TaskSchema>({
|
||||
resolver: zodResolver(taskSchema),
|
||||
defaultValues: {
|
||||
location: "",
|
||||
},
|
||||
});
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const requestData = {
|
||||
...data,
|
||||
output,
|
||||
assignmentType,
|
||||
assignmentCategory,
|
||||
target: selectedTarget,
|
||||
unitSelection,
|
||||
};
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/task");
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
<Card className="w-9/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konferensi Pers</p>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className=" gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul Kegiatan</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Judul"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="mt-5">
|
||||
<Label>Live Streaming</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<p>Aktifkan fitur live streaming</p>
|
||||
<Switch
|
||||
defaultChecked={isLiveStreamingEnabled}
|
||||
color="primary"
|
||||
id="c2"
|
||||
onCheckedChange={(checked) =>
|
||||
setIsLiveStreamingEnabled(checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLiveStreamingEnabled && (
|
||||
<div className="mt-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan ID youtube"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.title.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row mt-3 items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[300px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={1}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div className="">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="col-6">
|
||||
<Input
|
||||
defaultValue="08:00"
|
||||
type="time"
|
||||
onChange={(e) => handleStartTime(e)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<Input
|
||||
defaultValue="09:00"
|
||||
type="time"
|
||||
onChange={(e) => handleEndTime(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{/* Kirim setValue ke MapHome */}
|
||||
<MapHome
|
||||
draggable
|
||||
setLocation={(location) => setValue("location", location)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="location"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
rows={3}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan lokasi"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="invalid-feedback">
|
||||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rankName"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Pangkat"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.rankName?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.rankName.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="fullName"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Lengkap"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.fullName?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.fullName.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="w-3/12">
|
||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
"use client";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import JoditEditor from "jodit-react";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
});
|
||||
|
||||
export default function FormTask() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const editor = useRef(null);
|
||||
type TaskSchema = z.infer<typeof taskSchema>;
|
||||
|
||||
// State for various form fields
|
||||
const [output, setOutput] = useState({
|
||||
all: false,
|
||||
video: false,
|
||||
audio: false,
|
||||
image: false,
|
||||
text: false,
|
||||
});
|
||||
|
||||
const [assignmentType, setAssignmentType] = useState("mediahub");
|
||||
const [assignmentCategory, setAssignmentCategory] = useState("publication");
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<TaskSchema>({
|
||||
resolver: zodResolver(taskSchema),
|
||||
});
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const requestData = {
|
||||
...data,
|
||||
output,
|
||||
assignmentType,
|
||||
assignmentCategory,
|
||||
target: selectedTarget,
|
||||
unitSelection,
|
||||
};
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/task");
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Penugasan</p>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="mt-5">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Semua Pengguna</SelectItem>
|
||||
<SelectItem value="contributor">Kontributor</SelectItem>
|
||||
<SelectItem value="approver">Approver</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mt-5 pt-5 ml-3">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={unitSelection[key as keyof typeof unitSelection]}
|
||||
onCheckedChange={(value) =>
|
||||
setUnitSelection({ ...unitSelection, [key]: value })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<RadioGroup
|
||||
value={assignmentType}
|
||||
onValueChange={setAssignmentType}
|
||||
className="flex flex-wrap gap-6"
|
||||
>
|
||||
<RadioGroupItem value="mediahub" id="mediahub" />
|
||||
<Label htmlFor="mediahub">Mediahub</Label>
|
||||
<RadioGroupItem value="medsos-mediahub" id="medsos-mediahub" />
|
||||
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* RadioGroup Assignment Category */}
|
||||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
value={assignmentCategory}
|
||||
onValueChange={setAssignmentCategory}
|
||||
className="flex flex-wrap gap-6"
|
||||
>
|
||||
<RadioGroupItem value="publication" id="publication" />
|
||||
<Label htmlFor="publication">Publikasi</Label>
|
||||
<RadioGroupItem value="amplification" id="amplification" />
|
||||
<Label htmlFor="amplification">Amplifikasi</Label>
|
||||
<RadioGroupItem value="contra" id="contra" />
|
||||
<Label htmlFor="contra">Kontra</Label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Output Tugas</Label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{Object.keys(output).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={output[key as keyof typeof output]}
|
||||
onCheckedChange={(value) =>
|
||||
setOutput({ ...output, [key]: value })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="naration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.naration?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.naration.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
import Cookies from "js-cookie";
|
||||
import React, { Component } from "react";
|
||||
import Geocode from "react-geocode";
|
||||
import Autocomplete from "react-google-autocomplete";
|
||||
import {
|
||||
GoogleMap,
|
||||
InfoWindow,
|
||||
Marker,
|
||||
withGoogleMap,
|
||||
withScriptjs,
|
||||
} from "react-google-maps";
|
||||
import { GoogleMapsAPI } from "./client-config";
|
||||
|
||||
Geocode.setApiKey(GoogleMapsAPI);
|
||||
Geocode.enableDebug();
|
||||
|
||||
class Map extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
address: "",
|
||||
city: "",
|
||||
area: "",
|
||||
state: "",
|
||||
mapPosition: {
|
||||
lat: this.props.center.lat,
|
||||
lng: this.props.center.lng,
|
||||
},
|
||||
markerPosition: {
|
||||
lat: this.props.center.lat,
|
||||
lng: this.props.center.lng,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current address from the default map position and set those values in the state
|
||||
*/
|
||||
componentDidMount() {
|
||||
Geocode.fromLatLng(
|
||||
this.state.mapPosition.lat,
|
||||
this.state.mapPosition.lng,
|
||||
).then(
|
||||
(response) => {
|
||||
const address = response.results[0].formatted_address;
|
||||
const addressArray = response.results[0].address_components;
|
||||
const city = this.getCity(addressArray);
|
||||
const area = this.getArea(addressArray);
|
||||
const state = this.getState(addressArray);
|
||||
|
||||
console.log("city", city, area, state);
|
||||
|
||||
this.setState({
|
||||
address: address || "",
|
||||
area: area || "",
|
||||
city: city || "",
|
||||
state: state || "",
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component should only update ( meaning re-render ), when the user selects the address, or drags the pin
|
||||
*
|
||||
* @param nextProps
|
||||
* @param nextState
|
||||
* @return {boolean}
|
||||
*/
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (
|
||||
this.state.markerPosition.lat !== this.props.center.lat ||
|
||||
this.state.address !== nextState.address ||
|
||||
this.state.city !== nextState.city ||
|
||||
this.state.area !== nextState.area ||
|
||||
this.state.state !== nextState.state
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.props.center.lat == nextProps.center.lat) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the city and set the city input value to the one selected
|
||||
*
|
||||
* @param addressArray
|
||||
* @return {string}
|
||||
*/
|
||||
getCity = (addressArray) => {
|
||||
let city = "";
|
||||
|
||||
for (const element of addressArray) {
|
||||
if (
|
||||
element.types[0] &&
|
||||
element.types[0] == "administrative_area_level_2"
|
||||
) {
|
||||
city = element.long_name;
|
||||
return city;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get the area and set the area input value to the one selected
|
||||
*
|
||||
* @param addressArray
|
||||
* @return {string}
|
||||
*/
|
||||
getArea = (addressArray) => {
|
||||
let area = "";
|
||||
|
||||
for (const element of addressArray) {
|
||||
if (element.types[0]) {
|
||||
for (let j = 0; j < element.types.length; j++) {
|
||||
if (
|
||||
element.types[j] == "sublocality_level_1" ||
|
||||
element.types[j] == "locality"
|
||||
) {
|
||||
area = element.long_name;
|
||||
return area;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get the address and set the address input value to the one selected
|
||||
*
|
||||
* @param addressArray
|
||||
* @return {string}
|
||||
*/
|
||||
getState = (addressArray) => {
|
||||
let state = "";
|
||||
|
||||
for (let i = 0; i < addressArray.length; i++) {
|
||||
for (const element of addressArray) {
|
||||
if (
|
||||
element.types[0] &&
|
||||
element.types[0] == "administrative_area_level_1"
|
||||
) {
|
||||
state = element.long_name;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* And function for city,state and address input
|
||||
* @param event
|
||||
*/
|
||||
onChange = (event) => {
|
||||
this.setState({
|
||||
[event.target.name]: event.target.value,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* This Event triggers when the marker window is closed
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
onInfoWindowClose = () => {};
|
||||
/**
|
||||
* When the marker is dragged you get the lat and long using the functions available from event object.
|
||||
* Use geocode to get the address, city, area and state from the lat and lng positions.
|
||||
* And then set those values in the state.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
onMarkerDragEnd = (event) => {
|
||||
const newLat = event.latLng.lat();
|
||||
const newLng = event.latLng.lng();
|
||||
|
||||
Geocode.fromLatLng(newLat, newLng).then(
|
||||
(response) => {
|
||||
const address = response.results[0].formatted_address;
|
||||
const addressArray = response.results[0].address_components;
|
||||
const city = this.getCity(addressArray);
|
||||
const area = this.getArea(addressArray);
|
||||
const state = this.getState(addressArray);
|
||||
|
||||
this.setState({
|
||||
address: address || "",
|
||||
area: area || "",
|
||||
city: city || "",
|
||||
state: state || "",
|
||||
markerPosition: {
|
||||
lat: newLat,
|
||||
lng: newLng,
|
||||
},
|
||||
mapPosition: {
|
||||
lat: newLat,
|
||||
lng: newLng,
|
||||
},
|
||||
});
|
||||
Cookies.set("map_lat", `${newLat}`, {
|
||||
expires: 1
|
||||
});
|
||||
Cookies.set("map_long", `${newLng}`, {
|
||||
expires: 1
|
||||
});
|
||||
$(".input-location-schedule").val(address);
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
},
|
||||
);
|
||||
};
|
||||
/**
|
||||
* When the user types an address in the search box
|
||||
* @param place
|
||||
*/
|
||||
onPlaceSelected = (place) => {
|
||||
console.log("plc", place);
|
||||
const address = place.formatted_address;
|
||||
const addressArray = place.address_components;
|
||||
const city = this.getCity(addressArray);
|
||||
const area = this.getArea(addressArray);
|
||||
const state = this.getState(addressArray);
|
||||
const latValue = place.geometry.location.lat();
|
||||
const lngValue = place.geometry.location.lng();
|
||||
|
||||
// Set these values in the state.
|
||||
this.setState({
|
||||
address: address || "",
|
||||
area: area || "",
|
||||
city: city || "",
|
||||
state: state || "",
|
||||
markerPosition: {
|
||||
lat: latValue,
|
||||
lng: lngValue,
|
||||
},
|
||||
mapPosition: {
|
||||
lat: latValue,
|
||||
lng: lngValue,
|
||||
},
|
||||
});
|
||||
Cookies.set("map_lat", `${latValue}`, {
|
||||
expires: 1
|
||||
});
|
||||
Cookies.set("map_long", `${lngValue}`, {
|
||||
expires: 1
|
||||
});
|
||||
$(".input-location-schedule").val(address);
|
||||
};
|
||||
render() {
|
||||
const AsyncMap = withScriptjs(
|
||||
withGoogleMap(() => (
|
||||
<GoogleMap
|
||||
google={this.props.google}
|
||||
defaultZoom={this.props.zoom}
|
||||
defaultCenter={{
|
||||
lat: this.state.mapPosition.lat,
|
||||
lng: this.state.mapPosition.lng,
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "40px",
|
||||
paddingLeft: "16px",
|
||||
marginTop: "2px",
|
||||
marginBottom: "500px",
|
||||
}}
|
||||
onPlaceSelected={this.onPlaceSelected}
|
||||
options={{
|
||||
types: ["geocode"],
|
||||
}}
|
||||
/>
|
||||
{/* InfoWindow on top of marker */}
|
||||
<InfoWindow
|
||||
onClose={this.onInfoWindowClose}
|
||||
position={{
|
||||
lat: this.state.markerPosition.lat + 0.0018,
|
||||
lng: this.state.markerPosition.lng,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{this.state.address}
|
||||
</span>
|
||||
</div>
|
||||
</InfoWindow>
|
||||
{/* Marker */}
|
||||
<Marker
|
||||
google={this.props.google}
|
||||
name="Dolores park"
|
||||
draggable={this.props.draggable}
|
||||
onDragEnd={this.onMarkerDragEnd}
|
||||
position={{
|
||||
lat: this.state.markerPosition.lat,
|
||||
lng: this.state.markerPosition.lng,
|
||||
}}
|
||||
/>
|
||||
<Marker />
|
||||
{/* For Auto complete Search Box */}
|
||||
</GoogleMap>
|
||||
)),
|
||||
);
|
||||
|
||||
let map;
|
||||
|
||||
if (this.props.center.lat == undefined) {
|
||||
map = (
|
||||
<div
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
map = (
|
||||
<div>
|
||||
{/* <div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="">Address</label>
|
||||
<input type="text" name="address" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.address }/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="">All Data</label>
|
||||
<input type="text" name="address" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.markerPosition.lat + ";" + this.state.markerPosition.lng }/>
|
||||
</div>
|
||||
</div> */}
|
||||
<AsyncMap
|
||||
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${GoogleMapsAPI}&libraries=places`}
|
||||
loadingElement={
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
containerElement={
|
||||
<div
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
mapElement={
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
export default Map;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
import Geocode from "react-geocode";
|
||||
import { GoogleMap, Marker, useLoadScript } from "@react-google-maps/api";
|
||||
import { GoogleMapsAPI } from "./client-config";
|
||||
|
||||
Geocode.setApiKey(GoogleMapsAPI);
|
||||
|
||||
interface MapProps {
|
||||
lat: number;
|
||||
lng: number;
|
||||
draggable?: boolean;
|
||||
onLocationChange?: (location: string) => void; // Tambahkan onLocationChange
|
||||
}
|
||||
|
||||
function Map({ lat, lng, draggable, onLocationChange }: MapProps) {
|
||||
const containerStyle = { width: "100%", height: "400px" };
|
||||
const [selected, setSelected] = React.useState<{
|
||||
lat: number;
|
||||
lng: number;
|
||||
} | null>(null);
|
||||
|
||||
const onMarkerDragEnd = async (e: google.maps.MapMouseEvent) => {
|
||||
const lat = e.latLng?.lat() ?? 0;
|
||||
const lng = e.latLng?.lng() ?? 0;
|
||||
|
||||
try {
|
||||
const response = await Geocode.fromLatLng(lat.toString(), lng.toString());
|
||||
const address = response.results[0].formatted_address;
|
||||
|
||||
setSelected({ lat, lng });
|
||||
if (onLocationChange) {
|
||||
onLocationChange(address); // Panggil callback jika tersedia
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GoogleMap
|
||||
zoom={10}
|
||||
center={selected || { lat, lng }}
|
||||
mapContainerStyle={containerStyle}
|
||||
>
|
||||
<Marker
|
||||
draggable={draggable}
|
||||
position={selected || { lat, lng }}
|
||||
onDragEnd={onMarkerDragEnd}
|
||||
/>
|
||||
</GoogleMap>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import React, { Component } from "react";
|
||||
import Places from "./Maps"; // Pastikan ini adalah komponen Places
|
||||
|
||||
interface MapHomeProps {
|
||||
newLat?: any;
|
||||
newLng?: any;
|
||||
draggable?: boolean;
|
||||
setLocation: (location: string) => void; // Pastikan properti ini ada
|
||||
}
|
||||
|
||||
class MapHome extends Component<MapHomeProps> {
|
||||
render() {
|
||||
const { newLat, newLng, draggable, setLocation } = this.props;
|
||||
|
||||
const lat = newLat || -6.2393033;
|
||||
const lng = newLng || 106.8013579;
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "10px", marginTop: "8px" }}>
|
||||
<Places
|
||||
center={{ lat: Number(lat), lng: Number(lng) }}
|
||||
draggable={draggable}
|
||||
onLocationChange={setLocation} // Kirimkan setLocation ke Places
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MapHome;
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
import "@reach/combobox/styles.css";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxInput,
|
||||
ComboboxList,
|
||||
ComboboxOption,
|
||||
ComboboxPopover,
|
||||
} from "@reach/combobox";
|
||||
import { GoogleMap, Marker, useLoadScript } from "@react-google-maps/api";
|
||||
import Cookies from "js-cookie";
|
||||
import { useEffect, useState } from "react";
|
||||
import usePlacesAutocomplete, {
|
||||
getGeocode,
|
||||
getLatLng,
|
||||
} from "use-places-autocomplete";
|
||||
import { GoogleMapsAPI } from "./client-config";
|
||||
import Geocode from "react-geocode";
|
||||
|
||||
Geocode.setApiKey(GoogleMapsAPI);
|
||||
|
||||
export default function Places(props: {
|
||||
center: { lat: number; lng: number };
|
||||
draggable?: boolean;
|
||||
onLocationChange?: (location: string) => void; // Tambahkan onLocationChange
|
||||
}) {
|
||||
const { isLoaded } = useLoadScript({
|
||||
googleMapsApiKey: GoogleMapsAPI,
|
||||
libraries: ["places"],
|
||||
language: "id",
|
||||
});
|
||||
|
||||
const { center, draggable, onLocationChange } = props;
|
||||
|
||||
if (!isLoaded) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<Map
|
||||
lat={center.lat}
|
||||
lng={center.lng}
|
||||
draggable={draggable}
|
||||
onLocationChange={onLocationChange} // Kirimkan properti onLocationChange
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface MapProps {
|
||||
lat: number;
|
||||
lng: number;
|
||||
draggable?: boolean;
|
||||
onLocationChange?: (location: string) => void; // Tambahkan properti ini
|
||||
}
|
||||
|
||||
function Map(props: MapProps) {
|
||||
const containerStyle = {
|
||||
width: "100%",
|
||||
height: "400px",
|
||||
};
|
||||
|
||||
const center = {
|
||||
lat: -6.1754,
|
||||
lng: 106.8272,
|
||||
};
|
||||
|
||||
const [selected, setSelected] = useState<{ lat: number; lng: number } | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const { lat, lng, draggable, onLocationChange } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (lat !== undefined && lng !== undefined) {
|
||||
setSelected({ lat, lng });
|
||||
getAddressFromLatLong(lat, lng);
|
||||
}
|
||||
}, [lat, lng]);
|
||||
|
||||
const onMarkerDragEnd = (e: google.maps.MapMouseEvent) => {
|
||||
const lat = e.latLng?.lat() ?? 0;
|
||||
const lng = e.latLng?.lng() ?? 0;
|
||||
console.log(lat, lng);
|
||||
getAddressFromLatLong(lat, lng);
|
||||
if (onLocationChange) {
|
||||
onLocationChange(`Latitude: ${lat}, Longitude: ${lng}`); // Kirimkan lokasi ke parent melalui onLocationChange
|
||||
}
|
||||
};
|
||||
|
||||
async function getAddressFromLatLong(lat: number, lng: number) {
|
||||
try {
|
||||
const response = await Geocode.fromLatLng(lat.toString(), lng.toString());
|
||||
const address = response.results[0].formatted_address;
|
||||
Cookies.set("map_lat", `${lat}`, { expires: 1 });
|
||||
Cookies.set("map_long", `${lng}`, { expires: 1 });
|
||||
console.log("Address:", address);
|
||||
if (onLocationChange) {
|
||||
onLocationChange(address); // Kirimkan alamat jika berhasil
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<PlacesAutocomplete setSelected={setSelected} />
|
||||
</div>
|
||||
<GoogleMap
|
||||
zoom={selected == null ? 10 : 15}
|
||||
center={selected == null ? center : selected}
|
||||
mapContainerStyle={containerStyle}
|
||||
>
|
||||
{selected && (
|
||||
<Marker
|
||||
draggable={draggable}
|
||||
position={selected}
|
||||
onDragEnd={onMarkerDragEnd}
|
||||
/>
|
||||
)}
|
||||
</GoogleMap>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface PlacesAutocompleteProps {
|
||||
setSelected: (coords: { lat: number; lng: number }) => void;
|
||||
}
|
||||
|
||||
function PlacesAutocomplete({ setSelected }: PlacesAutocompleteProps) {
|
||||
const {
|
||||
ready,
|
||||
value,
|
||||
setValue,
|
||||
suggestions: { status, data },
|
||||
clearSuggestions,
|
||||
} = usePlacesAutocomplete();
|
||||
|
||||
const handleSelect = async (address: string) => {
|
||||
setValue(address, false);
|
||||
clearSuggestions();
|
||||
|
||||
try {
|
||||
const results = await getGeocode({ address });
|
||||
const { lat, lng } = await getLatLng(results[0]);
|
||||
|
||||
setSelected({ lat, lng });
|
||||
console.log("Selected Lat/Lng:", { lat, lng });
|
||||
Cookies.set("map_lat", `${lat}`, { expires: 1 });
|
||||
Cookies.set("map_long", `${lng}`, { expires: 1 });
|
||||
} catch (error) {
|
||||
console.error("Error fetching coordinates:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Combobox onSelect={handleSelect}>
|
||||
<ComboboxInput
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
disabled={!ready}
|
||||
placeholder="Cari Alamat"
|
||||
style={{ width: "100%" }}
|
||||
className="border"
|
||||
height={20}
|
||||
/>
|
||||
<ComboboxPopover>
|
||||
<ComboboxList>
|
||||
{status === "OK" &&
|
||||
data.map(({ place_id, description }) => (
|
||||
<ComboboxOption key={place_id} value={description} />
|
||||
))}
|
||||
</ComboboxList>
|
||||
</ComboboxPopover>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const GoogleMapsAPI = "AIzaSyCOkxoeKykE60L_nM4VS1JYJqBmqy2GA0Q";
|
||||
|
|
@ -49,6 +49,8 @@
|
|||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@reach/combobox": "^0.18.0",
|
||||
"@react-google-maps/api": "^2.20.3",
|
||||
"@south-paw/react-vector-maps": "^3.2.0",
|
||||
"@tanstack/react-table": "^8.19.2",
|
||||
"@types/cleave.js": "^1.4.12",
|
||||
|
|
@ -73,6 +75,7 @@
|
|||
"geojson": "^0.5.0",
|
||||
"google-map-react": "^2.2.1",
|
||||
"input-otp": "^1.2.4",
|
||||
"jodit-react": "^4.1.2",
|
||||
"jotai": "^2.9.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"leaflet": "^1.9.4",
|
||||
|
|
@ -92,6 +95,7 @@
|
|||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-geocode": "^0.2.3",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-leaflet": "^4.2.1",
|
||||
|
|
@ -99,23 +103,29 @@
|
|||
"react-resizable-panels": "^2.0.19",
|
||||
"react-select": "^5.8.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-time-picker": "^7.0.0",
|
||||
"recharts": "^2.12.7",
|
||||
"rtl-detect": "^1.1.2",
|
||||
"sharp": "^0.33.4",
|
||||
"sonner": "^1.5.0",
|
||||
"sweetalert2": "^11.10.5",
|
||||
"sweetalert2-react-content": "^5.0.7",
|
||||
"swiper": "^11.1.4",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-places-autocomplete": "^4.0.1",
|
||||
"vaul": "^0.9.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@next/bundle-analyzer": "^15.0.3",
|
||||
"@types/jquery": "^3.5.32",
|
||||
"@types/leaflet": "^1.9.12",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-geocode": "^0.2.4",
|
||||
"@types/rtl-detect": "^1.0.3",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
|
|
@ -737,6 +747,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz",
|
||||
"integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ=="
|
||||
},
|
||||
"node_modules/@googlemaps/markerclusterer": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
|
||||
"integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"supercluster": "^8.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "1.7.19",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz",
|
||||
|
|
@ -2488,6 +2507,137 @@
|
|||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
|
||||
},
|
||||
"node_modules/@reach/auto-id": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.18.0.tgz",
|
||||
"integrity": "sha512-XwY1IwhM7mkHZFghhjiqjQ6dstbOdpbFLdggeke75u8/8icT8uEHLbovFUgzKjy9qPvYwZIB87rLiR8WdtOXCg==",
|
||||
"dependencies": {
|
||||
"@reach/utils": "0.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/combobox": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/combobox/-/combobox-0.18.0.tgz",
|
||||
"integrity": "sha512-x60PiPOIB4azeyh+FZ/svh0kXZRCneGCXVLL6htWs1VmaKq+TWR/48V03yQX5cSKjvRM8UFDVn47mpcg5ZSFtg==",
|
||||
"dependencies": {
|
||||
"@reach/auto-id": "0.18.0",
|
||||
"@reach/descendants": "0.18.0",
|
||||
"@reach/polymorphic": "0.18.0",
|
||||
"@reach/popover": "0.18.0",
|
||||
"@reach/portal": "0.18.0",
|
||||
"@reach/utils": "0.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/descendants": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.18.0.tgz",
|
||||
"integrity": "sha512-GXUxnM6CfrX5URdnipPIl3Tlc6geuz4xb4n61y4tVWXQX1278Ra9Jz9DMRN8x4wheHAysvrYwnR/SzAlxQzwtA==",
|
||||
"dependencies": {
|
||||
"@reach/utils": "0.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/observe-rect": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz",
|
||||
"integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ=="
|
||||
},
|
||||
"node_modules/@reach/polymorphic": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/polymorphic/-/polymorphic-0.18.0.tgz",
|
||||
"integrity": "sha512-N9iAjdMbE//6rryZZxAPLRorzDcGBnluf7YQij6XDLiMtfCj1noa7KyLpEc/5XCIB/EwhX3zCluFAwloBKdblA==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/popover": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.18.0.tgz",
|
||||
"integrity": "sha512-mpnWWn4w74L2U7fcneVdA6Fz3yKWNdZIRMoK8s6H7F8U2dLM/qN7AjzjEBqi6LXKb3Uf1ge4KHSbMixW0BygJQ==",
|
||||
"dependencies": {
|
||||
"@reach/polymorphic": "0.18.0",
|
||||
"@reach/portal": "0.18.0",
|
||||
"@reach/rect": "0.18.0",
|
||||
"@reach/utils": "0.18.0",
|
||||
"tabbable": "^5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/portal": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.18.0.tgz",
|
||||
"integrity": "sha512-TImozRapd576ofRk30Le2L3lRTFXF1p47B182wnp5eMTdZa74JX138BtNGEPJFOyrMaVmguVF8SSwZ6a0fon1Q==",
|
||||
"dependencies": {
|
||||
"@reach/utils": "0.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/rect": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.18.0.tgz",
|
||||
"integrity": "sha512-Xk8urN4NLn3F70da/DtByMow83qO6DF6vOxpLjuDBqud+kjKgxAU9vZMBSZJyH37+F8mZinRnHyXtlLn5njQOg==",
|
||||
"dependencies": {
|
||||
"@reach/observe-rect": "1.2.0",
|
||||
"@reach/utils": "0.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@reach/utils": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.18.0.tgz",
|
||||
"integrity": "sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || 17.x",
|
||||
"react-dom": "^16.8.0 || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-google-maps/api": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.3.tgz",
|
||||
"integrity": "sha512-ndXC8nZDPT78nCceZnftGSvA/iVhwx2XhlfEYaoUy2biGelhrE2vDzjyTuZhb4RV+bVYpd4LkIf3hzyxAFd+Qg==",
|
||||
"dependencies": {
|
||||
"@googlemaps/js-api-loader": "1.16.8",
|
||||
"@googlemaps/markerclusterer": "2.5.3",
|
||||
"@react-google-maps/infobox": "2.20.0",
|
||||
"@react-google-maps/marker-clusterer": "2.20.0",
|
||||
"@types/google.maps": "3.58.1",
|
||||
"invariant": "2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18",
|
||||
"react-dom": "^16.8 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-google-maps/infobox": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz",
|
||||
"integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ=="
|
||||
},
|
||||
"node_modules/@react-google-maps/marker-clusterer": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz",
|
||||
"integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw=="
|
||||
},
|
||||
"node_modules/@react-leaflet/core": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
|
||||
|
|
@ -2716,6 +2866,11 @@
|
|||
"integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/google.maps": {
|
||||
"version": "3.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
|
||||
"integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ=="
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
|
||||
|
|
@ -2724,6 +2879,15 @@
|
|||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jquery": {
|
||||
"version": "3.5.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz",
|
||||
"integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/sizzle": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-cookie": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
|
||||
|
|
@ -2814,6 +2978,12 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-geocode": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-geocode/-/react-geocode-0.2.4.tgz",
|
||||
"integrity": "sha512-vkDID5kZwYRplECuUtAbPnRN9XyrzE+9lxl0VBV+h7pFDOc6hn+0xieswO7Wrzne/J9HstA3Nlb6OeMuuQxjuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react-syntax-highlighter": {
|
||||
"version": "15.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
|
||||
|
|
@ -2836,6 +3006,12 @@
|
|||
"integrity": "sha512-qpstuHivwg/HoXxRrBo5/r/OVx5M2SkqJpVu2haasdLctt+jMGHWjqdbI0LL7Rk2wRmN/UHdHK4JZg9RUMcvKA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/sizzle": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz",
|
||||
"integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
||||
|
|
@ -3010,6 +3186,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@wojtekmaj/date-utils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz",
|
||||
"integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/date-utils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
|
|
@ -3360,6 +3544,15 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/autobind-decorator": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz",
|
||||
"integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==",
|
||||
"engines": {
|
||||
"node": ">=8.10",
|
||||
"npm": ">=6.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
|
|
@ -4460,6 +4653,14 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-element-overflow": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-element-overflow/-/detect-element-overflow-1.4.2.tgz",
|
||||
"integrity": "sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/detect-element-overflow?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
|
|
@ -5421,8 +5622,7 @@
|
|||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
|
|
@ -5785,6 +5985,17 @@
|
|||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/get-user-locale": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.2.tgz",
|
||||
"integrity": "sha512-O2GWvQkhnbDoWFUJfaBlDIKUEdND8ATpBXD6KXcbhxlfktyD/d8w6mkzM/IlQEqGZAMz/PW6j6Hv53BiigKLUQ==",
|
||||
"dependencies": {
|
||||
"mem": "^8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/git-up": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz",
|
||||
|
|
@ -7254,6 +7465,26 @@
|
|||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jodit": {
|
||||
"version": "4.2.47",
|
||||
"resolved": "https://registry.npmjs.org/jodit/-/jodit-4.2.47.tgz",
|
||||
"integrity": "sha512-3dSdV+dUjwbuNKhn2M6hs8F7qkiufBPJFMB6YVJWbja7RqkQHGtrCGkpZJ1PH16chUgET2Yi+LVOonWyf/nV8g==",
|
||||
"dependencies": {
|
||||
"autobind-decorator": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jodit-react": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jodit-react/-/jodit-react-4.1.2.tgz",
|
||||
"integrity": "sha512-Hs1evpM1IK5zvy/5m5Gk819L8aC+9EmEdQvCoLHVUr/R3vtH4nYFD6wsMRj3ur3J4ZHhaSBjt0N3R7ggwP405Q==",
|
||||
"dependencies": {
|
||||
"jodit": "^4.2.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "5.9.6",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
|
||||
|
|
@ -7387,6 +7618,11 @@
|
|||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/kdbush": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
|
||||
"integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
@ -7569,6 +7805,25 @@
|
|||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/make-event-props": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz",
|
||||
"integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/make-event-props?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/map-age-cleaner": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
|
||||
"integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
|
||||
"dependencies": {
|
||||
"p-defer": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-extensions": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
|
||||
|
|
@ -8222,6 +8477,21 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mem": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz",
|
||||
"integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==",
|
||||
"dependencies": {
|
||||
"map-age-cleaner": "^0.1.3",
|
||||
"mimic-fn": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/mem?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
|
|
@ -9047,6 +9317,14 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
|
||||
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
|
@ -9602,6 +9880,14 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-defer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
||||
"integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
|
|
@ -10203,6 +10489,29 @@
|
|||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-clock": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-clock/-/react-clock-5.1.0.tgz",
|
||||
"integrity": "sha512-DKmr29VOK6M8wpbzGUZZa9PwGnG9uC6QXtDLwGwcc2r3vdS/HxNhf5xMMjudXLk7m096mNJQf7AgfjiDpzAYYw==",
|
||||
"dependencies": {
|
||||
"@wojtekmaj/date-utils": "^1.5.0",
|
||||
"clsx": "^2.0.0",
|
||||
"get-user-locale": "^2.2.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/react-clock?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-day-picker": {
|
||||
"version": "8.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
|
||||
|
|
@ -10244,6 +10553,44 @@
|
|||
"react": ">= 16.8 || 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fit": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-fit/-/react-fit-2.0.1.tgz",
|
||||
"integrity": "sha512-Eip6ALs/+6Jv82Si0I9UnfysdwVlAhkkZRycgmMdnj7jwUg69SVFp84ICxwB8zszkfvJJ2MGAAo9KAYM8ZUykQ==",
|
||||
"dependencies": {
|
||||
"detect-element-overflow": "^1.4.0",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/react-fit?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-geocode": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-geocode/-/react-geocode-0.2.3.tgz",
|
||||
"integrity": "sha512-sIpbgmn1IUzAxO4haOZ6jeeFnMD8ya9PC38yiNrmJ9vPWbvAO2D/2yfCBzZjGZVUm4PRzKAc0KghXfaEnug0TQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-geocode/node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.53.2",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz",
|
||||
|
|
@ -10432,6 +10779,33 @@
|
|||
"react": ">= 0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-time-picker": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-time-picker/-/react-time-picker-7.0.0.tgz",
|
||||
"integrity": "sha512-k6mUjkI+OsY73mg0yjMxqkLXv/UXR1LN7AARNqfyGZOwqHqo1JrjL3lLHTHWQ86HmPTBL/dZACbIX/fV1NLmWg==",
|
||||
"dependencies": {
|
||||
"@wojtekmaj/date-utils": "^1.1.3",
|
||||
"clsx": "^2.0.0",
|
||||
"get-user-locale": "^2.2.1",
|
||||
"make-event-props": "^1.6.0",
|
||||
"react-clock": "^5.0.0",
|
||||
"react-fit": "^2.0.0",
|
||||
"update-input-width": "^1.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/react-time-picker?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
|
|
@ -11615,6 +11989,14 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/supercluster": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
|
||||
"integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
|
||||
"dependencies": {
|
||||
"kdbush": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
|
@ -11721,6 +12103,25 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.10.5",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.5.tgz",
|
||||
"integrity": "sha512-q9eE3EKhMcpIDU/Xcz7z5lk8axCGkgxwK47gXGrrfncnBJWxHPPHnBVAjfsVXcTt8Yi8U6HNEcBRSu+qGeyFdA==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/limonte"
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2-react-content": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2-react-content/-/sweetalert2-react-content-5.0.7.tgz",
|
||||
"integrity": "sha512-8Fk82Mpk45lFXpJWKIFF/lq8k/dJKDDQGFcuqVosaL/qRdViyAs5+u37LoTGfnOIvf+rfQB3PAXcp1XLLn+0ew==",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"sweetalert2": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/swiper": {
|
||||
"version": "11.1.15",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.15.tgz",
|
||||
|
|
@ -11739,6 +12140,11 @@
|
|||
"node": ">= 4.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tabbable": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
|
||||
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA=="
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
|
||||
|
|
@ -12365,6 +12771,14 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/update-input-width": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/update-input-width/-/update-input-width-1.4.2.tgz",
|
||||
"integrity": "sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/update-input-width?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
@ -12419,6 +12833,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-places-autocomplete": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/use-places-autocomplete/-/use-places-autocomplete-4.0.1.tgz",
|
||||
"integrity": "sha512-AybOR/qzXcdaMCGSFveycfL3kztwseAOdagbYoJD8c3amll+gEiPmUkSNhYNUEBqbR+JmJG6/oBTRgihNbE+1A==",
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||
|
|
@ -12629,6 +13051,14 @@
|
|||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
|
||||
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg=="
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-namespaces": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -50,6 +50,8 @@
|
|||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@reach/combobox": "^0.18.0",
|
||||
"@react-google-maps/api": "^2.20.3",
|
||||
"@south-paw/react-vector-maps": "^3.2.0",
|
||||
"@tanstack/react-table": "^8.19.2",
|
||||
"@types/cleave.js": "^1.4.12",
|
||||
|
|
@ -74,6 +76,7 @@
|
|||
"geojson": "^0.5.0",
|
||||
"google-map-react": "^2.2.1",
|
||||
"input-otp": "^1.2.4",
|
||||
"jodit-react": "^4.1.2",
|
||||
"jotai": "^2.9.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"leaflet": "^1.9.4",
|
||||
|
|
@ -93,6 +96,7 @@
|
|||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-geocode": "^0.2.3",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-leaflet": "^4.2.1",
|
||||
|
|
@ -100,23 +104,29 @@
|
|||
"react-resizable-panels": "^2.0.19",
|
||||
"react-select": "^5.8.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-time-picker": "^7.0.0",
|
||||
"recharts": "^2.12.7",
|
||||
"rtl-detect": "^1.1.2",
|
||||
"sharp": "^0.33.4",
|
||||
"sonner": "^1.5.0",
|
||||
"sweetalert2": "^11.10.5",
|
||||
"sweetalert2-react-content": "^5.0.7",
|
||||
"swiper": "^11.1.4",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-places-autocomplete": "^4.0.1",
|
||||
"vaul": "^0.9.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@next/bundle-analyzer": "^15.0.3",
|
||||
"@types/jquery": "^3.5.32",
|
||||
"@types/leaflet": "^1.9.12",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-geocode": "^0.2.4",
|
||||
"@types/rtl-detect": "^1.0.3",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
|
|
|
|||
Loading…
Reference in New Issue