fix:create edit deatil category
This commit is contained in:
parent
e9d22ad3f2
commit
0cd97530ca
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue