diff --git a/app/[locale]/(admin)/admin/content/audio/components/table-audio.tsx b/app/[locale]/(admin)/admin/content/audio/components/table-audio.tsx index 662ccaa..428c166 100644 --- a/app/[locale]/(admin)/admin/content/audio/components/table-audio.tsx +++ b/app/[locale]/(admin)/admin/content/audio/components/table-audio.tsx @@ -186,7 +186,7 @@ const TableAudio = () => { totalPage: Number(showData), title: search || undefined, categoryId: categoryFilter ? Number(categoryFilter) : undefined, - typeId: 2, // video content type + typeId: 4, statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined, startDate: formattedStartDate || undefined, diff --git a/app/[locale]/(admin)/admin/content/text/components/table-teks.tsx b/app/[locale]/(admin)/admin/content/text/components/table-teks.tsx index 65b2697..7818ca3 100644 --- a/app/[locale]/(admin)/admin/content/text/components/table-teks.tsx +++ b/app/[locale]/(admin)/admin/content/text/components/table-teks.tsx @@ -185,7 +185,7 @@ const TableTeks = () => { totalPage: Number(showData), title: search || undefined, categoryId: categoryFilter ? Number(categoryFilter) : undefined, - typeId: 2, // video content type + typeId: 3, statusId: statusFilter?.length > 0 ? Number(statusFilter[0]) : undefined, startDate: formattedStartDate || undefined, diff --git a/components/form/content/audio-visual/video-detail-form.tsx b/components/form/content/audio-visual/video-detail-form.tsx index d117b7a..984858f 100644 --- a/components/form/content/audio-visual/video-detail-form.tsx +++ b/components/form/content/audio-visual/video-detail-form.tsx @@ -50,6 +50,7 @@ import dynamic from "next/dynamic"; import SuggestionModal from "@/components/modal/suggestions-modal"; import { formatDateToIndonesian } from "@/utils/globals"; import ApprovalHistoryModal from "@/components/modal/approval-history-modal"; +import { listArticleCategories } from "@/service/content"; const videoSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), @@ -63,7 +64,7 @@ const ViewEditor = dynamic(() => import("@/components/editor/view-editor"), { type Category = { id: string; - name: string; + title: string; }; type FileType = { @@ -153,6 +154,7 @@ export default function FormVideoDetail() { const [isUserMabesApprover, setIsUserMabesApprover] = useState(false); const [refresh, setRefresh] = useState(false); const [detailVideos, setDetailVideos] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(""); const { control, @@ -179,8 +181,8 @@ export default function FormVideoDetail() { useEffect(() => { async function fetchCategories() { - const categoryRes = await listEnableCategory("2"); // video type - setCategories(categoryRes?.data || []); + const categoryRes = await listArticleCategories(1, 100); + setCategories(categoryRes?.data.data || []); } fetchCategories(); }, []); @@ -191,11 +193,10 @@ export default function FormVideoDetail() { try { const response = await getArticleDetail(Number(id)); const details = response?.data?.data; - + setSelectedCategory(String(details.categories[0].id)); const mappedDetail: Detail = { ...details, - categoryName: - details?.categories?.[0]?.title || details?.categoryName, + categoryId: details?.categories?.[0]?.id, htmlDescription: details?.htmlDescription, statusName: getStatusName(details?.statusId), uploadedById: details?.createdById, @@ -311,39 +312,17 @@ export default function FormVideoDetail() {
- )} - /> - {errors.title?.message && ( -

- {errors.title.message} -

- )} -
-
-
- - -
-
+
+ {/* Left Side */} + +

Update Video

-
- - ( - - )} - /> - {errors.description?.message && ( -

- {errors.description.message} -

- )} -
-
- - {/* */} - -
- -
- -

- {/* Drop files here or click to upload. */} - Drag File -

-
- Upload File Video Max -
-
-
- {files.length ? ( - -
{fileList}
-
-
- -
- -
-
- {/* */} -
-
- ) : null} - {files.length > 0 && ( -
- -
- {files.map((file: any) => ( -
- {file.fileName} -
-
-

{file.fileName}

- - View File - -
-
- -
-
- -
-
- -
-
- -
-
-
- ))} -
-
- )} -
+ {/* Title */} +
+ + ( + + )} + /> + {errors.title && ( +

{errors.title.message}

+ )} +
+ + {/* Category */} +
+ + +
+ + {/* Description */} +
+ + ( + + )} + /> + {errors.description && ( +

+ {errors.description.message} +

+ )} +
+ +
+ +
+ +
+ +

+ {/* Drop files here or click to upload. */} + Drag File +

+
+ Upload File Video Max
- -
- -
-
- - ( - - )} +
+ {detailFiles.map((file) => ( +
+
-
- {/*
- - - Thumbnail Gambar Utama - -
*/} -
- - { - const file = e.target.files?.[0]; - if (file) { - setSelectedFiles([file]); - } - }} - className="dark:border dark:border-gray-500 dark:rounded-lg" - /> - {selectedFiles.length > 0 ? ( - - Thumbnail Baru - - ) : ( - - Thumbnail Lama - - )} -
-
-
- - {file.fileName}

+ handleDeleteFile(file.id)} + > + + +
+ ))} + {files?.map((file) => ( +
+
-
-
-
- - {options.map((option: Option) => ( -
- opt.id !== "all").length - : publishedFor.includes(option.id) - } - onCheckedChange={() => handleCheckboxChange(option.id)} - /> - -
- ))} -
-
-
- -

Suggestion Box (0)

-
-
-

Information:

- {/*

{detail?.status}

*/} -
- -
-
- -
-
- -
+ ))}
+ + + {/* Right Side */} +
+ + + } + /> + +
+ + + e.target.files && setSelectedFile(e.target.files[0]) + } + /> +
+ {selectedFile ? ( + Preview + ) : ( + Current Thumbnail + )} +
+
+ +
+ + +
+ {tags.map((tag, index) => ( + + {tag} + + + ))} +
+
+ +
+ + ( +
+
+ {options.map((option) => { + const isAllChecked = + field.value.length === + options.filter((opt: any) => opt.id !== "all").length; + + const isChecked = + option.id === "all" + ? isAllChecked + : field.value.includes(option.id); + + const handleChange = () => { + let updated: string[] = []; + + if (option.id === "all") { + updated = isAllChecked + ? [] + : options + .filter((opt: any) => opt.id !== "all") + .map((opt: any) => opt.id); + } else { + updated = isChecked + ? field.value.filter((val) => val !== option.id) + : [...field.value, option.id]; + + if (isAllChecked && option.id !== "all") { + updated = updated.filter((val) => val !== "all"); + } + } + + field.onChange(updated); + setPublishedFor(updated); + }; + + return ( +
+ + +
+ ); + })} + + {errors.publishedFor && ( +

+ {errors.publishedFor.message} +

+ )} +
+
+ )} + /> +
+ +
+ + +
+
- ) : ( - "" - )} +
); } diff --git a/components/form/content/audio/audio-detail-form.tsx b/components/form/content/audio/audio-detail-form.tsx index 5340472..c190974 100644 --- a/components/form/content/audio/audio-detail-form.tsx +++ b/components/form/content/audio/audio-detail-form.tsx @@ -25,6 +25,7 @@ import { Switch } from "@/components/ui/switch"; import Cookies from "js-cookie"; import { createMedia, + getArticleDetail, getTagsBySubCategoryId, listEnableCategory, rejectFiles, @@ -64,6 +65,7 @@ import { formatDateToIndonesian } from "@/utils/globals"; import ApprovalHistoryModal from "@/components/modal/approval-history-modal"; import { useDropzone } from "react-dropzone"; import AudioPlayer from "@/components/audio-player"; +import { listArticleCategories } from "@/service/content"; const imageSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), @@ -76,7 +78,7 @@ const imageSchema = z.object({ type Category = { id: string; - name: string; + title: string; }; type FileType = { @@ -230,28 +232,8 @@ export default function FormAudioDetail() { }, []); const getCategories = async () => { - try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data.data.content; - - setCategories(resCategory); - console.log("data category", resCategory); - - if (scheduleId && scheduleType === "3") { - const findCategory = resCategory.find((o) => - o.name.toLowerCase().includes("pers rilis") - ); - - if (findCategory) { - // setValue("categoryId", findCategory.id); - setSelectedCategory(findCategory.id); - const response = await getTagsBySubCategoryId(findCategory.id); - setTags(response?.data?.data); - } - } - } catch (error) { - console.error("Failed to fetch categories:", error); - } + const categoryRes = await listArticleCategories(1, 100); + setCategories(categoryRes?.data.data || []); }; const setupPlacementCheck = (length: number) => { @@ -265,11 +247,12 @@ export default function FormAudioDetail() { useEffect(() => { async function initState() { if (id) { - const response = await detailMedia(id); + const response = await getArticleDetail(Number(id)); const details = response?.data?.data; console.log("detail", details); setFiles(details?.files); console.log("ISI FILES:", details?.files); + setSelectedCategory(String(details.categories[0].id)); setDetail(details); setMain({ @@ -495,36 +478,17 @@ export default function FormAudioDetail() {
{ - console.log("Selected Category:", id); - setSelectedTarget(id); - }} + value={selectedCategory} + onValueChange={setSelectedCategory} > - + - {/* Show the category from details if it doesn't exist in categories list */} - {detail && - !categories.find( - (cat) => - String(cat.id) === String(detail.category.id) - ) && ( - - {detail.category.name} - - )} - {categories.map((category) => ( - - {" "} - {category.name} + {categories?.map((cat) => ( + + {cat.title} ))} @@ -954,15 +952,9 @@ export default function FormAudioUpdate() { ( - - )} + render={({ field }) => } /> + {errors.creatorName?.message && (

{errors.creatorName.message} @@ -1006,23 +998,77 @@ export default function FormAudioUpdate() {

-
+
- {options.map((option: Option) => ( -
- opt.id !== "all").length - : publishedFor.includes(option.id) - } - onCheckedChange={() => handleCheckboxChange(option.id)} - /> - -
- ))} + ( +
+
+ {options.map((option) => { + const isAllChecked = + field.value.length === + options.filter((opt: any) => opt.id !== "all") + .length; + + const isChecked = + option.id === "all" + ? isAllChecked + : field.value.includes(option.id); + + const handleChange = () => { + let updated: string[] = []; + + if (option.id === "all") { + updated = isAllChecked + ? [] + : options + .filter((opt: any) => opt.id !== "all") + .map((opt: any) => opt.id); + } else { + updated = isChecked + ? field.value.filter( + (val) => val !== option.id + ) + : [...field.value, option.id]; + + if (isAllChecked && option.id !== "all") { + updated = updated.filter( + (val) => val !== "all" + ); + } + } + + field.onChange(updated); + setPublishedFor(updated); + }; + + return ( +
+ + +
+ ); + })} + + {errors.publishedFor && ( +

+ {errors.publishedFor.message} +

+ )} +
+
+ )} + />
{/*
diff --git a/components/form/content/document/teks-detail-form.tsx b/components/form/content/document/teks-detail-form.tsx index b6df303..e2ab0ef 100644 --- a/components/form/content/document/teks-detail-form.tsx +++ b/components/form/content/document/teks-detail-form.tsx @@ -25,6 +25,7 @@ import { Switch } from "@/components/ui/switch"; import Cookies from "js-cookie"; import { createMedia, + getArticleDetail, getTagsBySubCategoryId, listEnableCategory, rejectFiles, @@ -63,6 +64,7 @@ import { formatDateToIndonesian } from "@/utils/globals"; import ApprovalHistoryModal from "@/components/modal/approval-history-modal"; import FileTextPreview from "../file-preview-text"; import FileTextThumbnail from "../file-text-thumbnail"; +import { listArticleCategories } from "@/service/content"; const imageSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), @@ -75,7 +77,7 @@ const imageSchema = z.object({ type Category = { id: string; - name: string; + title: string; }; type FileType = { @@ -199,24 +201,8 @@ export default function FormTeksDetail() { const getCategories = async () => { try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; - - setCategories(resCategory); - console.log("data category", resCategory); - - if (scheduleId && scheduleType === "3") { - const findCategory = resCategory.find((o) => - o.name.toLowerCase().includes("pers rilis") - ); - - if (findCategory) { - // setValue("categoryId", findCategory.id); - setSelectedCategory(findCategory.id); // Set the selected category - const response = await getTagsBySubCategoryId(findCategory.id); - setTags(response?.data?.data); - } - } + const categoryRes = await listArticleCategories(1, 100); + setCategories(categoryRes?.data.data || []); } catch (error) { console.error("Failed to fetch categories:", error); } @@ -233,9 +219,11 @@ export default function FormTeksDetail() { useEffect(() => { async function initState() { if (id) { - const response = await detailMedia(id); + const response = await getArticleDetail(Number(id)); const details = response?.data?.data; console.log("detail", details); + setSelectedCategory(String(details.categories[0].id)); + setFiles(details?.files); setDetail(details); setMain({ @@ -441,36 +429,17 @@ export default function FormTeksDetail() {
{ - console.log("Selected Category:", id); - setSelectedTarget(id); - }} + value={selectedCategory} + onValueChange={setSelectedCategory} > - + - {/* Show the category from details if it doesn't exist in categories list */} - {detail && - !categories.find( - (cat) => - String(cat.id) === String(detail.category.id) - ) && ( - - {detail.category.name} - - )} - {categories.map((category) => ( - - {" "} - {category.name} + {categories?.map((cat) => ( + + {cat.title} ))} @@ -861,15 +827,9 @@ export default function FormTeksUpdate() { ( - - )} + render={({ field }) => } /> + {errors.creatorName?.message && (

{errors.creatorName.message} @@ -923,24 +883,79 @@ export default function FormTeksUpdate() {

-
+
- {options.map((option: any) => ( -
- ( +
+
+ {options.map((option) => { + const isAllChecked = + field.value.length === options.filter((opt: any) => opt.id !== "all") - .length - : publishedFor.includes(option.id) - } - onCheckedChange={() => handleCheckboxChange(option.id)} - /> - -
- ))} + .length; + + const isChecked = + option.id === "all" + ? isAllChecked + : field.value.includes(option.id); + + const handleChange = () => { + let updated: string[] = []; + + if (option.id === "all") { + updated = isAllChecked + ? [] + : options + .filter((opt: any) => opt.id !== "all") + .map((opt: any) => opt.id); + } else { + updated = isChecked + ? field.value.filter( + (val) => val !== option.id + ) + : [...field.value, option.id]; + + if (isAllChecked && option.id !== "all") { + updated = updated.filter( + (val) => val !== "all" + ); + } + } + + field.onChange(updated); + setPublishedFor(updated); + }; + + return ( +
+ + +
+ ); + })} + + {errors.publishedFor && ( +

+ {errors.publishedFor.message} +

+ )} +
+
+ )} + />
diff --git a/components/form/content/image/image-update-form.tsx b/components/form/content/image/image-update-form.tsx index 1324bce..d97eb2d 100644 --- a/components/form/content/image/image-update-form.tsx +++ b/components/form/content/image/image-update-form.tsx @@ -24,149 +24,85 @@ import { SelectValue, } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { register } from "module"; -import { Switch } from "@/components/ui/switch"; +import { CloudUpload, MailIcon } from "lucide-react"; +import dynamic from "next/dynamic"; +import { useDropzone } from "react-dropzone"; +import Image from "next/image"; import Cookies from "js-cookie"; +import { error, loading, close } from "@/lib/swal"; +import { Upload } from "tus-js-client"; +import { htmlToString } from "@/utils/globals"; +import { getCookiesDecrypt } from "@/lib/utils"; import { createMedia, deleteFile, getTagsBySubCategoryId, listEnableCategory, uploadThumbnail, + getArticleDetail, + updateArticle, } from "@/service/content/content"; -import { detailMedia } from "@/service/curated-content/curated-content"; -import { Badge } from "@/components/ui/badge"; -import { CloudUpload, MailIcon } from "lucide-react"; -import dynamic from "next/dynamic"; -import { useDropzone } from "react-dropzone"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import Image from "next/image"; -import { error, loading } from "@/lib/swal"; import { getCsrfToken } from "@/service/auth"; -import { Upload } from "tus-js-client"; -import { htmlToString } from "@/utils/globals"; +import { getUserLevelForAssignments } from "@/service/task"; +import { v4 as uuidv4 } from "uuid"; +import { Switch } from "@/components/ui/switch"; +import { + Dialog, + DialogClose, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import { useTranslations } from "next-intl"; + +const CustomEditor = dynamic( + () => import("@/components/editor/custom-editor"), + { ssr: false } +); const imageSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), description: z .string() - .min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }), + .min(2, { message: "Narasi harus lebih dari 2 karakter." }), creatorName: z.string().min(1, { message: "Creator diperlukan" }), - // tags: z.string().min(1, { message: "Judul diperlukan" }), }); -type Category = { - id: string; - name: string; -}; - -type Detail = { - id: string; - title: string; - description: string; - htmldescription: string; - slug: string; - categoryId: number; - category: { - id: string; - name: string; - }; - publishedFor: string; - - publishedForObject: { - id: number; - name: string; - }; - creatorName: string; - categoryName: string; - thumbnailLink: string; - tags: string; -}; - -type Option = { - id: string; - name: string; -}; - -const CustomEditor = dynamic( - () => { - return import("@/components/editor/custom-editor"); - }, - { ssr: false } -); - -interface FileWithPreview extends File { - preview: string; -} +type ImageSchema = z.infer; +type Category = { id: string; name: string }; +type Option = { id: string; name: string }; export default function FormImageUpdate() { const MySwal = withReactContent(Swal); const router = useRouter(); const { id } = useParams() as { id: string }; - console.log("INI ID NYA", id); + const roleId = getCookiesDecrypt("urie"); const editor = useRef(null); - type ImageSchema = z.infer; - let progressInfo: any = []; - let counterUpdateProgress = 0; - const [progressList, setProgressList] = useState([]); - let uploadPersen = 0; - const [isStartUpload, setIsStartUpload] = useState(false); - const [counterProgress, setCounterProgress] = useState(0); - const [selectedFiles, setSelectedFiles] = useState([]); - const taskId = Cookies.get("taskId"); - const scheduleId = Cookies.get("scheduleId"); - const scheduleType = Cookies.get("scheduleType"); + const t = useTranslations("Form"); + const [detail, setDetail] = useState(null); const [categories, setCategories] = useState([]); - const [selectedCategory, setSelectedCategory] = useState(); - const [tags, setTags] = useState([]); - const [detail, setDetail] = useState(); - const [refresh, setRefresh] = useState(false); - const [selectedPublishers, setSelectedPublishers] = useState([]); - const [articleBody, setArticleBody] = useState(""); - const [files, setFiles] = useState([]); - const [filesTemp, setFilesTemp] = useState([]); - const [publishedFor, setPublishedFor] = useState([]); + const [selectedTarget, setSelectedTarget] = useState(""); + const [tags, setTags] = useState([]); + const [files, setFiles] = useState([]); const [thumbnailFile, setThumbnailFile] = useState(null); + const [publishedFor, setPublishedFor] = useState([]); + const [translatedContent, setTranslatedContent] = useState(""); + const [isLoadingTranslate, setIsLoadingTranslate] = useState(false); + const [selectedLang, setSelectedLang] = useState<"id" | "en">("id"); + const [filePlacements, setFilePlacements] = useState([]); const inputRef = useRef(null); - const [selectedOptions, setSelectedOptions] = useState<{ - [fileId: number]: string[]; - }>({}); - - const handleThumbnailChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - setThumbnailFile(file); - } - }; - - const options: Option[] = [ - { id: "all", name: "SEMUA" }, - { id: "5", name: "UMUM" }, - { id: "6", name: "JOURNALIS" }, - { id: "7", name: "POLRI" }, - { id: "8", name: "KSP" }, - ]; - - const [selectedTarget, setSelectedTarget] = useState( - detail?.category.id - ); - const [unitSelection, setUnitSelection] = useState({ - allUnit: false, - mabes: false, - polda: false, - polres: false, - }); - - let fileTypeId = "1"; const { getRootProps, getInputProps } = useDropzone({ - onDrop: (acceptedFiles) => { - setFiles(acceptedFiles.map((file) => Object.assign(file))); - }, - accept: { - "image/*": [], - }, + onDrop: (acceptedFiles) => + setFiles((prev) => [ + ...prev, + ...acceptedFiles.map((f) => + Object.assign(f, { id: uuidv4(), preview: URL.createObjectURL(f) }) + ), + ]), + accept: { "image/*": [] }, }); const { @@ -174,134 +110,61 @@ export default function FormImageUpdate() { handleSubmit, setValue, formState: { errors }, + getValues, } = useForm({ resolver: zodResolver(imageSchema), }); + // 🔹 Get initial data (article + categories) useEffect(() => { - async function initState() { - getCategories(); - } + async function init() { + try { + const [resArticle, resCategory] = await Promise.all([ + getArticleDetail(Number(id)), + listEnableCategory("1"), + ]); - initState(); - }, []); + const article = resArticle?.data?.data; + const categoryList: Category[] = resCategory?.data?.data?.content || []; + setCategories(categoryList); + + if (!article) return; + + // 🧩 Map article to state + setDetail(article); + setSelectedTarget(String(article.categories?.[0]?.id || "")); + setValue("title", article.title); + setValue("description", article.htmlDescription); + setValue("creatorName", article.createdByName); + if (article.tags) + setTags(article.tags.split(",").map((t: string) => t.trim())); + if (article.publishedFor) + setPublishedFor(article.publishedFor.split(",")); + if (article.files) setFiles(article.files); + } catch (err) { + console.error("Error getArticleDetail:", err); + } + } + init(); + }, [id, setValue]); + + const options: Option[] = [ + { id: "all", name: "SEMUA" }, + { id: "4", name: "UMUM" }, + { id: "5", name: "JOURNALIS" }, + ]; const handleAddTag = (e: React.KeyboardEvent) => { if (e.key === "Enter" && e.currentTarget.value.trim()) { e.preventDefault(); const newTag = e.currentTarget.value.trim(); - if (!tags.includes(newTag)) { - setTags((prevTags) => [...prevTags, newTag]); - if (inputRef.current) { - inputRef.current.value = ""; - } - } + if (!tags.includes(newTag)) setTags((prev) => [...prev, newTag]); + if (inputRef.current) inputRef.current.value = ""; } }; - const handleRemoveTag = (index: number) => { - setTags((prevTags) => prevTags.filter((_, i) => i !== index)); - }; - - const handleEditTag = (index: number, newValue: string) => { - setTags((prevTags) => - prevTags.map((tag, i) => (i === index ? newValue : tag)) - ); - }; - - const getCategories = async () => { - try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; - - setCategories(resCategory); - console.log("data category", resCategory); - - if (scheduleId && scheduleType === "3") { - const findCategory = resCategory.find((o) => - o.name.toLowerCase().includes("pers rilis") - ); - - if (findCategory) { - // setValue("categoryId", findCategory.id); - setSelectedCategory(findCategory.id); - const response = await getTagsBySubCategoryId(findCategory.id); - setTags(response?.data?.data); - } - } - } catch (error) { - console.error("Failed to fetch categories:", error); - } - }; - - useEffect(() => { - async function initState() { - if (id) { - const response = await detailMedia(id); - const details = response?.data?.data; - - setDetail(details); - - setSelectedTarget(String(details.category.id)); - - setValue("title", details.title); - setValue("description", details.htmlDescription); - setValue("creatorName", details.creatorName); - - setTimeout(() => { - setValue("title", details.title); - setValue("description", details.htmlDescription); - setValue("creatorName", details.creatorName); - }, 500); - - if (details?.files) { - setFiles(details.files); - - const initialOptions: { [key: number]: string[] } = {}; - details.files.forEach((file: any) => { - if (file.placements) { - initialOptions[file.id] = mapPlacementsToOptions(file.placements); - } - }); - setSelectedOptions(initialOptions); - } - - if (details?.publishedFor) { - // Split string "7" to an array ["7"] if needed - setPublishedFor(details.publishedFor.split(",")); - } - - if (details?.tags) { - setTags(details.tags.split(",").map((tag: string) => tag.trim())); - } - } - } - initState(); - }, [id, setValue]); - - const mapPlacementsToOptions = (placements: string): string[] => { - const mapping: Record = { - all: "all", - mabes: "nasional", - polda: "wilayah", - polres: "internasional", - }; - - if (placements.trim() === "all") { - return ["all", "nasional", "wilayah", "internasional"]; - } - - const options = placements - .split(",") - .map((p) => mapping[p.trim()]) - .filter(Boolean); - - const allSelected = ["nasional", "wilayah", "internasional"].every((opt) => - options.includes(opt) - ); - - return allSelected ? ["all", ...options] : options; - }; + const handleRemoveTag = (index: number) => + setTags((prev) => prev.filter((_, i) => i !== index)); const handleCheckboxChange = (id: string) => { if (id === "all") { @@ -318,139 +181,127 @@ export default function FormImageUpdate() { } }; - const save = async (data: ImageSchema) => { - loading(); - const finalTags = tags.join(", "); - const requestData = { - ...data, - id: detail?.id, - title: data.title, - description: htmlToString(data.description), - htmlDescription: data.description, - fileTypeId, - categoryId: selectedTarget, - subCategoryId: selectedTarget, - uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", - statusId: "1", - publishedFor: publishedFor.join(","), - creatorName: data.creatorName, - tags: finalTags, - isYoutube: false, - isInternationalMedia: false, - }; - - const response = await createMedia(requestData); - console.log("Form Data Submitted:", requestData); - if (response?.error) { - error(response?.message); - return false; - } - - const formMedia = new FormData(); - const thumbnail = thumbnailFile || files[0]; - formMedia.append("file", thumbnail); - - const responseThumbnail = await uploadThumbnail(id, formMedia); - - if (responseThumbnail?.error == true) { - error(responseThumbnail?.message); - return false; - } - - const progressInfoArr = []; - for (const item of files) { - progressInfoArr.push({ percentage: 0, fileName: item.name }); - } - progressInfo = progressInfoArr; - setIsStartUpload(true); - setProgressList(progressInfoArr); - - close(); - // showProgress(); - console.log("files:", files); - files.map(async (item: any, index: number) => { - await uploadResumableFile( - index, - String(id), - item, - fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" - ); - }); - + const handleDeleteFile = async (fileId: number) => { MySwal.fire({ - title: "Sukses", - text: "Data berhasil disimpan.", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then(() => { - router.push("/admin/content/image"); + title: "Hapus file?", + text: "Apakah Anda yakin ingin menghapus file ini?", + icon: "warning", + showCancelButton: true, + confirmButtonText: "Ya, hapus", + }).then(async (res) => { + if (res.isConfirmed) { + const response = await deleteFile({ id: fileId }); + if (response?.error) return error(response.message); + setFiles((prev) => prev.filter((f) => f.id !== fileId)); + Swal.fire("Dihapus!", "File berhasil dihapus.", "success"); + } }); }; - async function uploadResumableFile( - idx: number, - id: string, - file: any, - duration: string - ) { - console.log(idx, id, file, duration); + // const save = async (data: ImageSchema) => { + // loading(); + // const descFinal = + // selectedLang === "en" && translatedContent + // ? translatedContent + // : data.description; - // const placements = getPlacement(file.placements); - // console.log("Placementttt: : ", placements); + // const payload = { + // ...data, + // id: detail?.id, + // title: data.title, + // description: htmlToString(descFinal), + // htmlDescription: descFinal, + // categoryId: selectedTarget, + // publishedFor: publishedFor.join(","), + // creatorName: data.creatorName, + // tags: tags.join(", "), + // isYoutube: false, + // isInternationalMedia: false, + // }; - const resCsrf = await getCsrfToken(); - const csrfToken = resCsrf?.data?.token; - console.log("CSRF TOKEN : ", csrfToken); - const headers = { - "X-XSRF-TOKEN": csrfToken, - }; + // const res = await createMedia(payload); + // if (res?.error) return error(res.message); - if (!file.secondaryUrl || file.secondaryUrl == "") { - const upload = new Upload(file, { - endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`, - headers: headers, - retryDelays: [0, 3000, 6000, 12_000, 24_000], - chunkSize: 20_000, - metadata: { - mediaid: id, - filename: file.name, - filetype: file.type, - duration, - isWatermark: "true", - }, - onBeforeRequest: function (req) { - var xhr = req.getUnderlyingObject(); - xhr.withCredentials = true; - }, - onError: async (e: any) => { - console.log("Error upload :", e); - error(e); - }, - onChunkComplete: ( - chunkSize: any, - bytesAccepted: any, - bytesTotal: any - ) => { - const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); - progressInfo[idx].percentage = uploadPersen; - counterUpdateProgress++; - console.log(counterUpdateProgress); - setProgressList(progressInfo); - setCounterProgress(counterUpdateProgress); - }, - onSuccess: async () => { - uploadPersen = 100; - progressInfo[idx].percentage = 100; - counterUpdateProgress++; - setCounterProgress(counterUpdateProgress); - successTodo(); - }, + // if (thumbnailFile) { + // const form = new FormData(); + // form.append("file", thumbnailFile); + // await uploadThumbnail(id, form); + // } + + // close(); + // Swal.fire("Sukses", "Artikel berhasil diperbarui.", "success").then(() => { + // router.push("/admin/content/image"); + // }); + // }; + + // 🔹 ganti fungsi save di FormImageUpdate.tsx + const save = async (data: ImageSchema) => { + loading(); + + try { + const descFinal = + selectedLang === "en" && translatedContent + ? translatedContent + : data.description; + + // ✅ payload sesuai ArticlesUpdateRequest + const payload = { + aiArticleId: detail?.aiArticleId ?? null, + categoryIds: selectedTarget ? String(selectedTarget) : "", + createdAt: detail?.createdAt ?? new Date().toISOString(), + createdById: detail?.createdById ?? null, + description: htmlToString(descFinal), + htmlDescription: descFinal, + isDraft: false, + isPublish: true, + slug: + detail?.slug ?? + data.title + ?.toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)+/g, ""), + statusId: detail?.statusId ?? 1, + tags: tags, + title: data.title, + typeId: 1, // 1 = image (sesuai struktur kamu) + }; + + console.log("📤 Payload Update Article:", payload); + + // ✅ pakai updateArticle (PUT /articles/:id) + const res = await updateArticle(Number(id), payload); + + if (res?.error) { + error(res.message || "Gagal memperbarui artikel."); + return; + } + + // ✅ upload thumbnail jika user ganti gambar + if (thumbnailFile) { + const form = new FormData(); + form.append("file", thumbnailFile); + const thumbRes = await uploadThumbnail(id, form); + + if (thumbRes?.error) { + error(thumbRes.message); + return; + } + } + + close(); + Swal.fire({ + icon: "success", + title: "Sukses", + text: "Artikel berhasil diperbarui.", + }).then(() => { + router.push("/admin/content/image"); }); - - upload.start(); + } catch (err) { + close(); + error("Terjadi kesalahan saat memperbarui artikel."); + console.error("Update error:", err); } - } + }; const onSubmit = (data: ImageSchema) => { MySwal.fire({ @@ -458,573 +309,284 @@ export default function FormImageUpdate() { 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); - } + if (result.isConfirmed) save(data); }); }; - const successSubmit = (redirect: string) => { - MySwal.fire({ - title: "Sukses", - text: "Data berhasil disimpan.", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then(() => { - router.push(redirect); - }); - }; - - function successTodo() { - let counter = 0; - for (const element of progressInfo) { - if (element.percentage == 100) { - counter++; - } - } - if (counter == progressInfo.length) { - setIsStartUpload(false); - // hideProgress(); - Cookies.remove("idCreate"); - successSubmit("/in/contributor/content/image/"); - } - } - - const handleRemoveAllFiles = () => { - setFiles([]); - }; - - const renderFilePreview = (file: FileWithPreview) => { - if (file?.type?.startsWith("image")) { - return ( - {file.name} - ); - } else { - return ; - } - }; - - const handleRemoveFile = (file: FileWithPreview) => { - const uploadedFiles = files; - const filtered = uploadedFiles.filter((i) => i.name !== file.name); - setFiles([...filtered]); - }; - - const fileList = files.map((file: any) => ( -
-
-
{renderFilePreview(file)}
-
-
- {file.fileName || file.name} -
-
- {Math.round(file.size / 100) / 10 > 1000 ? ( - <>{(Math.round(file.size / 100) / 10000).toFixed(1)} - ) : ( - <>{(Math.round(file.size / 100) / 10).toFixed(1)} - )} - {" kb"} -
-
-
- - -
- )); - - const handleCheckboxChangeImage = (fileId: number, value: string) => { - setSelectedOptions((prev: any) => { - const currentSelections = prev[fileId] || []; - if (value === "all") { - if (currentSelections.includes("all")) { - return { ...prev, [fileId]: [] }; - } - return { - ...prev, - [fileId]: ["all", "nasional", "wilayah", "internasional"], - }; - } else { - const updatedSelections = currentSelections.includes(value) - ? currentSelections.filter((option: any) => option !== value) - : [...currentSelections, value]; - - const isAllSelected = ["nasional", "wilayah", "internasional"].every( - (opt) => updatedSelections.includes(opt) - ); - return { - ...prev, - [fileId]: isAllSelected - ? ["all", ...updatedSelections] - : updatedSelections.filter((opt: any) => opt !== "all"), - }; - } - }); - }; - - function success() { - MySwal.fire({ - title: "Sukses", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then((result) => { - if (result.isConfirmed) { - // window.location.reload(); - } - }); - } - - const handleDeleteFile = (id: number) => { - MySwal.fire({ - title: "Hapus file", - text: "Apakah Anda yakin ingin menghapus file ini?", - icon: "warning", - showCancelButton: true, - cancelButtonColor: "#3085d6", - confirmButtonColor: "#d33", - confirmButtonText: "Hapus", - }).then((result) => { - if (result.isConfirmed) { - doDelete(id); - } - }); - }; - - async function doDelete(id: number) { - const data = { id }; - - try { - const response = await deleteFile(data); - if (response?.error) { - error(response.message); - return; - } - setFiles((prevFiles: any) => - prevFiles.filter((file: any) => file.id !== id) - ); - success(); - } catch (err) { - error("Terjadi kesalahan saat menghapus file"); - } - } - return (
- {detail !== undefined ? ( + {detail && (
+ {/* Kolom Kiri */}
-

Form Image

-
- {/* Input Title */} -
- - ( - - )} - /> - {errors.title?.message && ( -

- {errors.title.message} -

- )} -
-
-
- - + {/* Title */} +
+ + ( + + )} + /> + {errors.title && ( +

{errors.title.message}

+ )} +
+ + {/* Category */} + {/*
+ + +
*/} + +
+ + +
+ + {/* Description */} +
+ + ( + + )} + /> + {errors.description && ( +

+ {errors.description.message} +

+ )} +
+ + {/* File Upload */} +
+ +
+ +
+ +

Drag File

-
- - ( - - )} - /> - {errors.description?.message && ( -

- {errors.description.message} -

- )} -
-
- - {/* */} - -
- -
- -

- {/* Drop files here or click to upload. */} - Drag File -

-
- Upload File Max -
-
-
- {files.length ? ( - -
{fileList}
-
-
- -
- -
-
- {/* */} -
-
- ) : null} - {files.length > 0 && ( -
- -
- {files.map((file: any) => ( -
0 && ( +
+ {files.map((file, index) => ( +
+
+ {file.fileName +
+

{file.fileName}

+ - {file.fileName} -
- -
- -
-
- -
-
- -
-
- -
-
-
- ))} + Lihat File + +
+
- )} - -
+ ))} +
+ )}
-
- -
-
- - ( - - )} - /> - {errors.creatorName?.message && ( -

- {errors.creatorName.message} -

- )} -
-
- {/*
- - - Thumbnail Gambar Utama - -
*/} -
- + {/* Kolom Kanan */} +
+ +
+ + ( + + )} + /> + {errors.creatorName && ( +

+ {errors.creatorName.message} +

+ )} +
+ +
+ { const file = e.target.files?.[0]; - if (file) { - setThumbnailFile(file); - } + if (file) setThumbnailFile(file); }} - className="dark:border dark:border-gray-500 dark:rounded-lg" /> - - - + Thumbnail Preview
-
-
- - -
- {tags.map((tag, index) => ( - - handleEditTag(index, e.target.value)} - className="bg-black text-white border-none focus:outline-none w-auto" - /> - - - ))} -
- {/*
- {detail?.tags?.split(",").map((tag, index) => ( - - {tag.trim()} - - ))} -
*/} +
+ + +
+ {tags.map((tag, i) => ( + + {tag} + + + ))}
-
-
- - {options.map((option: Option) => ( -
+ +
+ +
+ {options.map((opt) => ( +
opt.id !== "all").length - : publishedFor.includes(option.id) + options.filter((o) => o.id !== "all").length + : publishedFor.includes(opt.id) } - onCheckedChange={() => handleCheckboxChange(option.id)} + onCheckedChange={() => handleCheckboxChange(opt.id)} /> - +
))}
-
- -

Suggestion Box (0)

-
-
-

Information:

- {/*

{detail?.status}

*/} -
-
-
- -
-
- -
+ +
+ +
- ) : ( - "" )} ); diff --git a/service/content/content.ts b/service/content/content.ts index 56b853f..d5b94cb 100644 --- a/service/content/content.ts +++ b/service/content/content.ts @@ -3,6 +3,7 @@ import { httpDeleteInterceptor, httpGetInterceptor, httpPostInterceptor, + httpPutInterceptor, } from "../http-config/http-interceptor-service"; // Interface for Articles API filters @@ -24,6 +25,24 @@ export interface ArticleFilters { endDate?: string; } +// types/article.ts + +export interface UpdateArticleData { + aiArticleId?: number; + categoryIDs: string; + createdAt?: string; + createdById?: number; + description?: string; + htmlDescription?: string; + isDraft: boolean; + isPublish: boolean; + slug?: string; + statusId?: number; + tags?: string; + title: string; + typeId?: number; +} + // Interface for creating new article export interface CreateArticleData { aiArticleId: number; @@ -228,6 +247,11 @@ export async function createMedia(data: any) { return httpPostInterceptor(url, data); } +export async function updateArticle(id: number, data: any) { + const url = `articles/${id}`; + return httpPutInterceptor(url, data); +} + // New Articles API - Create Article export async function createArticle(data: CreateArticleData) { const url = "articles"; @@ -323,6 +347,11 @@ export async function deleteFile(data: any) { return httpDeleteInterceptor(url, data); } +export async function deleteArticleFile(id: number) { + const url = `article-files/${id}`; + return httpDeleteInterceptor(url); +} + export async function deleteSPIT(id: any) { const url = `media/spit?id=${id}`; return httpDeleteInterceptor(url);