feat:fix penugasan, konten

This commit is contained in:
Anang Yusman 2025-01-11 22:30:35 +08:00
parent 7b2cfe3eca
commit c9fb54cfbe
12 changed files with 1773 additions and 262 deletions

View File

@ -1,10 +1,5 @@
// "use client";
import React, {
useState,
useEffect,
useRef,
Fragment,
} from "react";
import React, { useState, useEffect, useRef, Fragment } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@ -49,6 +44,7 @@ import { getUserLevelForAssignments } from "@/service/task";
import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth";
const schema = z.object({
title: z.string().min(3, { message: "Required" }),
@ -75,9 +71,7 @@ const EventModal = ({
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const [isPending, startTransition] = React.useTransition();
const [agendaType, setAgendaType] = React.useState<any>(
categories[0].value
);
const [agendaType, setAgendaType] = React.useState<any>(categories[0].value);
const [listDest, setListDest] = useState([]);
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
@ -97,6 +91,12 @@ const EventModal = ({
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
let progressInfo: any = [];
let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const {
register,
@ -118,11 +118,17 @@ const EventModal = ({
const levelList = response?.data?.data.list;
let listFiltered = [];
if (agendaType == "polda") {
listFiltered = levelList.filter((level: any) => level.name != 'SATKER POLRI');
listFiltered = levelList.filter(
(level: any) => level.name != "SATKER POLRI"
);
} else if (agendaType == "polres") {
listFiltered = levelList.filter((level: any) => level.name != 'SATKER POLRI');
listFiltered = levelList.filter(
(level: any) => level.name != "SATKER POLRI"
);
} else if (agendaType == "satker") {
listFiltered = levelList.filter((level: any) => level.name == 'SATKER POLRI');
listFiltered = levelList.filter(
(level: any) => level.name == "SATKER POLRI"
);
}
setListDest(listFiltered);
const initialExpandedState = listFiltered.reduce(
@ -181,52 +187,28 @@ const EventModal = ({
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"1",
"0"
);
await uploadResumableFile(index, String(id), item, "1", "0");
});
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"2",
"0"
);
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"3",
"0"
);
await uploadResumableFile(index, String(id), item, "3", "0");
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
"4",
"0"
);
await uploadResumableFile(index, String(id), item, "4", "0");
});
// Optional: Use Swal for success feedback
@ -325,19 +307,24 @@ const EventModal = ({
};
async function uploadResumableFile(
idx: number,
id: string,
file: any,
fileTypeId: string,
duration: string
idx: number,
id: string,
file: any,
fileTypeId: string,
duration: string
) {
console.log(idx, id, file, fileTypeId, duration);
// const placements = getPlacement(file.placements);
// console.log("Placementttt: : ", placements);
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken);
const headers = {
"X-XSRF-TOKEN": csrfToken,
};
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/agenda-settings/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
@ -345,8 +332,12 @@ const EventModal = ({
filename: file.name,
filetype: file.type,
fileTypeId: fileTypeId,
duration: "",
isWatermark: "true", // hardcode
duration,
isWatermark: "false", // hardcode
},
onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
},
onError: async (e: any) => {
console.log("Error upload :", e);
@ -358,27 +349,18 @@ const EventModal = ({
bytesTotal: any
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// progressInfo[idx].percentage = uploadPersen;
// counterUpdateProgress++;
// console.log(counterUpdateProgress);
// setProgressList(progressInfo);
// setCounterProgress(counterUpdateProgress);
progressInfo[idx].percentage = uploadPersen;
counterUpdateProgress++;
console.log(counterUpdateProgress);
setProgressList(progressInfo);
setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
// uploadPersen = 100;
// progressInfo[idx].percentage = 100;
// counterUpdateProgress++;
// setCounterProgress(counterUpdateProgress);
uploadPersen = 100;
progressInfo[idx].percentage = 100;
counterUpdateProgress++;
setCounterProgress(counterUpdateProgress);
successTodo();
if (fileTypeId == '1'){
setIsImageUploadFinish(true);
} else if (fileTypeId == '2'){
setIsVideoUploadFinish(true);
} if (fileTypeId == '3'){
setIsTextUploadFinish(true);
} if (fileTypeId == '4'){
setIsAudioUploadFinish(true);
}
},
});
@ -387,14 +369,24 @@ const EventModal = ({
useEffect(() => {
successTodo();
}, [isImageUploadFinish, isVideoUploadFinish, isAudioUploadFinish, isTextUploadFinish])
}, [
isImageUploadFinish,
isVideoUploadFinish,
isAudioUploadFinish,
isTextUploadFinish,
]);
function successTodo() {
if (isImageUploadFinish && isVideoUploadFinish && isAudioUploadFinish && isTextUploadFinish) {
if (
isImageUploadFinish &&
isVideoUploadFinish &&
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/agenda-setting");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
@ -422,7 +414,9 @@ const EventModal = ({
>
<DialogHeader>
<DialogTitle>
{event?.length > 1 ? "Edit Agenda Setting" : "Create Agenda Setting"}{" "}
{event?.length > 1
? "Edit Agenda Setting"
: "Create Agenda Setting"}{" "}
{event?.title}
</DialogTitle>
</DialogHeader>
@ -551,7 +545,9 @@ const EventModal = ({
)}
/>
</div>
{(agendaType === "polda" || agendaType === "polres" || agendaType === "satker" )&& (
{(agendaType === "polda" ||
agendaType === "polres" ||
agendaType === "satker") && (
<div>
<Dialog>
<DialogTrigger asChild>
@ -588,50 +584,56 @@ const EventModal = ({
)}
</button>
</Label>
{(agendaType == "polres" || agendaType == "satker") && expandedPolda[polda.id] && (
<div className="ml-6 mt-2">
<Label className="block">
<Checkbox
checked={polda?.subDestination?.every(
(polres: any) =>
checkedLevels.has(polres.id)
)}
onCheckedChange={(isChecked) => {
const updatedLevels = new Set(
checkedLevels
);
polda?.subDestination?.forEach(
(polres: any) => {
if (isChecked) {
updatedLevels.add(polres.id);
} else {
updatedLevels.delete(polres.id);
}
}
);
setCheckedLevels(updatedLevels);
}}
className="mr-2"
/>
Pilih Semua Polres
</Label>
{polda?.subDestination?.map((polres: any) => (
<Label
key={polres.id}
className="block mt-1"
>
{(agendaType == "polres" ||
agendaType == "satker") &&
expandedPolda[polda.id] && (
<div className="ml-6 mt-2">
<Label className="block">
<Checkbox
checked={checkedLevels.has(polres.id)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
checked={polda?.subDestination?.every(
(polres: any) =>
checkedLevels.has(polres.id)
)}
onCheckedChange={(isChecked) => {
const updatedLevels = new Set(
checkedLevels
);
polda?.subDestination?.forEach(
(polres: any) => {
if (isChecked) {
updatedLevels.add(polres.id);
} else {
updatedLevels.delete(polres.id);
}
}
);
setCheckedLevels(updatedLevels);
}}
className="mr-2"
/>
{polres.name}
Pilih Semua Polres
</Label>
))}
</div>
)}
{polda?.subDestination?.map(
(polres: any) => (
<Label
key={polres.id}
className="block mt-1"
>
<Checkbox
checked={checkedLevels.has(
polres.id
)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
className="mr-2"
/>
{polres.name}
</Label>
)
)}
</div>
)}
</div>
))}
</div>
@ -665,7 +667,7 @@ const EventModal = ({
<div className="space-y-3">
<div>
<Label>Video</Label>
<FileUploader
<FileUploader
accept={{
"mp4/*": [],
"mov/*": [],
@ -677,7 +679,7 @@ const EventModal = ({
</div>
<div>
<Label>Foto</Label>
<FileUploader
<FileUploader
accept={{
"image/*": [],
}}
@ -688,7 +690,7 @@ const EventModal = ({
</div>
<div>
<Label>Teks</Label>
<FileUploader
<FileUploader
accept={{
"pdf/*": [],
}}
@ -708,7 +710,7 @@ const EventModal = ({
downloadOnSavePress={true}
downloadFileExtension="webm"
/>
<FileUploader
<FileUploader
accept={{
"mp3/*": [],
"wav/*": [],
@ -744,9 +746,9 @@ const EventModal = ({
{isPending ? (
<>
<Loader2 className="me-2 h-4 w-4 animate-spin" />
{event?.length > 1 ? "Updating..." : "Adding..."}
{event?.length > 1 ? "Updating..." : "Adding..."}
</>
) : event?.length > 1 ? (
) : event?.length > 1 ? (
"Update Agenda Setting"
) : (
"Simpan Agenda Setting"

View File

@ -112,12 +112,17 @@ const columns: ColumnDef<any>[] = [
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
const isDisabled = row.original.isPublish; // Check the isPublish value
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger asChild disabled={isDisabled}>
<Button
size="icon"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
className={`bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent ${
isDisabled ? "cursor-not-allowed opacity-50" : ""
}`}
disabled={isDisabled} // Disable button if isPublish is true
>
<span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" />
@ -127,7 +132,12 @@ const columns: ColumnDef<any>[] = [
<Link
href={`/contributor/content/spit/convert/${row.original.contentId}`}
>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<DropdownMenuItem
className={`p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none ${
isDisabled ? "cursor-not-allowed opacity-50" : ""
}`}
disabled={isDisabled} // Disable dropdown item if isPublish is true
>
<MoveDownRight className="w-4 h-4 me-1.5" />
Pindah Ke Mediahub
</DropdownMenuItem>

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 FormTaskDetail from "@/components/form/task/task-detail-form";
import FormTaskForward from "@/components/form/task/task-forward-form";
const TaskForwardPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTaskForward />
</div>
</div>
);
};
export default TaskForwardPage;

View File

@ -55,7 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
import ReactAudioPlayer from "react-audio-player";
import WavesurferPlayer from "@wavesurfer/react";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -140,6 +140,16 @@ export default function FormAudioDetail() {
const [isMabesApprover, setIsMabesApprover] = useState(false);
const [audioPlaying, setAudioPlaying] = useState<any>(null);
const waveSurferRef = useRef<any>(null);
const [isPlaying, setIsPlaying] = useState(false);
const onPlayPause = () => {
if (waveSurferRef.current) {
waveSurferRef.current.playPause();
setIsPlaying(!isPlaying);
}
};
let fileTypeId = "4";
const {
@ -245,9 +255,17 @@ export default function FormAudioDetail() {
setSelectedTarget(details.categoryId); // Untuk dropdown
const filesData = details.files || [];
const fileUrls = filesData.map((file: { secondaryUrl: string }) =>
file.secondaryUrl ? file.secondaryUrl : "default-image.jpg"
const audioFiles = filesData.filter(
(file: any) =>
file.contentType && file.contentType.startsWith("video/webm")
);
const fileUrls = audioFiles.map((file: { secondaryUrl: string }) =>
file.secondaryUrl ? file.secondaryUrl : "default-audio.mp3"
);
console.log("audio", fileUrls);
setDetailThumb(fileUrls);
}
}
@ -430,60 +448,19 @@ export default function FormAudioDetail() {
<Label className="text-xl text-black">File Mediaaa</Label>
<div className="w-full">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => {
return (
<SwiperSlide key={data.id}>
<div className="relative">
<ReactAudioPlayer src={data} autoPlay controls />
{/* <button
className="absolute bottom-2 left-2 text-white bg-black p-1 rounded"
onClick={() => handleAudioPlayPause(data)}
>
{audioPlaying === data ? "Pause" : "Play"}
</button> */}
</div>
</SwiperSlide>
);
})}
</Swiper>
{/* <div className="mt-2">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
>
{detailThumb?.map((data: any) => {
return (
<SwiperSlide key={data.id}>
<div className="relative">
<ReactAudioPlayer src={data} autoPlay controls />
<button
className="absolute bottom-2 left-2 text-white bg-black p-1 rounded"
onClick={() =>
handleAudioPlayPause(data.secondaryUrl)
}
>
{audioPlaying === data.secondaryUrl
? "Pause"
: "Play"}
</button>
</div>
</SwiperSlide>
);
})}
</Swiper>
</div> */}
<div className={"container example"}>
{detailThumb?.map((url: any, index: number) => (
<div key={url.id}>
<WavesurferPlayer
ref={waveSurferRef}
height={500}
waveColor="red"
url={url}
/>
</div>
))}
<p onClick={onPlayPause}>{isPlaying ? "Pause" : "Play"}</p>
</div>
</div>
</div>
</div>

View File

@ -334,7 +334,7 @@ export default function FormImageDetail() {
return false;
}
const setupPlacement = (
index: number,
placement: string,
@ -679,7 +679,7 @@ export default function FormImageDetail() {
<Icon icon="humbleicons:times" color="red" />
</a>
</div>
{isUserMabesApprover &&
{isUserMabesApprover && (
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox
@ -756,7 +756,7 @@ export default function FormImageDetail() {
</label>
</div>
</div>
}
)}
</div>
</div>
))

View File

@ -85,6 +85,17 @@ type Option = {
label: string;
};
interface FileData {
contentId: number;
placement?: string[];
[key: string]: any; // Extendable for additional properties
}
interface PlacementData {
mediaFileId: number;
placements: string;
}
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
@ -118,7 +129,6 @@ export default function FormConvertSPIT() {
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [title, setTitle] = useState<string>("");
const roleId = getCookiesDecrypt("urie");
const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
@ -128,7 +138,10 @@ export default function FormConvertSPIT() {
const [detailData, setDetailData] = useState<any>(null);
const [selectedFileType, setSelectedFileType] = useState("original");
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const userLevelId = getCookiesDecrypt("ulie");
const userLevelNumber = getCookiesDecrypt("ulne");
const roleId = getCookiesDecrypt("urie");
const [isMabesApprover, setIsMabesApprover] = useState(false);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
@ -137,6 +150,7 @@ export default function FormConvertSPIT() {
polres: false,
});
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [placementLength, setPlacementLength] = useState([]);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
@ -187,6 +201,17 @@ export default function FormConvertSPIT() {
initState();
}, []);
useEffect(() => {
if (
userLevelId != undefined &&
roleId != undefined &&
userLevelId == "216" &&
roleId == "3"
) {
setIsMabesApprover(true);
}
}, [userLevelId, roleId]);
const getCategories = async () => {
try {
const category = await listCategory(fileTypeId);
@ -247,26 +272,64 @@ export default function FormConvertSPIT() {
}))
);
const handleCheckboxChangeFile = (mediaFileId: any, value: any) => {
setTempFile((prev: any) =>
prev.map((file: any) =>
file.contentId === mediaFileId
? {
...file,
placement: file.placement.includes(value)
? file.placement.filter((item: any) => item !== value) // Hapus jika sudah ada
: [...file.placement, value], // Tambah jika belum ada
}
: file
)
);
const getPlacement = (): PlacementData[] => {
return tempFile
.filter((file: FileData) => (file.placement || []).length > 0) // Gunakan default array
.map((file: FileData) => ({
mediaFileId: Number(file.contentId),
placements: (file.placement || []).join(","), // Gunakan default array
}));
};
const getPlacement = () => {
return tempFile.map((file: any) => ({
mediaFileId: Number(file.contentId),
placements: file.placement.join(","),
}));
const setupPlacement = (value: string, id: number): void => {
const updatedFiles = tempFile.map((file: FileData) => {
if (file.contentId === id) {
const currentPlacement = file.placement || [];
if (currentPlacement.includes(value)) {
// Remove the placement value
file.placement = currentPlacement.filter((val) => val !== value);
} else {
// Add the placement value
file.placement =
value === "all"
? ["all", "mabes", "polda", "international"]
: [...currentPlacement, value];
if (file.placement.includes("all") && value !== "all") {
file.placement = file.placement.filter((val) => val !== "all");
}
}
}
return file;
});
const placementLength = updatedFiles.reduce(
(acc: any, file: any) => acc + (file.placement?.length || 0),
0
);
setTempFile(
updatedFiles.sort((a: any, b: any) => a.contentId - b.contentId)
);
setPlacementLength(placementLength);
console.log("Updated Files:", updatedFiles);
};
const handleCheckboxChangeFile = (contentId: number, value: string) => {
setTempFile((prevTempFile: any) => {
return prevTempFile.map((file: any) => {
if (file.contentId === contentId) {
const isChecked = file.placement?.includes(value);
return {
...file,
placement: isChecked
? file.placement.filter((v: any) => v !== value) // Uncheck
: [...(file.placement || []), value], // Check
};
}
return file;
});
});
};
const handleCheckboxChange = (id: string): void => {
@ -296,11 +359,17 @@ export default function FormConvertSPIT() {
}
};
const save = async (data: any) => {
const save = async (data: {
contentTitle: string;
contentDescription: string;
contentRewriteDescription: string;
contentCreator: string;
}): Promise<void> => {
const description =
selectedFileType === "original"
? data.contentDescription
: data.contentRewriteDescription;
const requestData = {
spitId: id,
title: data.contentTitle,
@ -310,7 +379,7 @@ export default function FormConvertSPIT() {
categoryId: selectedCategoryId,
publishedFor: publishedFor.join(","),
creator: data.contentCreator,
files: getPlacement(),
files: getPlacement(), // Include placement data
};
const response = await convertSPIT(requestData);
@ -630,18 +699,29 @@ export default function FormConvertSPIT() {
</div>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Penempatan File</Label>
{detailThumb.map((data: any) => (
<div key={data.id} className="flex items-center gap-3 mt-2">
<img
className="object-cover w-36 h-32"
src={data}
alt={`Thumbnail ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
{["all", "mabes", "polda", "internasional"].map(
(value) => (
{isMabesApprover ? (
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
{detailThumb.map((data: any) => (
<div
key={data.contentId}
className="flex items-center gap-3 mt-2"
>
<img
className="object-cover w-36 h-32"
src={data}
alt={`Thumbnail ${data.contentId}`}
/>
<div className="flex flex-row gap-3 items-center">
{[
"all",
"mabes",
"polda",
"satker",
"internasional",
].map((value) => (
<label
key={value}
className="text-blue-500 cursor-pointer flex items-center gap-1"
@ -651,22 +731,30 @@ export default function FormConvertSPIT() {
name="placement"
value={value}
onChange={() =>
handleCheckboxChangeFile(data.id, value)
}
checked={tempFile
.find(
(file: any) => file.contentId === data.id
handleCheckboxChangeFile(
data.contentId,
value
)
?.placement.includes(value)}
}
checked={
tempFile
.find(
(file: FileData) =>
file.contentId === data.contentId
)
?.placement?.includes(value) || false
}
/>
{value.charAt(0).toUpperCase() + value.slice(1)}
</label>
)
)}
))}
</div>
</div>
</div>
))}
</div>
))}
</div>
) : (
""
)}
</div>
</div>
</Card>

View File

@ -21,10 +21,14 @@ import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import {
acceptAssignment,
createAssignmentResponse,
createTask,
deleteAssignmentResponse,
deleteTask,
finishTask,
getAcceptance,
getAcceptanceAssignmentStatus,
getAssignmentResponseList,
getTask,
getUserLevelForAssignments,
@ -40,9 +44,10 @@ import { ChevronDown, ChevronUp, DotSquare, TrashIcon } from "lucide-react";
import dynamic from "next/dynamic";
import { Link } from "@/components/navigation";
import { Textarea } from "@/components/ui/textarea";
import { close, loading } from "@/lib/swal";
import { close, error, loading } from "@/lib/swal";
import { getCookiesDecrypt } from "@/lib/utils";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { successCallback } from "@/config/swal";
const taskSchema = z.object({
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -67,6 +72,21 @@ export type taskDetail = {
id: number;
name: string;
};
createdBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
taskType: string;
broadcastType: string;
narration: string;
@ -106,6 +126,25 @@ const formatDate = (dateString: string): string => {
return `${day}-${month}-${year} `;
};
interface AcceptanceData {
id: number;
acceptAt: string;
// Tambahkan properti lain sesuai dengan struktur data Anda
}
interface AcceptanceData {
id: number;
userLevelId: number;
sentAt: string;
isAccept: boolean;
isSent: boolean;
userLevels: {
id: number;
name: string;
aliasName: string;
};
}
export default function FormTaskDetail() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -144,9 +183,14 @@ export default function FormTaskDetail() {
const [showInput, setShowInput] = useState<boolean>(false);
const [listData, setListData] = useState([]);
const [message, setMessage] = useState<string>("");
const [sentAcceptance, setSentAcceptance] = useState([]);
const [acceptAcceptance, setAcceptAcceptance] = useState([]);
const [refreshAcceptance, setRefreshAcceptance] = useState(false);
const [sentAcceptance, setSentAcceptance] = useState<AcceptanceData[]>([]);
const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>(
[]
);
const [statusAcceptance, setStatusAcceptance] = useState();
const [modalType, setModalType] = useState<"terkirim" | "diterima" | "">("");
const [Isloading, setLoading] = useState<boolean>(false);
const [refreshAcceptance, setRefreshAcceptance] = useState<boolean>(false);
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
@ -313,6 +357,19 @@ export default function FormTaskDetail() {
});
};
const successConfirm = () => {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/en/contributor/task");
}
});
};
const handleCheckboxChange = (levelId: any) => {
setCheckedLevels((prev: any) => {
const updatedLevels = new Set(prev);
@ -458,35 +515,65 @@ export default function FormTaskDetail() {
// }
// }
async function getListAcceptance() {
async function getDataAcceptance() {
const response = await getAcceptanceAssignmentStatus(id);
setStatusAcceptance(response?.data?.data?.isAccept);
console.log("Status :", response?.data?.data?.isAccept);
}
const handleAcceptAcceptance = async () => {
const isAccept = true;
const resSent = await getAcceptance(id, !isAccept);
setSentAcceptance(resSent?.data?.data);
const resAccept = await getAcceptance(id, isAccept);
loading();
console.log("Id user :", userId);
const response = await acceptAssignment(id, !isAccept);
const acceptanceSort = resAccept?.data?.data?.sort((a: any, b: any) => {
const dateA = new Date(a.acceptAt).getTime();
const dateB = new Date(b.acceptAt).getTime();
return dateA - dateB;
});
if (response?.error) {
error(response?.message);
return false;
}
console.log("Data sort :", acceptanceSort);
setAcceptAcceptance(acceptanceSort);
successCallback();
getDataAcceptance();
return false;
};
async function getListAcceptance(): Promise<void> {
const isAccept = true;
try {
const resSent = await getAcceptance(id, !isAccept);
setSentAcceptance(resSent?.data?.data);
const resAccept = await getAcceptance(id, isAccept);
const acceptanceSort = resAccept?.data?.data?.sort(
(a: AcceptanceData, b: AcceptanceData) =>
new Date(a.acceptAt).getTime() - new Date(b.acceptAt).getTime()
);
console.log("Data sort:", acceptanceSort);
setAcceptAcceptance(acceptanceSort);
} catch (error) {
console.error("Error fetching acceptance data:", error);
}
}
useEffect(() => {
async function initState() {
getListAcceptance();
async function initState(): Promise<void> {
await getListAcceptance();
}
initState();
}, [refreshAcceptance]);
const setFinishAcceptance = async (id: any, isFinish: any) => {
const setFinishAcceptance = async (
id: number,
isFinish: boolean
): Promise<void> => {
if (!isFinish) {
loading();
setRefreshAcceptance(!refreshAcceptance);
setRefreshAcceptance((prev) => !prev);
close();
}
};
@ -495,15 +582,145 @@ export default function FormTaskDetail() {
setReplyingTo(id);
};
const getModalContent = (type: "terkirim" | "diterima") => (
<div className="overflow-x-auto ">
{Isloading ? (
<p>Loading...</p>
) : (
<table className="w-full border-collapse border border-gray-300">
<thead>
<tr className="bg-gray-100 border-b">
<th className="px-4 py-2 text-left">Waktu</th>
<th className="px-4 py-2 text-left">Unit</th>
<th className="px-4 py-2 text-left">Status</th>
</tr>
</thead>
<tbody>
{(type === "terkirim" ? sentAcceptance : acceptAcceptance).map(
(item) => (
<tr key={item.id} className="border-b">
<td className="px-4 py-2">
{new Date(item.sentAt).toLocaleString()}
</td>
<td className="px-4 py-2">{item.userLevels.name}</td>
<td className="px-4 py-2">
{type === "terkirim" ? "Terkirim" : "Diterima"}
</td>
</tr>
)
)}
</tbody>
</table>
)}
</div>
);
async function finishAssignment() {
const response = finishTask(id);
// if (response.error) {
// error(response.message);
// return false;
// }
successConfirm();
}
async function deleteAssignment() {
const response = deleteTask(id);
// if (response.error) {
// error(response.message);
// return false;
// }
successConfirm();
}
function handleForward() {
router.push(`/en/contributor/task/forward/${id}`);
}
async function handleDeleteAssignment() {
MySwal.fire({
title: "Apakah Anda yakin ingin menghapus data?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteAssignment();
}
});
}
async function handleAssignmentDone() {
MySwal.fire({
title: "Apakah tugas sudah selesai?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Ya",
cancelButtonText: "Tidak",
}).then((result) => {
if (result.isConfirmed) {
finishAssignment();
}
});
}
return (
<Card>
{detail !== undefined ? (
<div className="px-6 py-6">
<div className="flex flex-row justify-between">
<p className="text-lg font-semibold mb-3">Detail Penugasan</p>
<div className="flex gap-3">
<Button color="primary">2 Terkirim</Button>
<Button color="warning">0 Diterima</Button>
<div
className="flex gap-3"
style={
detail?.createdBy?.id === Number(userId)
? {}
: {
display: "none",
}
}
>
<div>
<Dialog>
<DialogTrigger asChild>
<Button
color="primary"
onClick={() => setModalType("terkirim")}
>
{sentAcceptance?.length} Terkirim
</Button>
</DialogTrigger>
<DialogTrigger asChild>
<Button
color="warning"
onClick={() => setModalType("diterima")}
className="ml-3"
>
{acceptAcceptance?.length} Diterima
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>Detail Status Penugasan</DialogTitle>
</DialogHeader>
{modalType === "terkirim" && getModalContent("terkirim")}
{modalType === "diterima" && getModalContent("diterima")}
</DialogContent>
</Dialog>
</div>
</div>
</div>
@ -786,7 +1003,17 @@ export default function FormTaskDetail() {
</Button>
</div>
<div className="">
<Button color="primary" variant={"default"}>
<Button
className="btn btn-primary ml-3 mr-3"
style={
statusAcceptance || detail?.createdBy?.id == Number(userId)
? {
display: "none",
}
: {}
}
onClick={() => handleAcceptAcceptance()}
>
Terima Tugas
</Button>
</div>
@ -976,10 +1203,41 @@ export default function FormTaskDetail() {
))}
</div>
<div className="flex justify-center mt-4">
{detail?.createdBy?.id == Number(userId) &&
detail?.isDone !== true ? (
<>
<Button
type="button"
color="warning"
variant={"outline"}
className="btn btn-outline-danger"
onClick={() => handleDeleteAssignment()}
>
Hapus Tugas
</Button>
<Button
color="primary"
type="button"
variant={"default"}
className="btn btn-primary ml-3"
onClick={() => handleAssignmentDone()}
>
Tugas Selesai
</Button>
</>
) : (
""
)}
{detail?.isDone !== true &&
(Number(userLevelNumber) !== 3 ||
Number(userLevelNumber) == 2) ? (
<Button color="primary" variant={"outline"}>
<Button
color="primary"
variant={"outline"}
type="button"
className="btn btn-outline-primary ml-3"
onClick={() => handleForward()}
>
Forward
</Button>
) : (

View File

@ -427,7 +427,7 @@ export default function FormTask() {
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/agenda-setting");
successSubmit("/in/contributor/task");
}
}

View File

@ -0,0 +1,963 @@
"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 {
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 {
createAssignmentResponse,
createTask,
deleteAssignmentResponse,
deleteTask,
finishTask,
forwardTask,
getAcceptance,
getAssignmentResponseList,
getTask,
getUserLevelForAssignments,
} from "@/service/task";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { ChevronDown, ChevronUp, DotSquare, TrashIcon } from "lucide-react";
import dynamic from "next/dynamic";
import { Link } from "@/components/navigation";
import { Textarea } from "@/components/ui/textarea";
import { close, error, loading } from "@/lib/swal";
import { getCookiesDecrypt } from "@/lib/utils";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const taskSchema = z.object({
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
forwardMessage: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
});
export type taskDetail = {
id: number;
uniqueCode: string;
title: string;
fileTypeOutput: string;
assignedToRole: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
assignmentMainType: {
id: number;
name: string;
};
createdBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
taskType: string;
broadcastType: string;
narration: string;
is_active: string;
isDone: any;
};
interface ListData {
id: number;
name: string;
content: string;
timestamp: string;
}
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
// Pastikan validitas tanggal
if (isNaN(date.getTime())) {
throw new Error("Invalid date format");
}
// Format tanggal
const day = date.getDate().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const year = date.getFullYear();
// const hours = date.getHours().toString().padStart(2, "0");
// Gabungkan hasil format
return `${day}-${month}-${year} `;
};
interface AcceptanceData {
id: number;
acceptAt: string;
// Tambahkan properti lain sesuai dengan struktur data Anda
}
interface AcceptanceData {
id: number;
userLevelId: number;
sentAt: string;
isAccept: boolean;
isSent: boolean;
userLevels: {
id: number;
name: string;
aliasName: string;
};
}
export default function FormTaskForward() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
const userLevelNumber = getCookiesDecrypt("ulne");
const userId = getCookiesDecrypt("uie");
// State for various form fields
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
// const [assignmentType, setAssignmentType] = useState("mediahub");
// const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<string>("1");
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
const [expandedPolda, setExpandedPolda] = useState([{}]);
const [isLoading, setIsLoading] = useState(false);
const [responses, setResponses] = useState<Response[]>([]);
const [response, setResponse] = useState<string>("");
const [showInput, setShowInput] = useState<boolean>(false);
const [listData, setListData] = useState([]);
const [message, setMessage] = useState<string>("");
const [sentAcceptance, setSentAcceptance] = useState<AcceptanceData[]>([]);
const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>(
[]
);
const [modalType, setModalType] = useState<"terkirim" | "diterima" | "">("");
const [Isloading, setLoading] = useState<boolean>(false);
const [refreshAcceptance, setRefreshAcceptance] = useState<boolean>(false);
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const [forwardMessage, setForwardMessage] = useState();
const [narration, setNarration] = useState(null);
const [checkedLevels, setCheckedLevels] = useState(new Set());
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
const {
control,
register,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
// const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// const selectedValue = Number(event.target.value);
// setMainType(selectedValue);
// setPlatformTypeVisible(selectedValue === 2);
// };
useEffect(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
try {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
const initialExpandedState = response?.data?.data.list.reduce(
(acc: any, polda: any) => {
acc[polda.id] = false;
return acc;
},
{}
);
setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
fetchPoldaPolres();
}, []);
useEffect(() => {
async function initState() {
if (id) {
const response = await getTask(id);
const details = response?.data?.data;
setDetail(details);
if (details?.assignedToLevel) {
const levels = new Set(
details.assignedToLevel.split(",").map(Number)
);
setCheckedLevels(levels);
}
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.broadcastType) {
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
}
}, [detail?.broadcastType]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
);
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "3",
image: "4",
text: "5",
};
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(",");
const requestData = {
...data,
// assignmentType,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
narration: data.naration,
platformType: "",
title: data.title,
};
const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
});
};
const onSubmit = (data: TaskSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
const successConfirm = () => {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/en/contributor/task");
}
});
};
const handlePoldaPolresChange = () => {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
};
async function saveForward() {
console.log("Narasi :", narration);
loading();
const data = {
id,
forwardMessage,
assignedToLevel: handlePoldaPolresChange(),
};
const response = await forwardTask(data);
if (response?.error) {
error(response.message);
return false;
}
successConfirm();
return false;
}
const handleCheckboxChange = (levelId: any) => {
setCheckedLevels((prev: any) => {
const updatedLevels = new Set(prev);
if (updatedLevels.has(levelId)) {
updatedLevels.delete(levelId);
} else {
updatedLevels.add(levelId);
}
return updatedLevels;
});
};
const toggleExpand = (poldaId: any) => {
setExpandedPolda((prev: any) => ({
...prev,
[poldaId]: !prev[poldaId],
}));
};
useEffect(() => {
async function initState() {
// loading();
const response = await getAssignmentResponseList(id);
console.log("data", response?.data?.data);
console.log("userLvl", userLevelNumber);
setListData(response?.data?.data);
close();
}
initState();
}, []);
const handleToggleInput = (): void => {
setShowInput(!showInput);
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
};
// const handleSubmitResponse = (): void => {
// if (response.trim()) {
// const newResponse: Response = {
// id: Date.now(), // Unique ID for each response
// name: "Mabes Polri - Approver",
// content: response,
// timestamp: new Date().toLocaleString("id-ID"), // Format timestamp for Indonesia locale
// };
// setResponses([newResponse, ...responses]); // Add new response to the top
// setResponse(""); // Reset textarea
// setShowInput(false); // Hide input
// }
// };
const postData = () => {
sendSuggestionParent();
};
const sendReplyData = async (parentId: number) => {
const inputElement = document.querySelector(
`#input-comment-${parentId}`
) as HTMLTextAreaElement;
if (inputElement?.value?.length > 1) {
loading();
const data = {
assignmentId: id,
message: inputElement.value,
parentId,
};
console.log(data);
const response = await createAssignmentResponse(data);
console.log(response);
const responseGet = await getAssignmentResponseList(id);
console.log(responseGet?.data?.data);
setListData(responseGet?.data?.data);
inputElement.value = "";
close();
setReplyingTo(null);
}
};
async function sendSuggestionParent() {
if (message?.length > 1) {
loading();
const data = {
assignmentId: id,
message,
parentId: null,
};
const response = await createAssignmentResponse(data);
console.log(response);
setMessage("");
const responseGet = await getAssignmentResponseList(id);
console.log(responseGet?.data?.data);
setListData(responseGet?.data?.data);
close();
}
}
async function deleteDataSuggestion(dataId: any) {
loading();
const response = await deleteAssignmentResponse(dataId);
console.log(response);
const responseGet = await getAssignmentResponseList(id);
console.log(responseGet?.data?.data);
setListData(responseGet?.data?.data);
close();
}
const deleteData = (dataId: any) => {
deleteDataSuggestion(dataId);
console.log(dataId);
};
// async function sendSuggestionChild(parentId: any) {
// const msg = document.querySelectorAll(`#input-comment-${parentId}`)[0]
// .value;
// if (msg?.length > 1) {
// loading();
// const data = {
// assignmentId: id,
// message: msg,
// parentId,
// };
// console.log(data);
// const response = await createAssignmentResponse(data);
// console.log(response);
// const responseGet = await getAssignmentResponseList(id);
// console.log(responseGet?.data?.data);
// setListData(responseGet?.data?.data);
// // $(":input").val("");
// close();
// }
// }
async function getListAcceptance(): Promise<void> {
const isAccept = true;
try {
const resSent = await getAcceptance(id, !isAccept);
setSentAcceptance(resSent?.data?.data);
const resAccept = await getAcceptance(id, isAccept);
const acceptanceSort = resAccept?.data?.data?.sort(
(a: AcceptanceData, b: AcceptanceData) =>
new Date(a.acceptAt).getTime() - new Date(b.acceptAt).getTime()
);
console.log("Data sort:", acceptanceSort);
setAcceptAcceptance(acceptanceSort);
} catch (error) {
console.error("Error fetching acceptance data:", error);
}
}
useEffect(() => {
async function initState(): Promise<void> {
await getListAcceptance();
}
initState();
}, [refreshAcceptance]);
const setFinishAcceptance = async (
id: number,
isFinish: boolean
): Promise<void> => {
if (!isFinish) {
loading();
setRefreshAcceptance((prev) => !prev);
close();
}
};
const handleReply = (id: any) => {
setReplyingTo(id);
};
async function finishAssignment() {
const response = finishTask(id);
// if (response.error) {
// error(response.message);
// return false;
// }
successConfirm();
}
async function deleteAssignment() {
const response = deleteTask(id);
// if (response.error) {
// error(response.message);
// return false;
// }
successConfirm();
}
async function handleForward() {
MySwal.fire({
title: "Forward Penugasan?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Ya",
cancelButtonText: "Tidak",
}).then((result) => {
if (result.isConfirmed) {
saveForward();
}
});
}
async function handleDeleteAssignment() {
MySwal.fire({
title: "Apakah Anda yakin ingin menghapus data?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteAssignment();
}
});
}
async function handleAssignmentDone() {
MySwal.fire({
title: "Apakah tugas sudah selesai?",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Ya",
cancelButtonText: "Tidak",
}).then((result) => {
if (result.isConfirmed) {
finishAssignment();
}
});
}
return (
<Card>
{detail !== undefined ? (
<div className="px-6 py-6">
<div className="flex flex-row justify-between">
<p className="text-lg font-semibold mb-3">Tujuan Forward</p>
</div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listDest.map((polda: any) => (
<div key={polda.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
checked={checkedLevels.has(polda.id)}
onCheckedChange={() => handleCheckboxChange(polda.id)}
className="mr-3"
/>
{polda.name}
<button
onClick={() => toggleExpand(polda.id)}
className="ml-2 focus:outline-none"
></button>
</Label>
</div>
))}
</div>
<div className="form-group mt-2">
<Label htmlFor="message">Pesan</Label>
<Textarea
id="forwardMessage"
placeholder="Message"
{...register("forwardMessage")}
/>
</div>
<Button
color="primary"
variant={"default"}
type="button"
className="btn btn-primary mr-2 float-right my-3"
onClick={() => handleForward()}
>
Forward Tugas
</Button>
<div className="space-y-2 mt-6">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
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-6">
<Label>Tujuan Pemilihan Tugas</Label>
<Select
onValueChange={setSelectedTarget}
value={detail.assignedToRole}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="3,4">Semua Pengguna</SelectItem>
<SelectItem value="4">Kontributor</SelectItem>
<SelectItem value="3">Approver</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-3 mt-6 pt-5 ml-3">
{Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
disabled
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 className="mt-6 pt-5 pl-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>
Daftar Wilayah Polda dan Polres
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listDest.map((polda: any) => (
<div key={polda.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
disabled
checked={checkedLevels.has(polda.id)}
onCheckedChange={() =>
handleCheckboxChange(polda.id)
}
className="mr-3"
/>
{polda.name}
<button
onClick={() => toggleExpand(polda.id)}
className="ml-2 focus:outline-none"
>
{expandedPolda[polda.id] ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</button>
</Label>
{expandedPolda[polda.id] && (
<div className="ml-6 mt-2">
<Label className="block">
<Checkbox
disabled
checked={polda?.subDestination?.every(
(polres: any) =>
checkedLevels.has(polres.id)
)}
onCheckedChange={(isChecked) => {
const updatedLevels = new Set(
checkedLevels
);
polda?.subDestination?.forEach(
(polres: any) => {
if (isChecked) {
updatedLevels.add(polres.id);
} else {
updatedLevels.delete(polres.id);
}
}
);
setCheckedLevels(updatedLevels);
}}
className="mr-2"
/>
Pilih Semua Polres
</Label>
{polda?.subDestination?.map((polres: any) => (
<Label key={polres.id} className="block mt-1">
<Checkbox
disabled
checked={checkedLevels.has(polres.id)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
className="mr-2"
/>
{polres.name}
</Label>
))}
</div>
)}
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
</div>
<div className="mt-6">
<Label>Tipe Penugasan</Label>
<RadioGroup
value={detail.assignmentMainType.id.toString()}
onValueChange={(value) => setMainType(value)}
// value={String(mainType)}
// onValueChange={(value) => setMainType(Number(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="1" id="mediahub" />
<Label htmlFor="mediahub">Mediahub</Label>
<RadioGroupItem value="2" id="medsos-mediahub" />
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup>
</div>
<div className="mt-6">
<Label>Jenis Tugas </Label>
<RadioGroup
value={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
<RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="tugas-harian">Tugas Harian</Label>
</RadioGroup>
</div>
{/* RadioGroup Assignment Category */}
<div className="mt-6">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={detail.assignmentType.id.toString()}
onValueChange={(value) => setType(value)}
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="amplification">Amplifikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div>
<div className="mt-6">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
disabled
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
{/* <div className="mt-6">
<Label>Broadcast </Label>
<RadioGroup
value={broadcastType}
onValueChange={(value) => setBroadcastType(value)}
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="all" id="all" />
<Label htmlFor="all">Semua</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="email" id="email" />
<Label htmlFor="email">Email Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="whatsapp" id="whatsapp" />
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
</div>
</RadioGroup>
</div> */}
<div className="mt-6">
<Label>Narasi Penugasan</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<ViewEditor initialData={detail?.narration} />
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
</div>
</form>
</div>
) : (
""
)}
</Card>
);
}

202
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dschoon/react-waves": "^4.0.3",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@fullcalendar/core": "^6.1.15",
@ -64,6 +65,7 @@
"@types/react-html-parser": "^2.0.6",
"@types/react-syntax-highlighter": "^15.5.13",
"@vercel/analytics": "^1.3.1",
"@wavesurfer/react": "^1.0.8",
"apexcharts": "^3.49.2",
"axios": "^1.7.8",
"chart.js": "^4.4.3",
@ -155,6 +157,7 @@
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8",
"react-wavesurfer.js": "0.0.5",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
@ -791,6 +794,55 @@
"react": ">=16.8.0"
}
},
"node_modules/@dschoon/react-waves": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@dschoon/react-waves/-/react-waves-4.0.3.tgz",
"integrity": "sha512-QlygFXBarpdRB/JhFaHkpqOVHfgmODShVkvB96g4JBIidDhhuWidfrudQSo9PxhQnLjAhSlxJjg+8ct7K8TCWw==",
"dependencies": {
"@types/jest": "^26.0.15",
"@types/node": "^14.14.7",
"@types/react": "^16.9.56",
"@types/react-dom": "^16.9.9",
"typescript": "^4.0.5",
"wavesurfer.js": "^4.1.1"
},
"engines": {
"node": ">=8",
"yarn": "^1.9.4"
},
"peerDependencies": {
"prop-types": "^15.7.2",
"react": "^15.0.0 || ^16.0.0 || ^17.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@dschoon/react-waves/node_modules/@types/node": {
"version": "14.18.63",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
},
"node_modules/@dschoon/react-waves/node_modules/@types/react": {
"version": "16.14.62",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.62.tgz",
"integrity": "sha512-BWf7hqninZav6nerxXj+NeZT/mTpDeG6Lk2zREHAy63CrnXoOGPGtNqTFYFN/sqpSaREDP5otVV88axIXmKfGA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "^0.16",
"csstype": "^3.0.2"
}
},
"node_modules/@dschoon/react-waves/node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/@emoji-mart/data": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz",
@ -1327,6 +1379,21 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@jest/types": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
"dependencies": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^15.0.0",
"chalk": "^4.0.0"
},
"engines": {
"node": ">= 10.14.2"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@ -3581,6 +3648,36 @@
"domelementtype": "1"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="
},
"node_modules/@types/istanbul-lib-report": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"dependencies": {
"@types/istanbul-lib-coverage": "*"
}
},
"node_modules/@types/istanbul-reports": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"dependencies": {
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/jest": {
"version": "26.0.24",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz",
"integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==",
"dependencies": {
"jest-diff": "^26.0.0",
"pretty-format": "^26.0.0"
}
},
"node_modules/@types/jquery": {
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz",
@ -3691,6 +3788,14 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "16.9.25",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.25.tgz",
"integrity": "sha512-ZK//eAPhwft9Ul2/Zj+6O11YR6L4JX0J2sVeBC9Ft7x7HFN7xk7yUV/zDxqV6rjvqgl6r8Dq7oQImxtyf/Mzcw==",
"peerDependencies": {
"@types/react": "^16.0.0"
}
},
"node_modules/@types/react-geocode": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@types/react-geocode/-/react-geocode-0.2.4.tgz",
@ -3736,6 +3841,11 @@
"integrity": "sha512-qpstuHivwg/HoXxRrBo5/r/OVx5M2SkqJpVu2haasdLctt+jMGHWjqdbI0LL7Rk2wRmN/UHdHK4JZg9RUMcvKA==",
"dev": true
},
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
},
"node_modules/@types/sizzle": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz",
@ -3747,6 +3857,19 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
},
"node_modules/@types/yargs": {
"version": "15.0.19",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz",
"integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==",
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
},
"node_modules/@typescript-eslint/parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
@ -3916,6 +4039,15 @@
}
}
},
"node_modules/@wavesurfer/react": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@wavesurfer/react/-/react-1.0.8.tgz",
"integrity": "sha512-YowJcynQTTC1oWBWlxr7IkZn9QlYfvOC2rYqOvVA2DwBh+nMEoZca7718disoFNnnX7/NzTKqZd/VuWI5RbhMQ==",
"peerDependencies": {
"react": "^19.0.0",
"wavesurfer.js": ">=7.8.11"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
@ -4235,7 +4367,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -4249,7 +4380,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@ -4892,7 +5022,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@ -6314,6 +6443,14 @@
"node": ">=0.3.1"
}
},
"node_modules/diff-sequences": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
"engines": {
"node": ">= 10.14.2"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -8064,7 +8201,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -9441,6 +9577,28 @@
"integrity": "sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ==",
"dev": true
},
"node_modules/jest-diff": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz",
"integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==",
"dependencies": {
"chalk": "^4.0.0",
"diff-sequences": "^26.6.2",
"jest-get-type": "^26.3.0",
"pretty-format": "^26.6.2"
},
"engines": {
"node": ">= 10.14.2"
}
},
"node_modules/jest-get-type": {
"version": "26.3.0",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
"integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
"engines": {
"node": ">= 10.14.2"
}
},
"node_modules/jest-worker": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
@ -13356,6 +13514,25 @@
"node": ">= 0.8.0"
}
},
"node_modules/pretty-format": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
"integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
"dependencies": {
"@jest/types": "^26.6.2",
"ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0",
"react-is": "^17.0.1"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/pretty-format/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
@ -14085,6 +14262,17 @@
"react": "^16.0.0"
}
},
"node_modules/react-wavesurfer.js": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/react-wavesurfer.js/-/react-wavesurfer.js-0.0.5.tgz",
"integrity": "sha512-KLjNTKS25xfsTWrJd+knXF5wFH8CRu7repXSeDSjh4zhj27QbNL5jtcK8pNl82/kcoTeB0fZvtCcxPreEE8dQw==",
"dev": true,
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"wavesurfer.js": "^5.2.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -15622,7 +15810,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@ -16968,6 +17155,11 @@
"node": ">=10.13.0"
}
},
"node_modules/wavesurfer.js": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-4.6.0.tgz",
"integrity": "sha512-+nn6VD86pTtRu9leVNXoIGOCMJyaTNsKNy9v+SfUsYo+SxLCQvEzrZZ/eKMImqspsk+BX1V1xlY4FRkHswu3fA=="
},
"node_modules/web-namespaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",

View File

@ -13,6 +13,7 @@
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dschoon/react-waves": "^4.0.3",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@fullcalendar/core": "^6.1.15",
@ -65,6 +66,7 @@
"@types/react-html-parser": "^2.0.6",
"@types/react-syntax-highlighter": "^15.5.13",
"@vercel/analytics": "^1.3.1",
"@wavesurfer/react": "^1.0.8",
"apexcharts": "^3.49.2",
"axios": "^1.7.8",
"chart.js": "^4.4.3",
@ -156,6 +158,7 @@
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8",
"react-wavesurfer.js": "0.0.5",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}

View File

@ -41,11 +41,6 @@ export async function forwardTask(data: any) {
return httpPostInterceptor(url, data);
}
export async function finishTask(id: any) {
const url = `assignment/finish?id=${id}`;
return httpPostInterceptor(url, id);
}
export async function deleteTask(id: any) {
const url = `assignment?id=${id}`;
return httpDeleteInterceptor(url);
@ -73,7 +68,7 @@ export async function deleteAssignmentResponse(id: any) {
export async function getAcceptance(id: any, isAccept: any) {
const url = `assignment/acceptance?id=${id}&isAccept=${isAccept}`;
return httpGetInterceptor({ url });
return httpGetInterceptor(url);
}
export async function acceptAssignment(id: any, isAccept: any) {
@ -95,3 +90,8 @@ export async function getListAttachment(assignmentId: any, isForCreator: any) {
const url = `media/list?&enablePage=0&assignmentId=${assignmentId}&isForAdmin=${isForCreator}`;
return httpGetInterceptor({ url });
}
export async function finishTask(id: any) {
const url = `assignment/finish?id=${id}`;
return httpPostInterceptor(url);
}