mediahub-fe/components/form/schedule/live-report-detail-form.tsx

745 lines
24 KiB
TypeScript

"use client";
import React, { useEffect, 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 { useParams, useRouter } from "next/navigation";
import { Switch } from "@/components/ui/switch";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import { CalendarIcon, Clock1, Locate, MapPin, User2 } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { addDays, format, parseISO, setDate } from "date-fns";
import { DateRange } from "react-day-picker";
import TimePicker from "react-time-picker";
import "react-time-picker/dist/TimePicker.css";
import "react-clock/dist/Clock.css";
import MapHome from "@/components/maps/MapHome";
import { Textarea } from "@/components/ui/textarea";
import { error, loading } from "@/lib/swal";
import Cookies from "js-cookie";
import {
detailSchedule,
listScheduleNext,
listScheduleToday,
postApprovalSchedule,
postSchedule,
} from "@/service/schedule/schedule";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { formatDateToIndonesian } from "@/utils/globals";
import { formatDate } from "@fullcalendar/core/index.js";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useTranslations } from "next-intl";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Badge } from "@/components/ui/badge";
import { close } from "@/config/swal";
import { Checkbox } from "@/components/ui/checkbox";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
level: z.string().min(1, { message: "Judul diperlukan" }),
name: z.string().min(1, { message: "Judul diperlukan" }),
location: z.string().min(1, { message: "Judul diperlukan" }),
});
interface Detail {
id: number;
title: string;
address: string;
speakerTitle: string;
speakerName: string;
addressLat: number;
addressLong: number;
isYoutube: boolean;
youtubeUrl: string;
needApprovalFrom: number;
uploadedById: number;
statusId?: number;
}
export default function FormDetailLiveReport() {
const { id } = useParams() as { id: string };
const router = useRouter();
type TaskSchema = z.infer<typeof taskSchema>;
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const userLevelNumber = getCookiesDecrypt("ulne");
const [startTime, setStartTime] = useState("08:00");
const [endTime, setEndTime] = useState("09:00");
const [date, setDate] = useState<DateRange | undefined>();
const [todayList, setTodayList] = useState([]);
const [nextDayList, setNextDayList] = useState([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
const t = useTranslations("Form");
const [status, setStatus] = useState("");
const [description, setDescription] = useState("");
const [modalOpen, setModalOpen] = useState(false);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
async function getDataByDate() {
const resToday = await listScheduleToday();
const today = resToday?.data?.data;
setTodayList(today);
const resNext = await listScheduleNext();
const next = resNext?.data?.data;
setNextDayList(next);
}
useEffect(() => {
initState();
}, [refresh]);
async function initState() {
if (id) {
loading();
const response = await detailSchedule(id);
close();
const details = response?.data?.data;
setDetail(details);
if (details) {
setDate({
from: parseISO(details.startDate),
to: parseISO(details.endDate),
});
}
if (details) {
setStartTime(details.startTime);
setEndTime(details.endTime);
}
getDataByDate();
}
}
const handleStartTime = (e: React.ChangeEvent<HTMLInputElement>) => {
setStartTime(e.target.value);
};
const handleEndTime = (e: React.ChangeEvent<HTMLInputElement>) => {
setEndTime(e.target.value);
};
const actionApproval = (e: string) => {
setStatus(e);
setDescription("");
setModalOpen(true);
};
const submit = async () => {
if (
description?.length > 1 &&
(Number(status) == 3 || Number(status) == 2 || Number(status) == 4)
) {
save();
}
};
async function save() {
const data = {
scheduleId: Number(id),
statusId: Number(status),
message: description,
isPublish: status === "2",
placements: schedulePlacements?.filter((val) => val !== "all")?.join(","),
};
loading();
const response = await postApprovalSchedule(data);
close();
setModalOpen(false);
if (response?.error) {
error(response?.message || "Gagal menyimpan data");
return;
}
// ✅ update UI lokal (optimistic)
setDetail((prev) =>
prev
? {
...prev,
statusId: Number(status),
}
: prev,
);
Swal.fire({
icon: "success",
title: "Berhasil",
text:
status === "2"
? "Jadwal berhasil disetujui"
: status === "3"
? "Jadwal dikembalikan untuk revisi"
: "Jadwal berhasil ditolak",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/contributor/schedule/live-report");
}
});
}
// async function save() {
// const data = {
// scheduleId: Number(id),
// statusId: Number(status),
// message: description,
// isPublish: status === "2",
// placements: schedulePlacements?.filter((val) => val != "all")?.join(","),
// };
// loading();
// const response = await postApprovalSchedule(data);
// close();
// setModalOpen(false);
// if (response?.error) {
// error(response?.message);
// return false;
// }
// initState();
// return false;
// }
const [schedulePlacements, setSchedulePlacements] = useState<string[]>([]);
const setupPlacement = (placement: string, checked: boolean) => {
let temp = [...schedulePlacements];
if (checked) {
if (placement === "all") {
temp = ["all", "mabes", "polda", "international"];
} else {
const now = temp;
now.push(placement);
if (now.length === 3 && !now.includes("all")) {
now.push("all");
}
temp = now;
}
} else {
if (placement === "all") {
temp = [];
} else {
const now = temp.filter((a) => a !== placement);
console.log("now", now);
temp = now;
if (now.length === 3 && now.includes("all")) {
const newData = now.filter((b) => b !== "all");
temp = newData;
}
}
}
console.log("temp");
setSchedulePlacements(temp);
};
const isCreator = Number(detail?.uploadedById) === Number(userId);
const isApprover =
Number(detail?.needApprovalFrom) === Number(userLevelId) &&
Number(userLevelNumber) < 2;
const isAlreadyProcessed =
detail?.statusId === 2 || detail?.statusId === 3 || detail?.statusId === 4;
return (
<div className="flex flex-col lg:flex-row gap-2">
<Card className="w-full lg:w-9/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konferensi Pers</p>
{detail !== undefined ? (
<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
type="text"
value={detail?.title}
onChange={field.onChange}
/>
)}
/>
{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-6">
<Label>Live Streaming</Label>
<div className="flex items-center gap-3">
<p className="text-sm">Aktifkan fitur live streaming</p>
<Switch
checked={detail.isYoutube}
color="primary"
id="c2"
/>
</div>
</div>
</div>
{detail.isYoutube && (
<div className="mt-1">
<Input
size={"md"}
type="text"
value={detail.youtubeUrl}
placeholder="Masukan ID youtube"
/>
</div>
)}
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mt-6">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant={"outline"}
className={cn(
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground",
)}
>
<CalendarIcon size={15} className="mr-3" />
{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 className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
<div className="col-6">
<Input
value={startTime}
type="time"
onChange={handleStartTime}
/>
</div>
<div className="col-6">
<Input
value={endTime}
type="time"
onChange={handleEndTime}
/>
</div>
</div>
</div>
</div>
</div>
<div className="mt-6">
{/* Kirim setValue ke MapHome */}
<MapHome
draggable
setLocation={(location) => setValue("location", location)}
/>
</div>
<div>
<Controller
control={control}
name="location"
render={({ field }) => (
<Textarea
rows={3}
value={detail?.address}
onChange={field.onChange}
placeholder="Masukan lokasi"
/>
)}
/>
<div className="invalid-feedback">
{errors.location?.message}
</div>
</div>
<p className="text-sm my-2 font-semiboldc mt-6">
DI SAMPAIKAN OLEH
</p>
<div className="flex flex-col ">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
name="level"
render={({ field }) => (
<Input
size={"md"}
type="text"
value={detail?.speakerTitle}
onChange={field.onChange}
placeholder="Masukan Nama Pangkat"
/>
)}
/>
{errors.level?.message && (
<p className="text-red-400 text-sm">
{errors.level.message}
</p>
)}
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
control={control}
name="name"
render={({ field }) => (
<Input
size={"md"}
type="text"
value={detail?.speakerName}
onChange={field.onChange}
placeholder="Masukan Nama Lengkap"
/>
)}
/>
{errors.name?.message && (
<p className="text-red-400 text-sm">
{errors.name.message}
</p>
)}
</div>
</div>
</div>
) : (
""
)}
</div>
</Card>
<Card className="w-full lg:w-3/12">
<Accordion type="single" collapsible>
{/* Jadwal Hari Ini */}
<AccordionItem value="today">
<AccordionTrigger className="font-semibold">
Jadwal Hari Ini
</AccordionTrigger>
<AccordionContent>
{todayList?.length > 0 ? (
<div className="list-disc ">
{todayList.map((item: any, index) => (
<div key={index} className="">
<li className="text-base font-semibold">{item.title}</li>
<p className="text-sm ml-5 flex my-2 gap-2">
<CalendarIcon size={20} />
{formatDate(item?.startDate)}-
{formatDate(item?.endDate)}
</p>
<p className="text-sm ml-5 flex my-2 gap-2">
<Clock1 size={20} />
{item?.startTime}-{item?.endTime}
</p>
<p className="text-sm ml-5 flex items-center my-2 gap-2">
<MapPin size={20} />
{item?.address}
</p>
<p className="text-sm ml-5">Disampaikan oleh:</p>
<p className="text-sm ml-5 flex my-2 items-center gap-2">
<User2 size={20} />
{item?.speakerTitle} {item?.speakerName}
</p>
</div>
))}
</div>
) : (
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
)}
</AccordionContent>
</AccordionItem>
{/* Jadwal Selanjutnya */}
<AccordionItem value="next">
<AccordionTrigger className="font-semibold">
Jadwal Selanjutnya
</AccordionTrigger>
<AccordionContent>
{nextDayList?.length > 0 ? (
<div className="list-disc ">
{nextDayList.map((item: any, index) => (
<div key={index} className="">
<li className="text-base font-semibold">{item.title}</li>
<p className="text-sm ml-5 flex my-2 gap-2">
<CalendarIcon size={20} />
{formatDate(item?.startDate)}-
{formatDate(item?.endDate)}
</p>
<p className="text-sm ml-5 flex my-2 gap-2">
<Clock1 size={20} />
{item?.startTime}-{item?.endTime}
</p>
<p className="text-sm ml-5 flex items-center my-2 gap-2">
<MapPin size={20} />
{item?.address}
</p>
<p className="text-sm ml-5">Disampaikan oleh:</p>
<p className="text-sm ml-5 flex my-2 items-center gap-2">
<User2 size={20} />
{item?.speakerTitle} {item?.speakerName}
</p>
</div>
))}
</div>
) : (
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
{(isApprover || isCreator) && !isAlreadyProcessed && (
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" />
{t("accept", { defaultValue: "Accept" })}
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" />
{t("revision", { defaultValue: "Revision" })}
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" />
{t("reject", { defaultValue: "Reject" })}
</Button>
</div>
)}
{/* {Number(detail?.needApprovalFrom) == Number(userLevelId) &&
Number(userLevelNumber) < 2 ? (
Number(detail?.uploadedById) == Number(userId) ? (
""
) : (
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" />
{t("accept", { defaultValue: "Accept" })}
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> {t("revision", { defaultValue: "Revision" })}
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" />
{t("reject", { defaultValue: "Reject" })}
</Button>
</div>
)
) : (
""
)} */}
</Card>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="overflow-y-auto">
<DialogHeader>
<DialogTitle>
{t("leave-comment", { defaultValue: "Leave Comment" })}
</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-1 text-sm">
<p>
{" "}
Status:{" "}
<span
className={
status === "2"
? "text-primary"
: status === "3"
? "text-warning"
: "text-destructive"
}
>
{status === "2"
? "Disetujui"
: status === "3"
? "Revisi"
: "Ditolak"}
</span>
</p>
{status === "2" && (
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox
id="terms"
value="all"
checked={schedulePlacements?.includes("all")}
onCheckedChange={(e) => setupPlacement("all", Boolean(e))}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{t("all", { defaultValue: "All" })}
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="terms"
checked={schedulePlacements?.includes("mabes")}
onCheckedChange={(e) => setupPlacement("mabes", Boolean(e))}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Nasional
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="terms"
checked={schedulePlacements?.includes("polda")}
onCheckedChange={(e) => setupPlacement("polda", Boolean(e))}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Wilayah
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="terms"
checked={schedulePlacements?.includes("international")}
onCheckedChange={(e) =>
setupPlacement("international", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Internasional
</label>
</div>
</div>
)}
<p>Deskripsi:</p>
<div className="flex flex-col gap-4">
<Textarea
placeholder="Type your message here."
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
</div>
<DialogFooter>
<Button
type="button"
color="primary"
onClick={() => submit()}
disabled={
description.length < 1 ||
(schedulePlacements.length < 1 && status === "2")
}
>
{t("submit", { defaultValue: "Submit" })}
</Button>
<Button
type="button"
color="destructive"
onClick={() => {
setModalOpen(false);
}}
>
{t("cancel", { defaultValue: "Cancel" })}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}