fix:create edit deatil category

This commit is contained in:
Rama Priyanto 2025-01-07 19:59:32 +07:00
parent e9d22ad3f2
commit 0cd97530ca
6 changed files with 753 additions and 207 deletions

View File

@ -103,7 +103,8 @@ const columns: ColumnDef<any>[] = [
</Button>
</MenubarTrigger>
<MenubarContent className="flex flex-col gap-2 justify-center items-start p-4">
<EditCategoryModal data={row.original} />
<EditCategoryModal id={row.original.id} isDetail={true} />
<EditCategoryModal id={row.original.id} />
<a
onClick={() => categoryDelete(row.original.id)}
className="hover:underline cursor-pointer hover:text-destructive"

View File

@ -25,11 +25,18 @@ import { useRouter } from "@/i18n/routing";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { getUserRoles, postCategory } from "@/service/settings/settings";
import { useEffect, useState } from "react";
import { Fragment, useEffect, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Textarea } from "@/components/ui/textarea";
import { close, error, loading } from "@/config/swal";
import { UnitMapping } from "./unit-mapping";
import { useToast } from "@/components/ui/use-toast";
import { stringify } from "querystring";
import { useDropzone } from "react-dropzone";
import { CloudUpload } from "lucide-react";
import Image from "next/image";
import { Upload } from "tus-js-client";
const FormSchema = z.object({
title: z.string({
@ -48,17 +55,11 @@ const FormSchema = z.object({
.refine((value) => value.some((item) => item), {
message: "Required",
}),
publishTo: z.string({
required_error: "Required",
publishTo: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
file: z
.instanceof(File, {
message: "Invalid file format",
})
.optional()
.refine((file) => file && file.size > 0, {
message: "File is required",
}),
file: z.string().optional(),
});
const listContent = [
@ -80,26 +81,62 @@ const listContent = [
},
];
const publishToList = [
{
id: "mabes",
name: "Nasional",
},
{
id: "polda",
name: "Polda",
},
{
id: "satker",
name: "Satker",
},
{
id: "internasional",
name: "Internasional",
},
];
export default function CreateCategoryModal() {
const router = useRouter();
const { toast } = useToast();
const [files, setFiles] = useState<File[]>([]);
const [isOpen, setIsOpen] = useState(false);
const [satkerData, setSatkerData] = useState<string[]>([]);
const [unitData, setUnitData] = useState<string[]>([]);
const [userList, setUserList] = useState<
{ id: string; name: string; isInternal: boolean }[]
>([]);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { contentType: [], selectedUser: [], publishTo: "national" },
defaultValues: { contentType: [], selectedUser: [], publishTo: [] },
});
const contentType = form.watch("contentType");
const isAllContentChecked = listContent.every((item) =>
contentType?.includes(item.id)
);
const users = form.watch("selectedUser");
const isAllUserChecked = userList.every((item) => users?.includes(item.id));
const target = form.watch("publishTo");
const isAllTargetChecked = publishToList.every((item) =>
target?.includes(item.id)
);
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
maxFiles: 1,
});
useEffect(() => {
getRoles();
}, []);
@ -111,36 +148,77 @@ export default function CreateCategoryModal() {
dataRoles[i].id = String(dataRoles[i].id);
}
setUserList(dataRoles);
console.log("dasda", dataRoles);
}
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (data.file instanceof Blob) {
const formMedia = new FormData();
loading();
formMedia.append("name", data.title);
formMedia.append("description", data.description);
formMedia.append("mediaTypes", data.contentType.join(","));
formMedia.append("file", data.file);
formMedia.append("publishedFor", data.selectedUser.sort().join(","));
formMedia.append(
"isInt",
data.publishTo === "national" ? "false" : "true"
);
console.log(formMedia);
const response = await postCategory(formMedia);
close();
if (response?.error == true) {
error(response.message);
return false;
if (data.publishTo.includes("polda") || data.publishTo.includes("satker")) {
if (
(data.publishTo.includes("polda") && unitData?.length < 1) ||
(data.publishTo.includes("satker") && satkerData?.length < 1)
) {
const poldaValidation = data.publishTo.includes("polda");
const satkerValidation = data.publishTo.includes("satker");
toast({
title:
poldaValidation && satkerValidation
? "Pilih Polda dan Satker tujuan"
: `Pilih ${poldaValidation ? "Polda" : "Satker"} tujuan`,
variant: "destructive",
});
} else {
save(data);
}
router.push("/admin/settings/category?dataChange=true");
} else {
save(data);
}
};
const save = async (data: z.infer<typeof FormSchema>) => {
const formMedia = new FormData();
loading();
const unit = unitData?.join(",");
const satker = satkerData?.join(",");
const join =
unitData?.length > 0 && satkerData?.length > 0
? unit + "," + satker
: unitData?.length > 0
? unit
: satkerData?.length > 0
? satker
: "";
formMedia.append("name", data.title);
formMedia.append("description", data.description);
formMedia.append("mediaTypes", data.contentType.join(","));
formMedia.append("publishedFor", data.selectedUser.join(","));
formMedia.append("file", files[0]);
formMedia.append("publishedLocation", data.publishTo.sort().join(","));
formMedia.append("publishedLocationLevel", join);
const response = await postCategory(formMedia);
close();
if (response?.error) {
toast({ title: stringify(response.message), variant: "destructive" });
return false;
}
toast({
title: "Succes",
description: "Kategori berhasil dibuat",
});
router.push("/admin/settings/category?dataChange=true");
setIsOpen(false);
};
const handleRemoveFile = (file: File) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
return (
<Dialog>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button color="primary" size="md">
Tambah Kategori
@ -299,31 +377,87 @@ export default function CreateCategoryModal() {
<FormField
control={form.control}
name="publishTo"
render={({ field }) => (
<FormItem className="space-y-3">
render={() => (
<FormItem>
<FormLabel>Wilayah Publish</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-2"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="national" />
</FormControl>
<FormLabel className="font-normal">Nasional</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="international" />
</FormControl>
<FormLabel className="font-normal">
Internasional
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<div className="flex flex-row items-center gap-2">
<div className="flex gap-3 items-center">
<Checkbox
id="all"
checked={isAllTargetChecked}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"publishTo",
publishToList.map((item) => item.id)
);
} else {
form.setValue("publishTo", []);
}
}}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{publishToList.map((item) => (
<>
<FormField
key={item.id}
control={form.control}
name="publishTo"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start "
>
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.name}{" "}
</FormLabel>
</div>
</FormItem>
);
}}
/>
{item.id === "polda" &&
form.getValues("publishTo")?.includes(item.id) && (
<UnitMapping
unit="Polda"
isDetail={false}
sendDataToParent={(data) => setUnitData(data)}
/>
)}
{item.id === "satker" &&
form.getValues("publishTo")?.includes(item.id) && (
<UnitMapping
isDetail={false}
unit="Satker"
sendDataToParent={(data) => setSatkerData(data)}
/>
)}
</>
))}
</div>
<FormMessage />
</FormItem>
)}
@ -350,41 +484,36 @@ export default function CreateCategoryModal() {
name="file"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Kategori</FormLabel>
{!field.value && (
<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-28 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Unggah Gambar</span>
</p>
<FormLabel>Thumbnail Category</FormLabel>
{files.length < 1 && (
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png.
Ukuran maksimal 100mb.)
</div>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
field.onChange(file);
}
}}
/>
</label>
</div>
</div>
</Fragment>
)}
{field.value && (
{files.length > 0 && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(field.value)}
src={URL.createObjectURL(files[0])}
className="w-[30%]"
alt="thumbnail"
/>
<a onClick={() => form.setValue("file", undefined)}>
<a
onClick={() => handleRemoveFile(files[0])}
className="cursor-pointer"
>
<Icon icon="fa-solid:times" color="red" />
</a>
</div>
@ -401,11 +530,7 @@ export default function CreateCategoryModal() {
<FormItem>
<FormLabel>Deskripsi</FormLabel>
<FormControl>
<Textarea
placeholder="Deskripsi"
// className="resize-none"
{...field}
/>
<Textarea placeholder="Deskripsi" {...field} />
</FormControl>
<FormMessage />

View File

@ -24,13 +24,20 @@ import {
import { useRouter } from "@/i18n/routing";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { getUserRoles, postCategory } from "@/service/settings/settings";
import { useEffect, useState } from "react";
import {
getCategoryDetail,
getUserRoles,
postCategory,
} from "@/service/settings/settings";
import { Fragment, useEffect, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Textarea } from "@/components/ui/textarea";
import { close, error, loading } from "@/config/swal";
import { UnitMapping } from "./unit-mapping";
import { useDropzone } from "react-dropzone";
import { CloudUpload } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
const FormSchema = z.object({
title: z.string({
required_error: "Required",
@ -48,20 +55,14 @@ const FormSchema = z.object({
.refine((value) => value.some((item) => item), {
message: "Required",
}),
publishTo: z.string({
required_error: "Required",
publishTo: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
file: z
.instanceof(File, {
message: "Invalid file format",
})
.optional()
.refine((file) => file && file.size > 0, {
message: "File is required",
}),
id: z.string({
required_error: "Required",
}),
file: z.string().optional(),
});
const listContent = [
@ -83,38 +84,110 @@ const listContent = [
},
];
export default function EditCategoryModal(props: { data: any }) {
const { data } = props;
const publishToList = [
{
id: "mabes",
name: "Nasional",
},
{
id: "polda",
name: "Polda",
},
{
id: "satker",
name: "Satker",
},
{
id: "internasional",
name: "Internasional",
},
];
export default function EditCategoryModal(props: {
id: string;
isDetail?: boolean;
}) {
const { id, isDetail } = props;
const [files, setFiles] = useState<File[]>([]);
const [isOpen, setIsOpen] = useState(false);
const { toast } = useToast();
const router = useRouter();
const [openModal, setOpenModal] = useState(false);
const [initDataUnit, setInitDataUnit] = useState<string[]>([]);
const [satkerData, setSatkerData] = useState<string[]>([]);
const [unitData, setUnitData] = useState<string[]>([]);
const [userList, setUserList] = useState<
{ id: string; name: string; isInternal: boolean }[]
>([]);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { contentType: [], selectedUser: [], publishTo: "national" },
defaultValues: { contentType: [], selectedUser: [], publishTo: [] },
});
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
maxFiles: 1,
});
useEffect(() => {
form.setValue("id", String(data?.id));
form.setValue("title", String(data?.name));
form.setValue("description", String(data?.description));
form.setValue("contentType", data?.mediaTypes.split(","));
form.setValue("selectedUser", data.publishedFor.split(","));
form.setValue("publishTo", data?.isInt ? "international" : "national");
}, [data]);
const initFetch = async () => {
const req = await getCategoryDetail(id);
const data = req?.data?.data;
console.log("dataC", data);
form.setValue("id", String(data?.id));
form.setValue("title", String(data?.name));
form.setValue("description", String(data?.description));
form.setValue("contentType", data?.mediaTypes?.split(","));
form.setValue(
"selectedUser",
removeAndReturn(data?.publishedFor, [2, 3, 4])
);
form.setValue("publishTo", data?.publishedLocation?.split(","));
setUnitData(filterString(data?.publishedLocationLevel, "under"));
setSatkerData(filterString(data?.publishedLocationLevel, "above"));
};
initFetch();
}, [id]);
function removeAndReturn(inputString: string, toRemove: number[]) {
const numbers = inputString.split(",").map(Number);
const filteredNumbers = numbers.filter((num) => !toRemove.includes(num));
return filteredNumbers.map(String);
}
function filterString(inputString: string, type: string) {
const numbers = inputString.split(",").map(Number);
if (type === "above") {
const above700 = numbers.filter((num) => num > 700);
return above700.map(String);
} else {
const under700 = numbers.filter((num) => num < 700);
return under700.map(String);
}
}
const contentType = form.watch("contentType");
const isAllContentChecked = listContent.every((item) =>
contentType?.includes(item.id)
);
const users = form.watch("selectedUser");
const isAllUserChecked = userList.every((item) => users?.includes(item.id));
const target = form.watch("publishTo");
const isAllTargetChecked = publishToList.every((item) =>
target?.includes(item.id)
);
useEffect(() => {
getRoles();
}, []);
@ -126,38 +199,63 @@ export default function EditCategoryModal(props: { data: any }) {
dataRoles[i].id = String(dataRoles[i].id);
}
setUserList(dataRoles);
console.log("dasda", dataRoles);
}
function removeDuplicates(inputString: string): string {
const numbers = inputString.split(",");
const uniqueNumbers = Array.from(new Set(numbers));
return uniqueNumbers.join(",");
}
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (data.file instanceof Blob) {
const formMedia = new FormData();
const formMedia = new FormData();
loading();
formMedia.append("id", data.id);
formMedia.append("name", data.title);
formMedia.append("description", data.description);
formMedia.append("mediaTypes", data.contentType.join(","));
formMedia.append("file", data.file);
formMedia.append("publishedFor", data.selectedUser.sort().join(","));
formMedia.append(
"isInt",
data.publishTo === "national" ? "false" : "true"
);
console.log(formMedia);
const response = await postCategory(formMedia);
close();
if (response?.error == true) {
error(response.message);
return false;
}
router.push("/admin/settings/category?dataChange=true");
loading();
const unit = unitData?.join(",");
const satker = satkerData?.join(",");
const join =
unitData?.length > 0 && satkerData?.length > 0
? unit + "," + satker
: unitData?.length > 0
? unit
: satkerData?.length > 0
? satker
: "";
formMedia.append("id", data.id);
formMedia.append("name", data.title);
formMedia.append("description", data.description);
formMedia.append("mediaTypes", data.contentType.join(","));
formMedia.append("publishedFor", data.selectedUser.join(","));
formMedia.append("file", files[0]);
formMedia.append("publishedLocation", data.publishTo.sort().join(","));
formMedia.append("publishedLocationLevel", removeDuplicates(join));
const response = await postCategory(formMedia);
close();
if (response?.error == true) {
error(response.message);
return false;
}
toast({
title: "Succes",
description: "Kategori berhasil diubah",
});
router.push("/admin/settings/category?dataChange=true");
setIsOpen(false);
};
const handleRemoveFile = (file: File) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
return (
<Dialog>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger>
<a className="hover:underline">Edit Kategori</a>
<a onClick={() => setIsOpen(true)} className="hover:underline">
{isDetail ? "Detail" : "Edit"}
</a>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
@ -180,6 +278,7 @@ export default function EditCategoryModal(props: { data: any }) {
<Checkbox
id="all"
checked={isAllContentChecked}
disabled={isDetail}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
@ -209,6 +308,7 @@ export default function EditCategoryModal(props: { data: any }) {
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
@ -250,6 +350,7 @@ export default function EditCategoryModal(props: { data: any }) {
<div className="flex gap-3 items-center">
<Checkbox
id="all"
disabled={isDetail}
checked={isAllUserChecked}
onCheckedChange={(checked) => {
if (checked) {
@ -280,6 +381,7 @@ export default function EditCategoryModal(props: { data: any }) {
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
@ -312,31 +414,91 @@ export default function EditCategoryModal(props: { data: any }) {
<FormField
control={form.control}
name="publishTo"
render={({ field }) => (
<FormItem className="space-y-3">
render={() => (
<FormItem>
<FormLabel>Wilayah Publish</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-2"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="national" />
</FormControl>
<FormLabel className="font-normal">Nasional</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="international" />
</FormControl>
<FormLabel className="font-normal">
Internasional
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<div className="flex flex-row items-center gap-2">
<div className="flex gap-3 items-center">
<Checkbox
id="all"
checked={isAllTargetChecked}
disabled={isDetail}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"publishTo",
publishToList.map((item) => item.id)
);
} else {
form.setValue("publishTo", []);
}
}}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{publishToList.map((item) => (
<>
<FormField
key={item.id}
control={form.control}
name="publishTo"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start "
>
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.name}{" "}
</FormLabel>
</div>
</FormItem>
);
}}
/>
{item.id === "polda" &&
form.getValues("publishTo")?.includes(item.id) && (
<UnitMapping
unit="Polda"
sendDataToParent={(data) => setUnitData(data)}
isDetail={isDetail ? true : false}
initData={unitData}
/>
)}
{item.id === "satker" &&
form.getValues("publishTo")?.includes(item.id) && (
<UnitMapping
unit="Satker"
sendDataToParent={(data) => setSatkerData(data)}
isDetail={isDetail ? true : false}
initData={satkerData}
/>
)}
</>
))}
</div>
<FormMessage />
</FormItem>
)}
@ -349,6 +511,7 @@ export default function EditCategoryModal(props: { data: any }) {
<FormLabel>Nama Kategori</FormLabel>
<Input
value={field.value}
readOnly={isDetail}
placeholder="Masukkan Nama Kategori"
onChange={field.onChange}
/>
@ -357,47 +520,41 @@ export default function EditCategoryModal(props: { data: any }) {
</FormItem>
)}
/>
<FormField
{/* <FormField
control={form.control}
name="file"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Kategori</FormLabel>
{!field.value && (
<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-28 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Unggah Gambar</span>
</p>
<FormLabel>Thumbnail Category</FormLabel>
{files.length < 1 && (
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png.
Ukuran maksimal 100mb.)
</div>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
field.onChange(file);
}
}}
/>
</label>
</div>
</div>
</Fragment>
)}
{field.value && (
{files.length > 0 && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(field.value)}
src={URL.createObjectURL(files[0])}
className="w-[30%]"
alt="thumbnail"
/>
<a onClick={() => form.setValue("file", undefined)}>
<a
onClick={() => handleRemoveFile(files[0])}
className="cursor-pointer"
>
<Icon icon="fa-solid:times" color="red" />
</a>
</div>
@ -406,7 +563,8 @@ export default function EditCategoryModal(props: { data: any }) {
<FormMessage />
</FormItem>
)}
/>
/> */}
<FormField
control={form.control}
name="description"
@ -415,6 +573,7 @@ export default function EditCategoryModal(props: { data: any }) {
<FormLabel>Deskripsi</FormLabel>
<FormControl>
<Textarea
readOnly={isDetail}
placeholder="Deskripsi"
// className="resize-none"
{...field}

View File

@ -61,7 +61,7 @@ const AdminCategoryTable = () => {
const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 50,
pageSize: 10,
});
const [page, setPage] = React.useState(1);
@ -108,16 +108,16 @@ const AdminCategoryTable = () => {
async function fetchData() {
try {
loading();
const response = await getCategories();
const response = await getCategories(page - 1);
const data = response?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * 50 + index + 1;
item.no = (page - 1) * 10 + index + 1;
});
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(1);
setTotalPage(data?.totalPages);
close();
} catch (error) {
console.error("Error fetching tasks:", error);
@ -172,11 +172,11 @@ const AdminCategoryTable = () => {
)}
</TableBody>
</Table>
{/* <TablePagination
<TablePagination
table={table}
totalData={totalData}
totalPage={totalPage}
/> */}
/>
</div>
);
};

View File

@ -0,0 +1,253 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useEffect, useState } from "react";
import { getUserLevelForAssignments } from "@/service/task";
const FormSchema = z.object({
items: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
});
interface UnitType {
id: number;
name: string;
subDestination: { id: number; name: string }[];
}
export function UnitMapping(props: {
unit: string;
sendDataToParent: (data: string[]) => void;
isDetail: boolean;
initData?: string[];
}) {
const { unit, sendDataToParent, isDetail } = props;
const [unitList, setUnitList] = useState<UnitType[]>([]);
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
[]
);
const [isOpen, setIsOpen] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
items: props.initData ? props.initData : [],
},
});
useEffect(() => {
async function initState() {
const response = await getUserLevelForAssignments();
setupUnit(response?.data?.data.list);
}
initState();
}, []);
const unitType = form.watch("items");
const isAllUnitChecked = unitList.every((item) =>
unitType?.includes(String(item.id))
);
const isAllSatkerChecked = satkerList.every((item) =>
unitType?.includes(String(item.id))
);
const setupUnit = (data: UnitType[]) => {
const temp = data.filter((a) => a.name.includes("POLDA"));
const temp2 = data.filter((a) => a.name.includes("SATKER"));
setUnitList(temp);
setSatkerList(temp2[0].subDestination);
};
useEffect(() => {
sendDataToParent(form.getValues("items"));
}, [unitType]);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<a
onClick={() => setIsOpen(true)}
className="text-primary cursor-pointer text-xs mr-3"
>
Pilih {unit}
</a>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>{unit}</DialogTitle>
</DialogHeader>
{unit === "Polda" ? (
<Form {...form}>
<form className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Checkbox
id={`all-${unit}`}
checked={isAllUnitChecked}
disabled={isDetail}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"items",
unitList.map((item) => String(item.id))
);
} else {
form.setValue("items", []);
}
}}
/>
<label htmlFor="all" className="text-sm text-black uppercase">
SEMUA {unit}
</label>
</div>
<FormField
control={form.control}
name="items"
render={() => (
<FormItem
className={`grid grid-cols-${unit === "Polda" ? "2" : "3"}`}
>
{unitList?.map((item: any) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => {
return (
<FormItem
key={String(item.id)}
className="flex flex-row items-center space-x-3 space-y-0"
>
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(
String(item.id)
)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
String(item.id),
])
: field.onChange(
field.value?.filter(
(value) => value !== String(item.id)
)
);
}}
/>
</FormControl>
<p className="text-sm text-black">{item.name}</p>
</FormItem>
);
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
) : (
<Form {...form}>
<form className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Checkbox
id={`all-${unit}`}
checked={isAllSatkerChecked}
disabled={isDetail}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"items",
satkerList.map((item) => String(item.id))
);
} else {
form.setValue("items", []);
}
}}
/>
<label htmlFor="all" className="text-sm text-black uppercase">
SEMUA {unit}
</label>
</div>
<FormField
control={form.control}
name="items"
render={() => (
<FormItem
className={`grid grid-cols-${unit === "Polda" ? "2" : "3"}`}
>
{satkerList?.map((item: any) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => {
return (
<FormItem
key={String(item.id)}
className="flex flex-row items-center space-x-3 space-y-0"
>
<FormControl>
<Checkbox
disabled={isDetail}
checked={field.value?.includes(
String(item.id)
)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
String(item.id),
])
: field.onChange(
field.value?.filter(
(value) => value !== String(item.id)
)
);
}}
/>
</FormControl>
<p className="text-sm text-black">{item.name}</p>
</FormItem>
);
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
)}
</DialogContent>
</Dialog>
);
}

View File

@ -4,8 +4,8 @@ import {
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function getCategories() {
const url = "media/categories/list?enablePage=1&size=50&sort=desc&sortBy=id";
export async function getCategories(page: number) {
const url = `media/categories/list?enablePage=1&page=${page}&size=10&sort=desc&sortBy=id`;
return httpGetInterceptor(url);
}
@ -24,9 +24,17 @@ export async function getUserRoles() {
return httpGetInterceptor(url);
}
export async function getCategoryDetail(id: string) {
const url = `media/categories/${id}`;
return httpGetInterceptor(url);
}
export async function postCategory(data: any) {
const url = "media/categories";
return httpPostInterceptor(url, data);
const headers = {
"Content-Type": "multipart/form-data",
};
return httpPostInterceptor(url, data, { headers });
}
export async function getPrivacy(id: string) {