feat:add form schedule:press conference,event,press release, form penugasan

This commit is contained in:
Anang Yusman 2024-12-04 00:49:25 +07:00
parent 046a9ee5ca
commit 56e5098c34
21 changed files with 2520 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

365
components/maps/Map copy.js Normal file
View File

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

52
components/maps/Map.tsx Normal file
View File

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

View File

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

175
components/maps/Maps.tsx Normal file
View File

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

View File

@ -0,0 +1 @@
export const GoogleMapsAPI = "AIzaSyCOkxoeKykE60L_nM4VS1JYJqBmqy2GA0Q";

434
package-lock.json generated
View File

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

View File

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