5198 lines
197 KiB
TypeScript
5198 lines
197 KiB
TypeScript
"use client";
|
|
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
|
import { useForm, Controller } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import * as z from "zod";
|
|
import { useParams, useRouter } from "next/navigation";
|
|
import { useTranslations } from "next-intl";
|
|
import dynamic from "next/dynamic";
|
|
import Cookies from "js-cookie";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
import {
|
|
AlertCircle,
|
|
FileText,
|
|
Image,
|
|
Loader2,
|
|
Save,
|
|
Trash2,
|
|
Edit3,
|
|
Globe,
|
|
Users,
|
|
Tag,
|
|
Eye,
|
|
Settings,
|
|
CheckCircle,
|
|
XCircle,
|
|
} from "lucide-react";
|
|
import { Swiper, SwiperSlide } from "swiper/react";
|
|
import { Swiper as SwiperType } from "swiper";
|
|
import "swiper/css";
|
|
import "swiper/css/free-mode";
|
|
import "swiper/css/navigation";
|
|
import "swiper/css/pagination";
|
|
import "swiper/css/thumbs";
|
|
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
|
import {
|
|
convertSPIT,
|
|
deleteSPIT,
|
|
detailSPIT,
|
|
getTagsBySubCategoryId,
|
|
listEnableCategory,
|
|
listEnableCategoryNew,
|
|
} from "@/service/content/content";
|
|
import { generateDataRewrite, getDetailArticle } from "@/service/content/ai";
|
|
import { getCookiesDecrypt } from "@/lib/utils";
|
|
import { error } from "@/lib/swal";
|
|
import Swal from "sweetalert2";
|
|
import withReactContent from "sweetalert2-react-content";
|
|
import { close, loading } from "@/config/swal";
|
|
|
|
interface Category {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
interface Detail {
|
|
id: string;
|
|
contentTitle: string;
|
|
contentDescription: string;
|
|
slug: string;
|
|
content: {
|
|
id: number;
|
|
name: string;
|
|
};
|
|
contentCreator: string;
|
|
creatorName: string;
|
|
contentThumbnail: string;
|
|
contentTag: string;
|
|
categoryId?: number;
|
|
contentList?: FileType[]; // keep as FileType[]
|
|
}
|
|
|
|
interface Option {
|
|
id: string;
|
|
label: string;
|
|
}
|
|
|
|
// Updated FileType to reflect API response
|
|
interface FileType {
|
|
contentId: number;
|
|
contentFile: string; // URL
|
|
contentType: "IMAGE" | "VIDEO" | string;
|
|
contentFileRawType?: string; // e.g. video/mp4 or image/jpeg
|
|
contentFileName?: string | null;
|
|
thumbnailFileUrl?: string | null;
|
|
fileName?: string | null;
|
|
}
|
|
|
|
interface PlacementData {
|
|
mediaFileId: number;
|
|
placements: string;
|
|
}
|
|
|
|
// Schema
|
|
const formSchema = z.object({
|
|
contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
|
|
contentDescription: z.string().optional(),
|
|
contentRewriteDescription: z.string().optional(),
|
|
contentCreator: z.string().min(1, { message: "Creator diperlukan" }),
|
|
});
|
|
|
|
type FormData = z.infer<typeof formSchema>;
|
|
|
|
// Constants
|
|
const PUBLISH_OPTIONS: Option[] = [
|
|
{ id: "all", label: "SEMUA" },
|
|
{ id: "5", label: "UMUM" },
|
|
{ id: "6", label: "JOURNALIS" },
|
|
{ id: "7", label: "POLRI" },
|
|
{ id: "8", label: "KSP" },
|
|
];
|
|
|
|
const WRITING_STYLES = [
|
|
{ value: "friendly", label: "Friendly" },
|
|
{ value: "professional", label: "Profesional" },
|
|
{ value: "informational", label: "Informational" },
|
|
{ value: "neutral", label: "Neutral" },
|
|
{ value: "witty", label: "Witty" },
|
|
];
|
|
|
|
const PLACEMENT_OPTIONS = [
|
|
{ value: "all", label: "Semua" },
|
|
{ value: "mabes", label: "Nasional" },
|
|
{ value: "polda", label: "Wilayah" },
|
|
{ value: "international", label: "Internasional" },
|
|
];
|
|
|
|
// Dynamic imports
|
|
const CustomEditor = dynamic(
|
|
() => import("@/components/editor/custom-editor"),
|
|
{
|
|
ssr: false,
|
|
loading: () => (
|
|
<div className="flex items-center justify-center h-32 border rounded-md bg-muted/50">
|
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
<span className="ml-2">Loading editor...</span>
|
|
</div>
|
|
),
|
|
}
|
|
);
|
|
|
|
export default function FormConvertSPIT() {
|
|
const MySwal = withReactContent(Swal);
|
|
const router = useRouter();
|
|
const t = useTranslations("Form");
|
|
const { id } = useParams() as { id: string };
|
|
const [isAlreadySaved, setIsAlreadySaved] = useState(false);
|
|
const {
|
|
control,
|
|
handleSubmit,
|
|
setValue,
|
|
formState: { errors, isSubmitting },
|
|
watch,
|
|
} = useForm<FormData>({
|
|
resolver: zodResolver(formSchema),
|
|
defaultValues: {
|
|
contentTitle: "",
|
|
contentDescription: "",
|
|
contentCreator: "",
|
|
contentRewriteDescription: "",
|
|
},
|
|
});
|
|
|
|
// Component state
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
const [detail, setDetail] = useState<Detail | null>(null);
|
|
const [categories, setCategories] = useState<Category[]>([]);
|
|
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(
|
|
null
|
|
);
|
|
const [selectedFileType, setSelectedFileType] = useState<
|
|
"original" | "rewrite"
|
|
>("original");
|
|
const [selectedWritingStyle, setSelectedWritingStyle] =
|
|
useState("professional");
|
|
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
|
|
const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false);
|
|
const [isLoadingRewrite, setIsLoadingRewrite] = useState(false);
|
|
// <-- changed: detailThumb is now FileType[] (objects from API) -->
|
|
const [detailThumb, setDetailThumb] = useState<FileType[]>([]);
|
|
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
|
const [files, setFiles] = useState<FileType[]>([]);
|
|
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
|
const [articleIds, setArticleIds] = useState<string[]>([]);
|
|
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
|
null
|
|
);
|
|
const [articleBody, setArticleBody] = useState<string>("");
|
|
const [tags, setTags] = useState<string[]>([]);
|
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
|
const [inputRef] = useState(useRef<HTMLInputElement>(null));
|
|
const userLevelId = getCookiesDecrypt("ulie");
|
|
const roleId = getCookiesDecrypt("urie");
|
|
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
|
|
|
useEffect(() => {
|
|
initializeComponent();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
checkUserPermissions();
|
|
}, [userLevelId, roleId]);
|
|
|
|
// Handle Swiper re-initialization when detailThumb changes
|
|
useEffect(() => {
|
|
if (thumbsSwiper && detailThumb.length > 0) {
|
|
thumbsSwiper.update();
|
|
}
|
|
}, [detailThumb, thumbsSwiper]);
|
|
|
|
const initializeComponent = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
await Promise.all([
|
|
loadCategories(),
|
|
id ? loadDetail() : Promise.resolve(),
|
|
]);
|
|
} catch (error) {
|
|
console.error("Failed to initialize component:", error);
|
|
MySwal.fire({
|
|
title: "Error",
|
|
text: "Failed to load data. Please try again.",
|
|
icon: "error",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const checkUserPermissions = () => {
|
|
if (userLevelId === "216" && roleId === "3") {
|
|
setIsUserMabesApprover(true);
|
|
}
|
|
};
|
|
|
|
const loadCategories = async () => {
|
|
try {
|
|
const response = await listEnableCategoryNew("1", true);
|
|
const categories = response?.data?.data?.content || [];
|
|
setCategories(categories);
|
|
|
|
const scheduleId = Cookies.get("scheduleId");
|
|
const scheduleType = Cookies.get("scheduleType");
|
|
|
|
if (scheduleId && scheduleType === "3") {
|
|
const persRilisCategory = categories.find((cat: Category) =>
|
|
cat.name.toLowerCase().includes("pers rilis")
|
|
);
|
|
if (persRilisCategory) {
|
|
setSelectedCategoryId(persRilisCategory.id);
|
|
await loadTags(persRilisCategory.id);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load categories:", error);
|
|
}
|
|
};
|
|
|
|
const loadTags = async (categoryId: number) => {
|
|
try {
|
|
const response = await getTagsBySubCategoryId(categoryId);
|
|
if (response?.data?.data?.length > 0) {
|
|
const apiTags = response?.data?.data?.map((tag: any) => tag.tagName);
|
|
const tagsMerge = Array.from(new Set([...tags, ...apiTags]));
|
|
setTags(tagsMerge);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load tags:", error);
|
|
}
|
|
};
|
|
|
|
const loadDetail = async () => {
|
|
loading();
|
|
try {
|
|
const response = await detailSPIT(id);
|
|
const details = response?.data?.data;
|
|
setIsAlreadySaved(details?.isPublish ? true : false);
|
|
|
|
if (!details) {
|
|
throw new Error("Detail not found");
|
|
}
|
|
|
|
setDetail(details);
|
|
|
|
const contentList: FileType[] = (details.contentList || []).map(
|
|
(it: any) => ({
|
|
contentId: it.contentId,
|
|
contentFile: it.contentFile,
|
|
contentType: it.contentType,
|
|
contentFileRawType: it.contentFileRawType,
|
|
contentFileName: it.contentFileName,
|
|
thumbnailFileUrl: it.thumbnailFileUrl || null,
|
|
fileName: it.contentFileName || null,
|
|
})
|
|
);
|
|
|
|
setFiles(contentList);
|
|
setDetailThumb(contentList);
|
|
|
|
const fileCount = contentList.length || 0;
|
|
setFilePlacements(Array(fileCount).fill([]));
|
|
|
|
setValue("contentTitle", details.contentTitle || "");
|
|
setValue("contentDescription", details.contentDescription || "");
|
|
setValue("contentCreator", details.contentCreator || "");
|
|
setValue(
|
|
"contentRewriteDescription",
|
|
(details as any).contentRewriteDescription || ""
|
|
);
|
|
|
|
if (details.categoryId) {
|
|
setSelectedCategoryId(details.categoryId);
|
|
await loadTags(details.categoryId);
|
|
}
|
|
|
|
if (details.contentTag) {
|
|
const contentTags = details.contentTag
|
|
.split(",")
|
|
.map((tag: string) => tag.trim());
|
|
setTags((prev) => Array.from(new Set([...prev, ...contentTags])));
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load detail:", error);
|
|
throw error;
|
|
}
|
|
close();
|
|
};
|
|
|
|
const handleCategoryChange = async (categoryId: string) => {
|
|
const id = Number(categoryId);
|
|
setSelectedCategoryId(id);
|
|
await loadTags(id);
|
|
};
|
|
|
|
const handleRewriteClick = async () => {
|
|
if (!detail?.contentDescription) {
|
|
MySwal.fire({
|
|
title: "Warning",
|
|
text: "Please add content description first",
|
|
icon: "warning",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsGeneratingRewrite(true);
|
|
const request = {
|
|
style: selectedWritingStyle,
|
|
lang: "id",
|
|
contextType: "text",
|
|
urlContext: null,
|
|
context: detail.contentDescription,
|
|
createdBy: roleId,
|
|
sentiment: "Humorous",
|
|
clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
|
|
};
|
|
|
|
const response = await generateDataRewrite(request);
|
|
console.log("REWRITE RESPONSE:", response);
|
|
|
|
if (response?.error) {
|
|
throw new Error(response.message);
|
|
}
|
|
|
|
const newArticleId = response?.data?.data?.id;
|
|
setArticleIds((prev) => {
|
|
const updated = [...prev];
|
|
if (updated.length < 3) {
|
|
updated.push(newArticleId);
|
|
} else {
|
|
updated[2] = newArticleId;
|
|
}
|
|
return updated;
|
|
});
|
|
|
|
setShowRewriteEditor(true);
|
|
MySwal.fire({
|
|
title: "Success",
|
|
text: "Content rewrite generated successfully",
|
|
icon: "success",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to generate rewrite:", error);
|
|
MySwal.fire({
|
|
title: "Error",
|
|
text: "Failed to generate content rewrite",
|
|
icon: "error",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
} finally {
|
|
setIsGeneratingRewrite(false);
|
|
}
|
|
};
|
|
|
|
const handleArticleSelect = async (articleId: string) => {
|
|
try {
|
|
setIsLoadingRewrite(true);
|
|
setSelectedArticleId(articleId);
|
|
|
|
let retryCount = 0;
|
|
const maxRetries = 20;
|
|
|
|
while (retryCount < maxRetries) {
|
|
const response = await getDetailArticle(articleId);
|
|
const articleData = response?.data?.data;
|
|
|
|
if (articleData?.status === 2) {
|
|
const cleanArticleBody = articleData.articleBody?.replace(
|
|
/<img[^>]*>/g,
|
|
""
|
|
);
|
|
setArticleBody(cleanArticleBody || "");
|
|
setValue("contentRewriteDescription", cleanArticleBody || "");
|
|
break;
|
|
}
|
|
|
|
retryCount++;
|
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
}
|
|
|
|
if (retryCount >= maxRetries) {
|
|
throw new Error("Timeout: Article processing took too long");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load article:", error);
|
|
MySwal.fire({
|
|
title: "Error",
|
|
text: "Failed to load article content",
|
|
icon: "error",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
} finally {
|
|
setIsLoadingRewrite(false);
|
|
}
|
|
};
|
|
|
|
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === "Enter" && inputRef.current?.value.trim()) {
|
|
e.preventDefault();
|
|
const newTag = inputRef.current.value.trim();
|
|
|
|
if (!tags.includes(newTag)) {
|
|
setTags((prev) => [...prev, newTag]);
|
|
}
|
|
|
|
inputRef.current.value = "";
|
|
}
|
|
};
|
|
|
|
const handleRemoveTag = (index: number) => {
|
|
setTags((prev) => prev.filter((_, i) => i !== index));
|
|
};
|
|
|
|
const handlePublishTargetChange = (optionId: string) => {
|
|
if (optionId === "all") {
|
|
setPublishedFor((prev) =>
|
|
prev.length === PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").length
|
|
? []
|
|
: PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").map(
|
|
(opt) => opt.id
|
|
)
|
|
);
|
|
} else {
|
|
setPublishedFor((prev) =>
|
|
prev.includes(optionId)
|
|
? prev.filter((id) => id !== optionId && id !== "all")
|
|
: [...prev.filter((id) => id !== "all"), optionId]
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleFilePlacementChange = (
|
|
fileIndex: number,
|
|
placement: string,
|
|
checked: boolean
|
|
) => {
|
|
setFilePlacements((prev) => {
|
|
const updated = [...prev];
|
|
const currentPlacements = updated[fileIndex] || [];
|
|
|
|
if (checked) {
|
|
if (placement === "all") {
|
|
updated[fileIndex] = ["all", "mabes", "polda", "international"];
|
|
} else {
|
|
const newPlacements = [...currentPlacements, placement];
|
|
if (newPlacements.length === 3 && !newPlacements.includes("all")) {
|
|
newPlacements.push("all");
|
|
}
|
|
updated[fileIndex] = newPlacements;
|
|
}
|
|
} else {
|
|
if (placement === "all") {
|
|
updated[fileIndex] = [];
|
|
} else {
|
|
const newPlacements = currentPlacements.filter(
|
|
(p) => p !== placement
|
|
);
|
|
if (newPlacements.length === 3 && newPlacements.includes("all")) {
|
|
updated[fileIndex] = newPlacements.filter((p) => p !== "all");
|
|
} else {
|
|
updated[fileIndex] = newPlacements;
|
|
}
|
|
}
|
|
}
|
|
|
|
return updated;
|
|
});
|
|
};
|
|
|
|
const handleSelectAllPlacements = (placement: string, checked: boolean) => {
|
|
setFilePlacements((prev) =>
|
|
prev.map((filePlacements) =>
|
|
checked
|
|
? Array.from(new Set([...filePlacements, placement]))
|
|
: filePlacements.filter((p) => p !== placement)
|
|
)
|
|
);
|
|
};
|
|
|
|
const getPlacementData = (type: string) => {
|
|
const placementData: PlacementData[] = [];
|
|
if (type == "mabes") {
|
|
for (let i = 0; i < filePlacements.length; i++) {
|
|
if (filePlacements[i].length > 0) {
|
|
const placements = filePlacements[i];
|
|
placementData.push({
|
|
mediaFileId: files[i].contentId,
|
|
placements: placements.join(","),
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
for (let i = 0; i < files.length; i++) {
|
|
placementData.push({
|
|
mediaFileId: files[i].contentId,
|
|
placements: "polda",
|
|
});
|
|
}
|
|
}
|
|
return placementData;
|
|
};
|
|
|
|
const checkPlacement = (data: any) => {
|
|
let temp = true;
|
|
for (const element of data) {
|
|
if (element.length < 1) {
|
|
temp = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return temp;
|
|
};
|
|
|
|
const onSubmit = async (data: FormData) => {
|
|
const pnmhTags = tags.filter((tag) => tag.toLowerCase().includes("pnmh"));
|
|
|
|
if (pnmhTags.length > 1) {
|
|
MySwal.fire({
|
|
title: "Error",
|
|
text: "Tags penugasan hanya diperbolehkan 1 (satu) saja.",
|
|
icon: "error",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// if (!checkPlacement(filePlacements)) {
|
|
// error("Select File Placement");
|
|
// return false;
|
|
// }
|
|
|
|
if (!selectedCategoryId) {
|
|
error("Select a category");
|
|
return false;
|
|
}
|
|
|
|
if (publishedFor.length < 1) {
|
|
error("Select Publish Target");
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
setIsSaving(true);
|
|
|
|
const description =
|
|
selectedFileType === "original"
|
|
? data.contentDescription
|
|
: data.contentRewriteDescription;
|
|
|
|
const requestData = {
|
|
spitId: id,
|
|
title: data.contentTitle,
|
|
description,
|
|
htmlDescription: description,
|
|
tags: tags.join(", "),
|
|
categoryId: selectedCategoryId,
|
|
publishedFor: publishedFor.join(","),
|
|
creator: data.contentCreator,
|
|
files: isUserMabesApprover
|
|
? getPlacementData("mabes")
|
|
: getPlacementData("polda"),
|
|
};
|
|
|
|
await convertSPIT(requestData);
|
|
|
|
MySwal.fire({
|
|
title: "Success",
|
|
text: "Data saved successfully",
|
|
icon: "success",
|
|
confirmButtonColor: "#3085d6",
|
|
}).then(() => {
|
|
loadDetail();
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to save:", error);
|
|
MySwal.fire({
|
|
title: "Error",
|
|
text: "Failed to save data",
|
|
icon: "error",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
const result = await MySwal.fire({
|
|
title: "Delete Content",
|
|
text: "Are you sure you want to delete this content?",
|
|
icon: "warning",
|
|
showCancelButton: true,
|
|
confirmButtonColor: "#dc3545",
|
|
cancelButtonColor: "#6c757d",
|
|
confirmButtonText: "Delete",
|
|
cancelButtonText: "Cancel",
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
try {
|
|
setIsDeleting(true);
|
|
await deleteSPIT(id);
|
|
|
|
MySwal.fire({
|
|
title: "Success",
|
|
text: "Content deleted successfully",
|
|
icon: "success",
|
|
confirmButtonColor: "#3085d6",
|
|
}).then(() => {
|
|
router.back();
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to delete:", error);
|
|
MySwal.fire({
|
|
title: "Error",
|
|
text: "Failed to delete content",
|
|
icon: "error",
|
|
confirmButtonColor: "#3085d6",
|
|
});
|
|
} finally {
|
|
setIsDeleting(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Loading state
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<div className="text-center">
|
|
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
|
|
<p className="text-muted-foreground">Loading form data...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto py-6 space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">
|
|
SPIT Convert Form
|
|
</h1>
|
|
<p className="text-muted-foreground">
|
|
Convert and manage your SPIT content
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" size="sm" onClick={() => router.back()}>
|
|
<XCircle className="h-4 w-4 mr-2" />
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="default"
|
|
color="destructive"
|
|
size="sm"
|
|
onClick={handleDelete}
|
|
disabled={isDeleting}
|
|
>
|
|
{isDeleting ? (
|
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
) : (
|
|
<Trash2 className="h-4 w-4 mr-2" />
|
|
)}
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Main Content */}
|
|
<div className="lg:col-span-3 space-y-6">
|
|
{/* Basic Information */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<FileText className="h-5 w-5" />
|
|
Basic Information
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Title */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="title">Title *</Label>
|
|
<Controller
|
|
control={control}
|
|
name="contentTitle"
|
|
render={({ field }) => (
|
|
<Input
|
|
id="title"
|
|
placeholder="Enter content title"
|
|
{...field}
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.contentTitle && (
|
|
<Alert variant="soft">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<AlertDescription>
|
|
{errors.contentTitle.message}
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
</div>
|
|
|
|
{/* Category */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="category">Category</Label>
|
|
<Select
|
|
value={selectedCategoryId?.toString()}
|
|
onValueChange={handleCategoryChange}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select category" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{categories.map((category) => (
|
|
<SelectItem
|
|
key={category.id}
|
|
value={category.id.toString()}
|
|
>
|
|
{category.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Content Editor */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Edit3 className="h-5 w-5" />
|
|
Content Editor
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-8">
|
|
{/* Pilih Upload Type */}
|
|
<div className="space-y-2">
|
|
<Label>Upload Type</Label>
|
|
<RadioGroup
|
|
value={selectedFileType}
|
|
onValueChange={(value: "original" | "rewrite") =>
|
|
setSelectedFileType(value)
|
|
}
|
|
className="grid grid-cols-2 gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="original" id="original" />
|
|
<Label htmlFor="original">Original Content</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="rewrite" id="rewrite" />
|
|
<Label htmlFor="rewrite">Rewritten Content</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
|
|
{/* Tampilkan keduanya berdampingan */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* Original Content */}
|
|
<div
|
|
className={`space-y-2 p-4 rounded-lg border ${
|
|
selectedFileType === "original"
|
|
? "border-blue-600"
|
|
: "border-gray-300"
|
|
}`}
|
|
>
|
|
<Label className="text-lg text-black">
|
|
Original Content
|
|
</Label>
|
|
<Controller
|
|
control={control}
|
|
name="contentDescription"
|
|
render={({ field }) => (
|
|
<CustomEditor
|
|
onChange={field.onChange}
|
|
initialData={field.value}
|
|
/>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Rewrite Content */}
|
|
<div
|
|
className={`space-y-4 p-4 rounded-lg border ${
|
|
selectedFileType === "rewrite"
|
|
? "border-blue-600"
|
|
: "border-gray-300"
|
|
}`}
|
|
>
|
|
<Label className="text-lg text-black">
|
|
Rewritten Content
|
|
</Label>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-2">
|
|
<Label>Writing Style</Label>
|
|
<Select
|
|
value={selectedWritingStyle}
|
|
onValueChange={setSelectedWritingStyle}
|
|
>
|
|
<SelectTrigger className="w-48">
|
|
<SelectValue placeholder="Select style" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{WRITING_STYLES.map((style) => (
|
|
<SelectItem key={style.value} value={style.value}>
|
|
{style.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
onClick={handleRewriteClick}
|
|
disabled={
|
|
isGeneratingRewrite || !detail?.contentDescription
|
|
}
|
|
className="bg-blue-600 hover:bg-blue-700"
|
|
>
|
|
{isGeneratingRewrite ? (
|
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
) : (
|
|
<Edit3 className="h-4 w-4 mr-2" />
|
|
)}
|
|
Generate Rewrite
|
|
</Button>
|
|
</div>
|
|
|
|
{showRewriteEditor && (
|
|
<div className="space-y-4">
|
|
{articleIds.length > 0 && (
|
|
<div className="flex gap-2">
|
|
{articleIds.map((articleId, index) => (
|
|
<Button
|
|
key={articleId}
|
|
type="button"
|
|
variant={
|
|
selectedArticleId === articleId
|
|
? "default"
|
|
: "outline"
|
|
}
|
|
size="sm"
|
|
onClick={() => handleArticleSelect(articleId)}
|
|
disabled={isLoadingRewrite}
|
|
>
|
|
{isLoadingRewrite &&
|
|
selectedArticleId === articleId && (
|
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
)}
|
|
Narrative {index + 1}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2">
|
|
<Label>Rewritten Content</Label>
|
|
<Controller
|
|
control={control}
|
|
name="contentRewriteDescription"
|
|
render={({ field }) => (
|
|
<CustomEditor
|
|
onChange={field.onChange}
|
|
initialData={articleBody || field.value}
|
|
/>
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Media Files */}
|
|
<div className="flex flex-col lg:flex-row gap-6">
|
|
<div className="flex-1 space-y-6">
|
|
{detailThumb.length > 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Image className="h-5 w-5" />
|
|
Media Files
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-4 overflow-hidden">
|
|
<Swiper
|
|
key={`main-swiper-${detailThumb.length}`}
|
|
thumbs={{ swiper: thumbsSwiper }}
|
|
modules={[FreeMode, Navigation, Thumbs]}
|
|
navigation={true}
|
|
className="w-full h-96"
|
|
spaceBetween={10}
|
|
loop={false}
|
|
watchSlidesProgress={true}
|
|
allowTouchMove={true}
|
|
resistance={true}
|
|
resistanceRatio={0.85}
|
|
preventClicks={false}
|
|
preventClicksPropagation={false}
|
|
slidesPerView={1}
|
|
centeredSlides={true}
|
|
>
|
|
{detailThumb.map((item) => (
|
|
<SwiperSlide
|
|
key={item.contentId}
|
|
className="!w-full"
|
|
>
|
|
{item.contentType === "VIDEO" ? (
|
|
<div className="relative w-full h-96 overflow-hidden rounded-lg">
|
|
<video
|
|
className="w-full h-full object-contain rounded-lg"
|
|
src={item.contentFile}
|
|
controls
|
|
playsInline
|
|
poster={item.thumbnailFileUrl || undefined}
|
|
title={`Video ${item.contentId}`}
|
|
onError={(e) => {
|
|
console.error("Video load error:", e);
|
|
e.currentTarget.style.display = "none";
|
|
}}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="w-full h-96 flex items-center justify-center overflow-hidden rounded-lg">
|
|
<img
|
|
src={item.contentFile}
|
|
alt={`Media ${item.contentId}`}
|
|
className="max-w-full max-h-full object-contain rounded-lg"
|
|
style={{
|
|
maxWidth: "100%",
|
|
maxHeight: "100%",
|
|
}}
|
|
onError={(e) => {
|
|
console.error("Image load error:", e);
|
|
e.currentTarget.style.display = "none";
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
</SwiperSlide>
|
|
))}
|
|
</Swiper>
|
|
|
|
<Swiper
|
|
key={`thumbs-swiper-${detailThumb.length}`}
|
|
onSwiper={setThumbsSwiper}
|
|
slidesPerView={8}
|
|
spaceBetween={8}
|
|
freeMode={true}
|
|
watchSlidesProgress={true}
|
|
modules={[FreeMode, Thumbs]}
|
|
breakpoints={{
|
|
320: {
|
|
slidesPerView: 3,
|
|
spaceBetween: 4,
|
|
},
|
|
640: {
|
|
slidesPerView: 5,
|
|
spaceBetween: 6,
|
|
},
|
|
768: {
|
|
slidesPerView: 6,
|
|
spaceBetween: 8,
|
|
},
|
|
1024: {
|
|
slidesPerView: 8,
|
|
spaceBetween: 8,
|
|
},
|
|
}}
|
|
className="w-full"
|
|
loop={false}
|
|
allowTouchMove={true}
|
|
resistance={true}
|
|
resistanceRatio={0.85}
|
|
preventClicks={false}
|
|
preventClicksPropagation={false}
|
|
>
|
|
{detailThumb.map((item) => (
|
|
<SwiperSlide
|
|
key={`thumb-${item.contentId}`}
|
|
className="!w-auto flex-shrink-0"
|
|
>
|
|
{item.contentType === "VIDEO" ? (
|
|
<div className="relative w-20 h-16 rounded cursor-pointer overflow-hidden flex-shrink-0">
|
|
{/* use preload metadata so browser doesn't download full video */}
|
|
<video
|
|
src={item.contentFile}
|
|
className="w-full h-full object-cover"
|
|
muted
|
|
preload="metadata"
|
|
playsInline
|
|
tabIndex={-1}
|
|
onError={(e) => {
|
|
console.error(
|
|
"Thumbnail video load error:",
|
|
e
|
|
);
|
|
e.currentTarget.style.display = "none";
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/30 pointer-events-none">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="w-4 h-4 text-white"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="w-20 h-16 rounded cursor-pointer overflow-hidden flex-shrink-0">
|
|
<img
|
|
src={item.contentFile}
|
|
alt={`Thumbnail ${item.contentId}`}
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
console.error(
|
|
"Thumbnail image load error:",
|
|
e
|
|
);
|
|
e.currentTarget.style.display = "none";
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
</SwiperSlide>
|
|
))}
|
|
</Swiper>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* File Placement */}
|
|
{files.length > 0 && isUserMabesApprover && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Globe className="h-5 w-5" />
|
|
File Placement
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{files.length > 1 && (
|
|
<div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
|
{PLACEMENT_OPTIONS.map((option) => (
|
|
<div
|
|
key={option.value}
|
|
className="flex items-center space-x-2"
|
|
>
|
|
<Checkbox
|
|
id={`select-all-${option.value}`}
|
|
onCheckedChange={(checked) =>
|
|
handleSelectAllPlacements(
|
|
option.value,
|
|
Boolean(checked)
|
|
)
|
|
}
|
|
/>
|
|
<Label
|
|
htmlFor={`select-all-${option.value}`}
|
|
className="text-sm"
|
|
>
|
|
All {option.label}
|
|
</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-4">
|
|
{files.map((file, index) => (
|
|
<div
|
|
key={file.contentId}
|
|
className="flex gap-4 p-4 border rounded-lg"
|
|
>
|
|
{file.contentType === "VIDEO" ? (
|
|
<video
|
|
src={file.contentFile}
|
|
className="w-32 h-24 object-cover rounded"
|
|
muted
|
|
preload="metadata"
|
|
playsInline
|
|
/>
|
|
) : (
|
|
<img
|
|
src={file.contentFile}
|
|
alt={file.fileName || `file-${file.contentId}`}
|
|
className="w-32 h-24 object-cover rounded"
|
|
/>
|
|
)}
|
|
|
|
<div className="flex-1 space-y-3">
|
|
<p className="font-medium text-sm">
|
|
{file.fileName ||
|
|
file.contentFileName ||
|
|
`File ${file.contentId}`}
|
|
</p>
|
|
<div className="flex flex-wrap gap-3">
|
|
{PLACEMENT_OPTIONS.map((option) => (
|
|
<div
|
|
key={option.value}
|
|
className="flex items-center space-x-2"
|
|
>
|
|
<Checkbox
|
|
id={`${file.contentId}-${option.value}`}
|
|
checked={filePlacements[index]?.includes(
|
|
option.value
|
|
)}
|
|
onCheckedChange={(checked) =>
|
|
handleFilePlacementChange(
|
|
index,
|
|
option.value,
|
|
Boolean(checked)
|
|
)
|
|
}
|
|
/>
|
|
<Label
|
|
htmlFor={`${file.contentId}-${option.value}`}
|
|
className="text-sm"
|
|
>
|
|
{option.label}
|
|
</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
|
|
{/* Sidebar */}
|
|
<div className="w-full lg:w-[30%] space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Users className="h-5 w-5" />
|
|
Creator Information
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="creator">Creator *</Label>
|
|
<Controller
|
|
control={control}
|
|
name="contentCreator"
|
|
render={({ field }) => (
|
|
<Input
|
|
id="creator"
|
|
placeholder="Enter creator name"
|
|
{...field}
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.contentCreator && (
|
|
<Alert variant="soft">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<AlertDescription>
|
|
{errors.contentCreator.message}
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Preview */}
|
|
{detail?.contentThumbnail && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Eye className="h-5 w-5" />
|
|
Preview
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<img
|
|
src={detail.contentThumbnail}
|
|
alt="Content thumbnail"
|
|
className="w-full h-auto rounded-lg"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Tags */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Tag className="h-5 w-5" />
|
|
Tags
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="tag-input">Add Tags</Label>
|
|
<Input
|
|
id="tag-input"
|
|
placeholder="Type a tag and press Enter"
|
|
onKeyDown={handleAddTag}
|
|
ref={inputRef}
|
|
/>
|
|
</div>
|
|
{tags.length > 0 && (
|
|
<div className="flex flex-wrap gap-2">
|
|
{tags.map((tag, index) => (
|
|
<Badge
|
|
key={index}
|
|
className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
|
|
onClick={() => handleRemoveTag(index)}
|
|
>
|
|
{tag}
|
|
<XCircle className="h-3 w-3 ml-1" />
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Publish Targets */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Settings className="h-5 w-5" />
|
|
Publish Targets
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{PUBLISH_OPTIONS.map((option) => (
|
|
<div
|
|
key={option.id}
|
|
className="flex items-center space-x-2"
|
|
>
|
|
<Checkbox
|
|
id={option.id}
|
|
checked={
|
|
option.id === "all"
|
|
? publishedFor.length ===
|
|
PUBLISH_OPTIONS.filter(
|
|
(opt) => opt.id !== "all"
|
|
).length
|
|
: publishedFor.includes(option.id)
|
|
}
|
|
onCheckedChange={() =>
|
|
handlePublishTargetChange(option.id)
|
|
}
|
|
/>
|
|
<Label htmlFor={option.id} className="text-sm">
|
|
{option.label}
|
|
</Label>
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Submit Button */}
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<Button
|
|
type="submit"
|
|
className="w-full mb-4"
|
|
disabled={isSubmitting || isSaving || isAlreadySaved}
|
|
>
|
|
{isSubmitting || isSaving ? (
|
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
) : (
|
|
<Save className="h-4 w-4 mr-2" />
|
|
)}
|
|
{isAlreadySaved
|
|
? "Already Saved"
|
|
: isSubmitting || isSaving
|
|
? "Saving..."
|
|
: "Save Changes"}
|
|
</Button>
|
|
|
|
{isAlreadySaved && (
|
|
<Alert variant="soft">
|
|
<CheckCircle className="h-4 w-4 text-red-500" />
|
|
<AlertDescription className="text-red-500">
|
|
Konten sudah disimpan. Anda tidak dapat menyimpan
|
|
ulang.
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// {spit with new layout file placements}
|
|
// "use client";
|
|
// import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
|
// import { useForm, Controller } from "react-hook-form";
|
|
// import { zodResolver } from "@hookform/resolvers/zod";
|
|
// import * as z from "zod";
|
|
// import { useParams, useRouter } from "next/navigation";
|
|
// import { useTranslations } from "next-intl";
|
|
// import dynamic from "next/dynamic";
|
|
// import Cookies from "js-cookie";
|
|
// import { Input } from "@/components/ui/input";
|
|
// import { Button } from "@/components/ui/button";
|
|
// import { Label } from "@/components/ui/label";
|
|
// import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
// import { Badge } from "@/components/ui/badge";
|
|
// import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
// import {
|
|
// Select,
|
|
// SelectContent,
|
|
// SelectItem,
|
|
// SelectTrigger,
|
|
// SelectValue,
|
|
// } from "@/components/ui/select";
|
|
// import { Checkbox } from "@/components/ui/checkbox";
|
|
// import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
// import {
|
|
// AlertCircle,
|
|
// FileText,
|
|
// Image,
|
|
// Loader2,
|
|
// Save,
|
|
// Trash2,
|
|
// Edit3,
|
|
// Globe,
|
|
// Users,
|
|
// Tag,
|
|
// Eye,
|
|
// Settings,
|
|
// CheckCircle,
|
|
// XCircle,
|
|
// } from "lucide-react";
|
|
// import { Swiper, SwiperSlide } from "swiper/react";
|
|
// import { Swiper as SwiperType } from "swiper";
|
|
// import "swiper/css";
|
|
// import "swiper/css/free-mode";
|
|
// import "swiper/css/navigation";
|
|
// import "swiper/css/pagination";
|
|
// import "swiper/css/thumbs";
|
|
// import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
|
// import {
|
|
// convertSPIT,
|
|
// deleteSPIT,
|
|
// detailSPIT,
|
|
// getTagsBySubCategoryId,
|
|
// listEnableCategory,
|
|
// listEnableCategoryNew,
|
|
// updateFilePlacements,
|
|
// } from "@/service/content/content";
|
|
// import { generateDataRewrite, getDetailArticle } from "@/service/content/ai";
|
|
// import { getCookiesDecrypt } from "@/lib/utils";
|
|
// import { error } from "@/lib/swal";
|
|
// import Swal from "sweetalert2";
|
|
// import withReactContent from "sweetalert2-react-content";
|
|
// import { close, loading } from "@/config/swal";
|
|
// import {
|
|
// Dialog,
|
|
// DialogClose,
|
|
// DialogContent,
|
|
// DialogHeader,
|
|
// DialogTitle,
|
|
// DialogTrigger,
|
|
// } from "@/components/ui/dialog";
|
|
// import { Icon } from "@/components/ui/icon";
|
|
// import { getUserLevelForAssignments } from "@/service/task";
|
|
|
|
// interface Category {
|
|
// id: number;
|
|
// name: string;
|
|
// }
|
|
|
|
// interface Detail {
|
|
// id: string;
|
|
// contentTitle: string;
|
|
// contentDescription: string;
|
|
// slug: string;
|
|
// content: {
|
|
// id: number;
|
|
// name: string;
|
|
// };
|
|
// contentCreator: string;
|
|
// creatorName: string;
|
|
// contentThumbnail: string;
|
|
// contentTag: string;
|
|
// categoryId?: number;
|
|
// contentList?: FileType[]; // keep as FileType[]
|
|
// }
|
|
|
|
// interface Option {
|
|
// id: string;
|
|
// label: string;
|
|
// }
|
|
|
|
// interface Destination {
|
|
// id: string;
|
|
// name: string;
|
|
// subDestination?: SubDestination[];
|
|
// }
|
|
|
|
// interface SubDestination {
|
|
// id: string;
|
|
// name: string;
|
|
// }
|
|
|
|
// type PlacementType =
|
|
// | "all"
|
|
// | "mabes"
|
|
// | "polda"
|
|
// | "international"
|
|
// | "wilayah"
|
|
// | "nasional"
|
|
// | "satker"
|
|
// | string;
|
|
|
|
// interface TempFileItem {
|
|
// id: number | string;
|
|
// }
|
|
|
|
// // Updated FileType to reflect API response
|
|
// interface FileType {
|
|
// contentId: number;
|
|
// contentFile: string; // URL
|
|
// contentType: "IMAGE" | "VIDEO" | string;
|
|
// contentFileRawType?: string; // e.g. video/mp4 or image/jpeg
|
|
// contentFileName?: string | null;
|
|
// thumbnailFileUrl?: string | null;
|
|
// fileName?: string | null;
|
|
// }
|
|
|
|
// interface PlacementData {
|
|
// mediaFileId: number;
|
|
// placements: string;
|
|
// }
|
|
|
|
// // Schema
|
|
// const formSchema = z.object({
|
|
// contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
|
|
// contentDescription: z.string().optional(),
|
|
// contentRewriteDescription: z.string().optional(),
|
|
// contentCreator: z.string().min(1, { message: "Creator diperlukan" }),
|
|
// });
|
|
|
|
// type FormData = z.infer<typeof formSchema>;
|
|
|
|
// // Constants
|
|
// const PUBLISH_OPTIONS: Option[] = [
|
|
// { id: "all", label: "SEMUA" },
|
|
// { id: "5", label: "UMUM" },
|
|
// { id: "6", label: "JOURNALIS" },
|
|
// { id: "7", label: "POLRI" },
|
|
// { id: "8", label: "KSP" },
|
|
// ];
|
|
|
|
// const WRITING_STYLES = [
|
|
// { value: "friendly", label: "Friendly" },
|
|
// { value: "professional", label: "Profesional" },
|
|
// { value: "informational", label: "Informational" },
|
|
// { value: "neutral", label: "Neutral" },
|
|
// { value: "witty", label: "Witty" },
|
|
// ];
|
|
|
|
// const PLACEMENT_OPTIONS = [
|
|
// { value: "semua", label: "Semua" },
|
|
// { value: "nasional", label: "Nasional" },
|
|
// { value: "wilayah", label: "Wilayah" },
|
|
// { value: "international", label: "Internasional" },
|
|
// ];
|
|
|
|
// // Dynamic imports
|
|
// const CustomEditor = dynamic(
|
|
// () => import("@/components/editor/custom-editor"),
|
|
// {
|
|
// ssr: false,
|
|
// loading: () => (
|
|
// <div className="flex items-center justify-center h-32 border rounded-md bg-muted/50">
|
|
// <Loader2 className="h-6 w-6 animate-spin" />
|
|
// <span className="ml-2">Loading editor...</span>
|
|
// </div>
|
|
// ),
|
|
// }
|
|
// );
|
|
|
|
// export default function FormConvertSPIT() {
|
|
// const MySwal = withReactContent(Swal);
|
|
// const router = useRouter();
|
|
// const t = useTranslations("Form");
|
|
// const { id } = useParams() as { id: string };
|
|
// const [isAlreadySaved, setIsAlreadySaved] = useState(false);
|
|
// const {
|
|
// control,
|
|
// handleSubmit,
|
|
// setValue,
|
|
// formState: { errors, isSubmitting },
|
|
// watch,
|
|
// } = useForm<FormData>({
|
|
// resolver: zodResolver(formSchema),
|
|
// defaultValues: {
|
|
// contentTitle: "",
|
|
// contentDescription: "",
|
|
// contentCreator: "",
|
|
// contentRewriteDescription: "",
|
|
// },
|
|
// });
|
|
|
|
// // Component state
|
|
// const [isLoading, setIsLoading] = useState(true);
|
|
// const [isSaving, setIsSaving] = useState(false);
|
|
// const [isDeleting, setIsDeleting] = useState(false);
|
|
// const [detail, setDetail] = useState<Detail | null>(null);
|
|
// const [categories, setCategories] = useState<Category[]>([]);
|
|
// const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(
|
|
// null
|
|
// );
|
|
// const [selectedFileType, setSelectedFileType] = useState<
|
|
// "original" | "rewrite"
|
|
// >("original");
|
|
// const [selectedWritingStyle, setSelectedWritingStyle] =
|
|
// useState("professional");
|
|
// const [showRewriteEditor, setShowRewriteEditor] = useState(false);
|
|
// const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false);
|
|
// const [isLoadingRewrite, setIsLoadingRewrite] = useState(false);
|
|
// // <-- changed: detailThumb is now FileType[] (objects from API) -->
|
|
// const [detailThumb, setDetailThumb] = useState<FileType[]>([]);
|
|
// const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
|
// const [files, setFiles] = useState<FileType[]>([]);
|
|
// const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
|
// const [articleIds, setArticleIds] = useState<string[]>([]);
|
|
// const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
|
// null
|
|
// );
|
|
// const [articleBody, setArticleBody] = useState<string>("");
|
|
// const [tags, setTags] = useState<string[]>([]);
|
|
// const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
|
// const [inputRef] = useState(useRef<HTMLInputElement>(null));
|
|
// const userLevelId = getCookiesDecrypt("ulie");
|
|
// const roleId = getCookiesDecrypt("urie");
|
|
// const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
|
// const [fileUnitSelections, setFileUnitSelections] = useState<
|
|
// Array<{
|
|
// semua: boolean;
|
|
// nasional: boolean;
|
|
// wilayah: boolean;
|
|
// international: boolean;
|
|
// polda: boolean;
|
|
// satker: boolean;
|
|
// }>
|
|
// >([]);
|
|
// const [unitSelection, setUnitSelection] = useState({
|
|
// semua: false,
|
|
// nasional: false,
|
|
// wilayah: false,
|
|
// international: false,
|
|
// polda: false,
|
|
// satker: false,
|
|
// });
|
|
// const [fileCheckedLevels, setFileCheckedLevels] = useState<
|
|
// Array<Set<number>>
|
|
// >([]);
|
|
// const [listDest, setListDest] = useState<Destination[]>([]);
|
|
// const [isDetailOfRegionShowed, setIsDetailOfRegionShowed] = useState(false);
|
|
// const [isUpdatingFromMainCheckbox, setIsUpdatingFromMainCheckbox] =
|
|
// useState(false);
|
|
// const [mainCheckboxChangeType, setMainCheckboxChangeType] =
|
|
// useState<string>("");
|
|
// const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
|
|
// const [expandedPolda, setExpandedPolda] = useState<Record<number, boolean>>(
|
|
// {}
|
|
// );
|
|
|
|
// useEffect(() => {
|
|
// async function fetchPoldaPolres() {
|
|
// setIsLoading(true);
|
|
// try {
|
|
// const response = await getUserLevelForAssignments();
|
|
// setListDest(response?.data?.data.list);
|
|
// const initialExpandedState = response?.data?.data.list.reduce(
|
|
// (acc: any, polda: any) => {
|
|
// acc[polda.id] = false;
|
|
// return acc;
|
|
// },
|
|
// {}
|
|
// );
|
|
// setExpandedPolda(initialExpandedState);
|
|
// console.log("polres", initialExpandedState);
|
|
// } catch (error) {
|
|
// console.error("Error fetching Polda/Polres data:", error);
|
|
// } finally {
|
|
// setIsLoading(false);
|
|
// }
|
|
// }
|
|
// fetchPoldaPolres();
|
|
// }, []);
|
|
|
|
// // useEffect untuk sinkronisasi checkbox modal dengan checkbox utama
|
|
// useEffect(() => {
|
|
// if (
|
|
// listDest.length > 0 &&
|
|
// isUpdatingFromMainCheckbox &&
|
|
// mainCheckboxChangeType
|
|
// ) {
|
|
// syncModalWithMainCheckbox();
|
|
// }
|
|
// }, [isUpdatingFromMainCheckbox, mainCheckboxChangeType]);
|
|
|
|
// // useEffect untuk update checkbox utama ketika pilihan modal berubah
|
|
// useEffect(() => {
|
|
// if (!isUpdatingFromMainCheckbox && listDest.length > 0) {
|
|
// updateMainCheckboxFromModalLegacy();
|
|
// }
|
|
// }, [checkedLevels, isUpdatingFromMainCheckbox]);
|
|
|
|
// useEffect(() => {
|
|
// initializeComponent();
|
|
// }, []);
|
|
|
|
// useEffect(() => {
|
|
// checkUserPermissions();
|
|
// }, [userLevelId, roleId]);
|
|
|
|
// // Handle Swiper re-initialization when detailThumb changes
|
|
// useEffect(() => {
|
|
// if (thumbsSwiper && detailThumb.length > 0) {
|
|
// thumbsSwiper.update();
|
|
// }
|
|
// }, [detailThumb, thumbsSwiper]);
|
|
|
|
// const initializeComponent = async () => {
|
|
// try {
|
|
// setIsLoading(true);
|
|
// await Promise.all([
|
|
// loadCategories(),
|
|
// id ? loadDetail() : Promise.resolve(),
|
|
// ]);
|
|
// } catch (error) {
|
|
// console.error("Failed to initialize component:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to load data. Please try again.",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsLoading(false);
|
|
// }
|
|
// };
|
|
|
|
// const checkUserPermissions = () => {
|
|
// if (userLevelId === "216" && roleId === "3") {
|
|
// setIsUserMabesApprover(true);
|
|
// }
|
|
// };
|
|
|
|
// const loadCategories = async () => {
|
|
// try {
|
|
// const response = await listEnableCategoryNew("1", true);
|
|
// const categories = response?.data?.data?.content || [];
|
|
// setCategories(categories);
|
|
|
|
// const scheduleId = Cookies.get("scheduleId");
|
|
// const scheduleType = Cookies.get("scheduleType");
|
|
|
|
// if (scheduleId && scheduleType === "3") {
|
|
// const persRilisCategory = categories.find((cat: Category) =>
|
|
// cat.name.toLowerCase().includes("pers rilis")
|
|
// );
|
|
// if (persRilisCategory) {
|
|
// setSelectedCategoryId(persRilisCategory.id);
|
|
// await loadTags(persRilisCategory.id);
|
|
// }
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load categories:", error);
|
|
// }
|
|
// };
|
|
|
|
// const loadTags = async (categoryId: number) => {
|
|
// try {
|
|
// const response = await getTagsBySubCategoryId(categoryId);
|
|
// if (response?.data?.data?.length > 0) {
|
|
// const apiTags = response?.data?.data?.map((tag: any) => tag.tagName);
|
|
// const tagsMerge = Array.from(new Set([...tags, ...apiTags]));
|
|
// setTags(tagsMerge);
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load tags:", error);
|
|
// }
|
|
// };
|
|
|
|
// const loadDetail = async () => {
|
|
// loading();
|
|
// try {
|
|
// const response = await detailSPIT(id);
|
|
// const details = response?.data?.data;
|
|
// setIsAlreadySaved(details?.isPublish ? true : false);
|
|
|
|
// if (!details) {
|
|
// throw new Error("Detail not found");
|
|
// }
|
|
|
|
// setDetail(details);
|
|
|
|
// const contentList: FileType[] = (details.contentList || []).map(
|
|
// (it: any) => ({
|
|
// contentId: it.contentId,
|
|
// contentFile: it.contentFile,
|
|
// contentType: it.contentType,
|
|
// contentFileRawType: it.contentFileRawType,
|
|
// contentFileName: it.contentFileName,
|
|
// thumbnailFileUrl: it.thumbnailFileUrl || null,
|
|
// fileName: it.contentFileName || null,
|
|
// })
|
|
// );
|
|
|
|
// setFiles(contentList);
|
|
// setDetailThumb(contentList);
|
|
|
|
// const fileCount = contentList.length || 0;
|
|
// setFilePlacements(Array(fileCount).fill([]));
|
|
|
|
// setValue("contentTitle", details.contentTitle || "");
|
|
// setValue("contentDescription", details.contentDescription || "");
|
|
// setValue("contentCreator", details.contentCreator || "");
|
|
// setValue(
|
|
// "contentRewriteDescription",
|
|
// (details as any).contentRewriteDescription || ""
|
|
// );
|
|
|
|
// if (details.categoryId) {
|
|
// setSelectedCategoryId(details.categoryId);
|
|
// await loadTags(details.categoryId);
|
|
// }
|
|
|
|
// if (details.contentTag) {
|
|
// const contentTags = details.contentTag
|
|
// .split(",")
|
|
// .map((tag: string) => tag.trim());
|
|
// setTags((prev) => Array.from(new Set([...prev, ...contentTags])));
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load detail:", error);
|
|
// throw error;
|
|
// }
|
|
// close();
|
|
// };
|
|
|
|
// const handleCategoryChange = async (categoryId: string) => {
|
|
// const id = Number(categoryId);
|
|
// setSelectedCategoryId(id);
|
|
// await loadTags(id);
|
|
// };
|
|
|
|
// const handleRewriteClick = async () => {
|
|
// if (!detail?.contentDescription) {
|
|
// MySwal.fire({
|
|
// title: "Warning",
|
|
// text: "Please add content description first",
|
|
// icon: "warning",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// return;
|
|
// }
|
|
|
|
// try {
|
|
// setIsGeneratingRewrite(true);
|
|
// const request = {
|
|
// style: selectedWritingStyle,
|
|
// lang: "id",
|
|
// contextType: "text",
|
|
// urlContext: null,
|
|
// context: detail.contentDescription,
|
|
// createdBy: roleId,
|
|
// sentiment: "Humorous",
|
|
// clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
|
|
// };
|
|
|
|
// const response = await generateDataRewrite(request);
|
|
// console.log("REWRITE RESPONSE:", response);
|
|
|
|
// if (response?.error) {
|
|
// throw new Error(response.message);
|
|
// }
|
|
|
|
// const newArticleId = response?.data?.data?.id;
|
|
// setArticleIds((prev) => {
|
|
// const updated = [...prev];
|
|
// if (updated.length < 3) {
|
|
// updated.push(newArticleId);
|
|
// } else {
|
|
// updated[2] = newArticleId;
|
|
// }
|
|
// return updated;
|
|
// });
|
|
|
|
// setShowRewriteEditor(true);
|
|
// MySwal.fire({
|
|
// title: "Success",
|
|
// text: "Content rewrite generated successfully",
|
|
// icon: "success",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Failed to generate rewrite:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to generate content rewrite",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsGeneratingRewrite(false);
|
|
// }
|
|
// };
|
|
|
|
// const handleArticleSelect = async (articleId: string) => {
|
|
// try {
|
|
// setIsLoadingRewrite(true);
|
|
// setSelectedArticleId(articleId);
|
|
|
|
// let retryCount = 0;
|
|
// const maxRetries = 20;
|
|
|
|
// while (retryCount < maxRetries) {
|
|
// const response = await getDetailArticle(articleId);
|
|
// const articleData = response?.data?.data;
|
|
|
|
// if (articleData?.status === 2) {
|
|
// const cleanArticleBody = articleData.articleBody?.replace(
|
|
// /<img[^>]*>/g,
|
|
// ""
|
|
// );
|
|
// setArticleBody(cleanArticleBody || "");
|
|
// setValue("contentRewriteDescription", cleanArticleBody || "");
|
|
// break;
|
|
// }
|
|
|
|
// retryCount++;
|
|
// await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
// }
|
|
|
|
// if (retryCount >= maxRetries) {
|
|
// throw new Error("Timeout: Article processing took too long");
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load article:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to load article content",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsLoadingRewrite(false);
|
|
// }
|
|
// };
|
|
|
|
// const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
// if (e.key === "Enter" && inputRef.current?.value.trim()) {
|
|
// e.preventDefault();
|
|
// const newTag = inputRef.current.value.trim();
|
|
|
|
// if (!tags.includes(newTag)) {
|
|
// setTags((prev) => [...prev, newTag]);
|
|
// }
|
|
|
|
// inputRef.current.value = "";
|
|
// }
|
|
// };
|
|
|
|
// const handleRemoveTag = (index: number) => {
|
|
// setTags((prev) => prev.filter((_, i) => i !== index));
|
|
// };
|
|
|
|
// const handlePublishTargetChange = (optionId: string) => {
|
|
// if (optionId === "all") {
|
|
// setPublishedFor((prev) =>
|
|
// prev.length === PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").length
|
|
// ? []
|
|
// : PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").map(
|
|
// (opt) => opt.id
|
|
// )
|
|
// );
|
|
// } else {
|
|
// setPublishedFor((prev) =>
|
|
// prev.includes(optionId)
|
|
// ? prev.filter((id) => id !== optionId && id !== "all")
|
|
// : [...prev.filter((id) => id !== "all"), optionId]
|
|
// );
|
|
// }
|
|
// };
|
|
|
|
// const handleFilePlacementChange = (
|
|
// fileIndex: number,
|
|
// placement: string,
|
|
// checked: boolean
|
|
// ) => {
|
|
// setFilePlacements((prev) => {
|
|
// const updated = [...prev];
|
|
// const currentPlacements = updated[fileIndex] || [];
|
|
|
|
// if (checked) {
|
|
// if (placement === "all") {
|
|
// updated[fileIndex] = ["all", "mabes", "polda", "international"];
|
|
// } else {
|
|
// const newPlacements = [...currentPlacements, placement];
|
|
// if (newPlacements.length === 3 && !newPlacements.includes("all")) {
|
|
// newPlacements.push("all");
|
|
// }
|
|
// updated[fileIndex] = newPlacements;
|
|
// }
|
|
// } else {
|
|
// if (placement === "all") {
|
|
// updated[fileIndex] = [];
|
|
// } else {
|
|
// const newPlacements = currentPlacements.filter(
|
|
// (p) => p !== placement
|
|
// );
|
|
// if (newPlacements.length === 3 && newPlacements.includes("all")) {
|
|
// updated[fileIndex] = newPlacements.filter((p) => p !== "all");
|
|
// } else {
|
|
// updated[fileIndex] = newPlacements;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return updated;
|
|
// });
|
|
// };
|
|
|
|
// const handleSelectAllPlacements = (placement: string, checked: boolean) => {
|
|
// setFilePlacements((prev) =>
|
|
// prev.map((filePlacements) =>
|
|
// checked
|
|
// ? Array.from(new Set([...filePlacements, placement]))
|
|
// : filePlacements.filter((p) => p !== placement)
|
|
// )
|
|
// );
|
|
// };
|
|
|
|
// const getPlacementData = () => {
|
|
// return files.map((file, index) => {
|
|
// const fid = index;
|
|
|
|
// // ✅ LEVEL CHECKBOX (POLDA, SATKER, dst.)
|
|
// const levelSet = fileCheckedLevels[fid]
|
|
// ? Array.from(fileCheckedLevels[fid])
|
|
// : [];
|
|
|
|
// // ✅ PLACEMENTS UTAMA (nasional, wilayah, international)
|
|
// const unit = fileUnitSelections[fid] || {
|
|
// nasional: false,
|
|
// wilayah: false,
|
|
// international: false,
|
|
// polda: false,
|
|
// satker: false,
|
|
// semua: false,
|
|
// };
|
|
|
|
// let placements: string[] = [];
|
|
|
|
// // ✅ Map utama
|
|
// if (unit.nasional) placements.push("mabes");
|
|
// // if (unit.wilayah) placements.push("polda");
|
|
// if (unit.international) placements.push("international");
|
|
// if (unit.polda) placements.push("polda");
|
|
// if (unit.satker) placements.push("satker");
|
|
|
|
// placements = Array.from(new Set(placements));
|
|
|
|
// return {
|
|
// mediaFileId: file.contentId,
|
|
// placements: placements.join(","), // ✅ "polda,satker"
|
|
// customLocationPlacements: levelSet.join(","), // ✅ "219,220,221,..."
|
|
// };
|
|
// });
|
|
// };
|
|
|
|
// const checkPlacement = (data: any) => {
|
|
// let temp = true;
|
|
// for (const element of data) {
|
|
// if (element.length < 1) {
|
|
// temp = false;
|
|
// break;
|
|
// }
|
|
// }
|
|
|
|
// return temp;
|
|
// };
|
|
|
|
// const onSubmit = async (data: FormData) => {
|
|
// const pnmhTags = tags.filter((tag) => tag.toLowerCase().includes("pnmh"));
|
|
|
|
// if (pnmhTags.length > 1) {
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Tags penugasan hanya diperbolehkan 1 (satu) saja.",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// return false;
|
|
// }
|
|
|
|
// // if (!checkPlacement(filePlacements)) {
|
|
// // error("Select File Placement");
|
|
// // return false;
|
|
// // }
|
|
|
|
// if (!selectedCategoryId) {
|
|
// error("Select a category");
|
|
// return false;
|
|
// }
|
|
|
|
// if (publishedFor.length < 1) {
|
|
// error("Select Publish Target");
|
|
// return false;
|
|
// }
|
|
|
|
// try {
|
|
// setIsSaving(true);
|
|
|
|
// const description =
|
|
// selectedFileType === "original"
|
|
// ? data.contentDescription
|
|
// : data.contentRewriteDescription;
|
|
|
|
// const requestData = {
|
|
// spitId: id,
|
|
// title: data.contentTitle,
|
|
// description,
|
|
// htmlDescription: description,
|
|
// tags: tags.join(", "),
|
|
// categoryId: selectedCategoryId,
|
|
// publishedFor: publishedFor.join(","),
|
|
// creator: data.contentCreator,
|
|
// files: getPlacementData(),
|
|
// };
|
|
|
|
// await convertSPIT(requestData);
|
|
|
|
// // update placements (CRITICAL)
|
|
// const placementsPayload = buildPlacementsPayload();
|
|
// const responseFilePlacements = await updateFilePlacements(
|
|
// placementsPayload
|
|
// );
|
|
// if (responseFilePlacements?.error) {
|
|
// close();
|
|
// error(responseFilePlacements?.message);
|
|
// return false;
|
|
// }
|
|
|
|
// MySwal.fire({
|
|
// title: "Success",
|
|
// text: "Data saved successfully",
|
|
// icon: "success",
|
|
// confirmButtonColor: "#3085d6",
|
|
// }).then(() => {
|
|
// loadDetail();
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Failed to save:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to save data",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsSaving(false);
|
|
// }
|
|
|
|
// handleFileUnitChange;
|
|
// };
|
|
|
|
// const handleDelete = async () => {
|
|
// const result = await MySwal.fire({
|
|
// title: "Delete Content",
|
|
// text: "Are you sure you want to delete this content?",
|
|
// icon: "warning",
|
|
// showCancelButton: true,
|
|
// confirmButtonColor: "#dc3545",
|
|
// cancelButtonColor: "#6c757d",
|
|
// confirmButtonText: "Delete",
|
|
// cancelButtonText: "Cancel",
|
|
// });
|
|
|
|
// if (result.isConfirmed) {
|
|
// try {
|
|
// setIsDeleting(true);
|
|
// await deleteSPIT(id);
|
|
|
|
// MySwal.fire({
|
|
// title: "Success",
|
|
// text: "Content deleted successfully",
|
|
// icon: "success",
|
|
// confirmButtonColor: "#3085d6",
|
|
// }).then(() => {
|
|
// router.back();
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Failed to delete:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to delete content",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsDeleting(false);
|
|
// }
|
|
// }
|
|
// };
|
|
|
|
// // Loading state
|
|
// if (isLoading) {
|
|
// return (
|
|
// <div className="flex items-center justify-center min-h-[400px]">
|
|
// <div className="text-center">
|
|
// <Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
|
|
// <p className="text-muted-foreground">Loading form data...</p>
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// }
|
|
|
|
// const handleFileUnitChange = (
|
|
// fileIndex: number,
|
|
// key: keyof typeof unitSelection,
|
|
// value: boolean
|
|
// ) => {
|
|
// setFileUnitSelections((prev) => {
|
|
// const newSelections = [...prev];
|
|
// const currentSelection = { ...newSelections[fileIndex] };
|
|
|
|
// if (key === "semua") {
|
|
// // Jika klik Semua, set semua value ke true/false
|
|
// currentSelection.semua = value;
|
|
// currentSelection.nasional = value;
|
|
// currentSelection.wilayah = value;
|
|
// currentSelection.international = value;
|
|
// currentSelection.polda = value;
|
|
// currentSelection.satker = value;
|
|
|
|
// // Update fileCheckedLevels untuk sinkronisasi dengan modal
|
|
// setFileCheckedLevels((prevLevels) => {
|
|
// const newArray = [...prevLevels];
|
|
// const currentFileLevels = new Set<number>(
|
|
// newArray[fileIndex] || new Set()
|
|
// );
|
|
|
|
// if (value) {
|
|
// // Checklist semua item di modal
|
|
// listDest.forEach((item: any) => {
|
|
// currentFileLevels.add(Number(item.id));
|
|
// if (item.subDestination) {
|
|
// item.subDestination.forEach((sub: any) => {
|
|
// currentFileLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
// });
|
|
// } else {
|
|
// // Unchecklist semua item di modal
|
|
// currentFileLevels.clear();
|
|
// }
|
|
|
|
// newArray[fileIndex] = currentFileLevels;
|
|
// return newArray;
|
|
// });
|
|
// } else {
|
|
// // Jika wilayah dicentang, auto centang POLDA, SATKER
|
|
// if (key === "wilayah") {
|
|
// currentSelection.wilayah = value;
|
|
// setIsDetailOfRegionShowed(value);
|
|
|
|
// if (value) {
|
|
// // Ketika wilayah dicentang, auto centang POLDA, SATKER
|
|
// currentSelection.polda = true;
|
|
// currentSelection.satker = true;
|
|
|
|
// // Update fileCheckedLevels untuk mengisi semua POLDA dan SATKER POLRI ketika wilayah dicentang
|
|
// setFileCheckedLevels((prevLevels) => {
|
|
// const newArray = [...prevLevels];
|
|
// const currentFileLevels = new Set<number>(
|
|
// newArray[fileIndex] || new Set()
|
|
// );
|
|
|
|
// // Checklist semua POLDA di modal
|
|
// listDest.forEach((item: any) => {
|
|
// if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
|
// currentFileLevels.add(Number(item.id));
|
|
// }
|
|
// });
|
|
|
|
// // Checklist SATKER POLRI dan semua sub-itemsnya
|
|
// const satkerItem = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// if (satkerItem) {
|
|
// currentFileLevels.add(Number(satkerItem.id));
|
|
// // Checklist semua sub-items di bawah SATKER POLRI
|
|
// if (satkerItem.subDestination) {
|
|
// satkerItem.subDestination.forEach((sub: any) => {
|
|
// currentFileLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// newArray[fileIndex] = currentFileLevels;
|
|
// return newArray;
|
|
// });
|
|
// } else {
|
|
// // Ketika wilayah di-uncheck, uncheck POLDA, SATKER dan hapus data POLDA dari fileCheckedLevels
|
|
// currentSelection.polda = false;
|
|
// currentSelection.satker = false;
|
|
|
|
// // Update fileCheckedLevels untuk menghapus semua POLDA dan SATKER POLRI ketika wilayah di-uncheck
|
|
// setFileCheckedLevels((prevLevels) => {
|
|
// const newArray = [...prevLevels];
|
|
// const currentFileLevels = new Set<number>(
|
|
// newArray[fileIndex] || new Set()
|
|
// );
|
|
|
|
// // Hapus semua POLDA dari modal
|
|
// listDest.forEach((item: any) => {
|
|
// if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
|
// currentFileLevels.delete(Number(item.id));
|
|
// }
|
|
// });
|
|
|
|
// // Hapus SATKER POLRI dan semua sub-itemsnya
|
|
// const satkerItem = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// if (satkerItem) {
|
|
// currentFileLevels.delete(Number(satkerItem.id));
|
|
// // Hapus semua sub-items di bawah SATKER POLRI
|
|
// if (satkerItem.subDestination) {
|
|
// satkerItem.subDestination.forEach((sub: any) => {
|
|
// currentFileLevels.delete(Number(sub.id));
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// newArray[fileIndex] = currentFileLevels;
|
|
// return newArray;
|
|
// });
|
|
// }
|
|
// } else {
|
|
// // Update salah satu saja
|
|
// currentSelection[key] = value;
|
|
// }
|
|
|
|
// // Cek apakah semua selain "semua" sudah dicentang
|
|
// const allChecked = [
|
|
// "nasional",
|
|
// "wilayah",
|
|
// "international",
|
|
// "polda",
|
|
// "satker",
|
|
// ].every((k) => currentSelection[k as keyof typeof unitSelection]);
|
|
|
|
// currentSelection.semua = allChecked;
|
|
// }
|
|
|
|
// newSelections[fileIndex] = currentSelection;
|
|
// return newSelections;
|
|
// });
|
|
// };
|
|
|
|
// const setupPlacement = (
|
|
// index: number,
|
|
// placement: string,
|
|
// checked: boolean
|
|
// ) => {
|
|
// let temp = [...filePlacements];
|
|
// if (checked) {
|
|
// if (placement === "all") {
|
|
// temp[index] = ["all", "mabes", "polda", "international"];
|
|
// setFileCheckedLevels((prevLevels) => {
|
|
// const newArray = [...prevLevels];
|
|
// const currentFileLevels = new Set<number>(
|
|
// newArray[index] || new Set()
|
|
// );
|
|
// listDest.forEach((item: any) => {
|
|
// currentFileLevels.add(Number(item.id));
|
|
// if (item.subDestination) {
|
|
// item.subDestination.forEach((sub: any) => {
|
|
// currentFileLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
// });
|
|
// newArray[index] = currentFileLevels;
|
|
// return newArray;
|
|
// });
|
|
// setFileUnitSelections((prevSelections) => {
|
|
// const newSelections = [...prevSelections];
|
|
// const currentSelection = { ...newSelections[index] };
|
|
// currentSelection.nasional = true;
|
|
// currentSelection.wilayah = true;
|
|
// currentSelection.international = true;
|
|
// currentSelection.polda = true;
|
|
// // currentSelection.polres = true;
|
|
// currentSelection.satker = true;
|
|
// currentSelection.semua = true;
|
|
|
|
// newSelections[index] = currentSelection;
|
|
// return newSelections;
|
|
// });
|
|
// } else if (placement === "satker") {
|
|
// const now = temp[index] || [];
|
|
// if (!now.includes("satker")) {
|
|
// now.push("satker");
|
|
// }
|
|
// temp[index] = now;
|
|
// } else {
|
|
// const now = temp[index] || [];
|
|
// if (!now.includes(placement)) {
|
|
// now.push(placement);
|
|
// }
|
|
// const nonSatkerItems = now.filter(
|
|
// (item) => item !== "satker" && item !== "all"
|
|
// );
|
|
// if (nonSatkerItems.length === 3 && !now.includes("all")) {
|
|
// now.push("all");
|
|
// }
|
|
// temp[index] = now;
|
|
// }
|
|
// } else {
|
|
// if (placement === "all") {
|
|
// temp[index] = [];
|
|
// setFileCheckedLevels((prevLevels) => {
|
|
// const newArray = [...prevLevels];
|
|
// const currentFileLevels = new Set<number>(
|
|
// newArray[index] || new Set()
|
|
// );
|
|
// currentFileLevels.clear();
|
|
|
|
// newArray[index] = currentFileLevels;
|
|
// return newArray;
|
|
// });
|
|
|
|
// // Update fileUnitSelections untuk checkbox tingkat utama
|
|
// setFileUnitSelections((prevSelections) => {
|
|
// const newSelections = [...prevSelections];
|
|
// const currentSelection = { ...newSelections[index] };
|
|
|
|
// // Set semua checkbox tingkat utama ke false
|
|
// currentSelection.nasional = false;
|
|
// currentSelection.wilayah = false;
|
|
// currentSelection.international = false;
|
|
// currentSelection.polda = false;
|
|
// // currentSelection.polres = false;
|
|
// currentSelection.satker = false;
|
|
// currentSelection.semua = false;
|
|
|
|
// newSelections[index] = currentSelection;
|
|
// return newSelections;
|
|
// });
|
|
// } else {
|
|
// const now = temp[index]?.filter((a) => a !== placement);
|
|
// console.log("now", now);
|
|
// temp[index] = now;
|
|
// if (placement === "wilayah") {
|
|
// // Ketika wilayah di-uncheck, hapus wilayah, polda, dan satker
|
|
// const now = temp[index]?.filter(
|
|
// (a) => a !== "wilayah" && a !== "polda" && a !== "satker"
|
|
// );
|
|
// temp[index] = now;
|
|
// } else if (placement === "polda") {
|
|
// // Ketika polda di-uncheck, hapus polda dari filePlacements
|
|
// const now = temp[index]?.filter((a) => a !== "polda");
|
|
// temp[index] = now;
|
|
// } else if (placement === "satker") {
|
|
// // Ketika satker di-uncheck, hapus satker dari filePlacements
|
|
// const now = temp[index]?.filter((a) => a !== "satker");
|
|
// temp[index] = now;
|
|
// } else {
|
|
// const now = temp[index]?.filter((a) => a !== placement);
|
|
// temp[index] = now;
|
|
// }
|
|
|
|
// // Hapus "all" jika tidak semua item ter-checklist
|
|
// if (now.includes("all")) {
|
|
// const nonSatkerItems = now.filter(
|
|
// (item) => item !== "satker" && item !== "all"
|
|
// );
|
|
// if (nonSatkerItems.length < 3) {
|
|
// const newData = now.filter((b) => b !== "all");
|
|
// temp[index] = newData;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// setFilePlacements(temp);
|
|
|
|
// // Update checklist levels di modal berdasarkan placement yang diubah
|
|
// updateModalChecklistLevels(index, placement, checked);
|
|
// };
|
|
// const updateModalChecklistLevels = (
|
|
// fileIndex: number,
|
|
// placement: string,
|
|
// checked: boolean
|
|
// ) => {
|
|
// if (!listDest || listDest.length === 0) return;
|
|
|
|
// setFileCheckedLevels((prev) => {
|
|
// const newArray = [...prev];
|
|
// const currentFileLevels = new Set<number>(newArray[fileIndex]);
|
|
|
|
// if (checked) {
|
|
// if (placement === "polda") {
|
|
// // Checklist semua POLDA (bukan SATKER POLRI)
|
|
// listDest.forEach((item: any) => {
|
|
// if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
|
// currentFileLevels.add(Number(item.id));
|
|
// }
|
|
// });
|
|
// } else if (placement === "satker") {
|
|
// // Checklist SATKER POLRI dan semua sub-item di bawahnya
|
|
// const satkerItem: any = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// if (satkerItem) {
|
|
// currentFileLevels.add(Number(satkerItem.id));
|
|
// if (satkerItem.subDestination) {
|
|
// satkerItem.subDestination.forEach((sub: any) => {
|
|
// currentFileLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
// }
|
|
// }
|
|
// } else {
|
|
// if (placement === "polda") {
|
|
// // Unchecklist semua POLDA
|
|
// listDest.forEach((item: any) => {
|
|
// if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
|
// currentFileLevels.delete(Number(item.id));
|
|
// }
|
|
// });
|
|
// } else if (placement === "satker") {
|
|
// // Unchecklist SATKER POLRI dan semua sub-item di bawahnya
|
|
// const satkerItem: any = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// if (satkerItem) {
|
|
// currentFileLevels.delete(Number(satkerItem.id));
|
|
// if (satkerItem.subDestination) {
|
|
// satkerItem.subDestination.forEach((sub: any) => {
|
|
// currentFileLevels.delete(Number(sub.id));
|
|
// });
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// newArray[fileIndex] = currentFileLevels;
|
|
// return newArray;
|
|
// });
|
|
// };
|
|
|
|
// // Fungsi untuk update checkbox utama berdasarkan checkbox modal (global/legacy)
|
|
// const updateMainCheckboxFromModalLegacy = () => {
|
|
// if (!isUpdatingFromMainCheckbox && listDest.length > 0) {
|
|
// // Hitung item yang dipilih berdasarkan checkedLevels
|
|
// const checkedPoldaCount = listDest.filter(
|
|
// (item: any) =>
|
|
// item.levelNumber === 2 &&
|
|
// item.name !== "SATKER POLRI" &&
|
|
// checkedLevels.has(Number(item.id))
|
|
// ).length;
|
|
|
|
// const satkerItem: any = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// const checkedSatkerCount = satkerItem
|
|
// ? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
|
|
// (satkerItem.subDestination?.filter((sub: any) =>
|
|
// checkedLevels.has(Number(sub.id))
|
|
// ).length || 0)
|
|
// : 0;
|
|
|
|
// // Checkbox aktif jika ADA item yang dipilih dalam kategori tersebut
|
|
// const hasSelectedPolda = checkedPoldaCount > 0;
|
|
// const hasSelectedSatker = checkedSatkerCount > 0;
|
|
|
|
// // Update unitSelection berdasarkan yang dipilih di modal
|
|
// setUnitSelection((prev) => {
|
|
// const newState = { ...prev };
|
|
|
|
// // Update individual checkboxes
|
|
// newState.polda = hasSelectedPolda;
|
|
// newState.satker = hasSelectedSatker;
|
|
|
|
// // Update checkbox "semua" berdasarkan semua checkbox yang aktif
|
|
// newState.semua =
|
|
// newState.nasional &&
|
|
// newState.wilayah &&
|
|
// newState.international &&
|
|
// hasSelectedPolda &&
|
|
// hasSelectedSatker;
|
|
|
|
// return newState;
|
|
// });
|
|
// }
|
|
// };
|
|
|
|
// const updateMainCheckboxFromModal = (fileIndex: number) => {
|
|
// setFileUnitSelections((prev) => {
|
|
// const newSelections = [...prev];
|
|
// const currentSelection = { ...newSelections[fileIndex] };
|
|
// const currentFileLevels = fileCheckedLevels[fileIndex];
|
|
|
|
// if (!currentFileLevels) return prev;
|
|
|
|
// // Hitung total POLDA yang ada (bukan SATKER POLRI)
|
|
// const totalPoldaCount = listDest.filter(
|
|
// (item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
|
|
// ).length;
|
|
|
|
// // Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
|
|
// const checkedPoldaCount = listDest.reduce((total: number, item: any) => {
|
|
// if (
|
|
// item.levelNumber === 2 &&
|
|
// item.name !== "SATKER POLRI" &&
|
|
// currentFileLevels.has(Number(item.id))
|
|
// ) {
|
|
// return total + 1;
|
|
// }
|
|
// return total;
|
|
// }, 0);
|
|
|
|
// // Cek apakah SATKER POLRI ter-checklist
|
|
// const satkerItem = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// const isSatkerChecked =
|
|
// satkerItem && currentFileLevels.has(Number(satkerItem.id));
|
|
|
|
// // Update checkbox berdasarkan kondisi
|
|
// // POLDA aktif jika ada minimal 1 POLDA ter-checklist
|
|
// currentSelection.polda = checkedPoldaCount > 0;
|
|
// currentSelection.satker = Boolean(isSatkerChecked);
|
|
|
|
// // Update checkbox "semua" berdasarkan semua checkbox yang aktif
|
|
// currentSelection.semua =
|
|
// currentSelection.nasional &&
|
|
// currentSelection.wilayah &&
|
|
// currentSelection.international &&
|
|
// currentSelection.polda &&
|
|
// currentSelection.satker;
|
|
|
|
// newSelections[fileIndex] = currentSelection;
|
|
// return newSelections;
|
|
// });
|
|
// };
|
|
|
|
// const toggleExpand = (id: number) => {
|
|
// setExpandedPolda((prev) => ({
|
|
// ...prev,
|
|
// [id]: !prev[id],
|
|
// }));
|
|
// };
|
|
|
|
// const handleFileCheckboxChangePlacement = (
|
|
// fileIndex: number,
|
|
// levelId: number
|
|
// ) => {
|
|
// setFileCheckedLevels((prev) => {
|
|
// const newArray = [...prev];
|
|
// const currentFileLevels = new Set<number>(newArray[fileIndex]);
|
|
// const isCurrentlyChecked = currentFileLevels.has(levelId);
|
|
|
|
// if (isCurrentlyChecked) {
|
|
// currentFileLevels.delete(levelId);
|
|
|
|
// // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
|
// const poldaItem = listDest.find(
|
|
// (item: any) => Number(item.id) === levelId
|
|
// ) as any;
|
|
// if (
|
|
// poldaItem &&
|
|
// poldaItem.subDestination &&
|
|
// poldaItem.name !== "SATKER POLRI"
|
|
// ) {
|
|
// poldaItem.subDestination.forEach((polres: any) => {
|
|
// currentFileLevels.delete(Number(polres.id));
|
|
// });
|
|
// }
|
|
|
|
// // Jika ini adalah SATKER POLRI yang di-unchecklist, unchecklist juga semua sub-item di bawahnya
|
|
// if (poldaItem && poldaItem.name === "SATKER POLRI") {
|
|
// poldaItem.subDestination?.forEach((subItem: any) => {
|
|
// currentFileLevels.delete(Number(subItem.id));
|
|
// });
|
|
// }
|
|
// } else {
|
|
// currentFileLevels.add(levelId);
|
|
|
|
// // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
|
|
// const satkerItem = listDest.find(
|
|
// (item: any) => Number(item.id) === levelId
|
|
// ) as any;
|
|
// if (satkerItem && satkerItem.name === "SATKER POLRI") {
|
|
// // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
|
|
// satkerItem.subDestination?.forEach((subItem: any) => {
|
|
// currentFileLevels.add(Number(subItem.id));
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// newArray[fileIndex] = currentFileLevels;
|
|
|
|
// // Update checkbox utama berdasarkan perubahan di modal
|
|
// // Pindahkan ke sini agar state sudah ter-update
|
|
// setTimeout(() => updateMainCheckboxFromModal(fileIndex), 0);
|
|
|
|
// return newArray;
|
|
// });
|
|
// };
|
|
|
|
// const handleSelectAllSubItems = (fileIndex: number, polda: any) => {
|
|
// setFileCheckedLevels((prev) => {
|
|
// const newArray = [...prev];
|
|
// const currentFileLevels = new Set<number>(newArray[fileIndex]);
|
|
|
|
// // Cek apakah semua sub-items sudah ter-checklist
|
|
// const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
|
|
// currentFileLevels.has(Number(sub.id))
|
|
// );
|
|
|
|
// if (allSubItemsChecked) {
|
|
// // Jika semua sudah ter-checklist, unchecklist semuanya
|
|
// polda.subDestination?.forEach((sub: any) => {
|
|
// currentFileLevels.delete(Number(sub.id));
|
|
// });
|
|
// } else {
|
|
// // Jika belum semua ter-checklist, checklist semuanya
|
|
// // Checklist SATKER POLRI juga jika belum ter-checklist
|
|
// if (!currentFileLevels.has(Number(polda.id))) {
|
|
// currentFileLevels.add(Number(polda.id));
|
|
// }
|
|
// // Checklist semua sub-items
|
|
// polda.subDestination?.forEach((sub: any) => {
|
|
// currentFileLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
|
|
// newArray[fileIndex] = currentFileLevels;
|
|
|
|
// // Update checkbox utama berdasarkan perubahan di modal
|
|
// setTimeout(() => updateMainCheckboxFromModal(fileIndex), 0);
|
|
|
|
// return newArray;
|
|
// });
|
|
// };
|
|
|
|
// // Fungsi untuk sinkronisasi checkbox modal dengan checkbox utama
|
|
// const syncModalWithMainCheckbox = () => {
|
|
// if (isUpdatingFromMainCheckbox) {
|
|
// const newCheckedLevels = new Set(checkedLevels);
|
|
|
|
// // Handle checklist actions - menambahkan semua item ke modal
|
|
// if (mainCheckboxChangeType === "polda_checked") {
|
|
// // Checklist semua polda
|
|
// listDest.forEach((item: any) => {
|
|
// if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
|
// newCheckedLevels.add(Number(item.id));
|
|
// }
|
|
// });
|
|
// } else if (mainCheckboxChangeType === "satker_checked") {
|
|
// // Checklist satker
|
|
// const satkerItem: any = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// if (satkerItem) {
|
|
// newCheckedLevels.add(Number(satkerItem.id));
|
|
// // Checklist semua sub-item yang ada di bawah SATKER (bukan POLRES)
|
|
// if (satkerItem.subDestination) {
|
|
// satkerItem.subDestination.forEach((sub: any) => {
|
|
// newCheckedLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
// }
|
|
// } else if (mainCheckboxChangeType === "semua_checked") {
|
|
// // Checklist semua item
|
|
// listDest.forEach((item: any) => {
|
|
// newCheckedLevels.add(Number(item.id));
|
|
// // Checklist semua sub-item di bawah setiap item
|
|
// if (item.subDestination) {
|
|
// item.subDestination.forEach((sub: any) => {
|
|
// newCheckedLevels.add(Number(sub.id));
|
|
// });
|
|
// }
|
|
// });
|
|
// } else if (mainCheckboxChangeType === "polda_unchecked") {
|
|
// // Clear polda dari checkedLevels, tapi jangan hapus SATKER POLRI
|
|
// listDest.forEach((item: any) => {
|
|
// if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
|
// newCheckedLevels.delete(Number(item.id));
|
|
// }
|
|
// });
|
|
// } else if (mainCheckboxChangeType === "satker_unchecked") {
|
|
// // Clear satker dan semua sub-item di bawahnya dari checkedLevels
|
|
// const satkerItem: any = listDest.find(
|
|
// (item: any) => item.name === "SATKER POLRI"
|
|
// );
|
|
// if (satkerItem) {
|
|
// newCheckedLevels.delete(Number(satkerItem.id));
|
|
// if (satkerItem.subDestination) {
|
|
// satkerItem.subDestination.forEach((sub: any) => {
|
|
// newCheckedLevels.delete(Number(sub.id));
|
|
// });
|
|
// }
|
|
// }
|
|
// } else if (mainCheckboxChangeType === "semua_unchecked") {
|
|
// // Clear semua
|
|
// newCheckedLevels.clear();
|
|
// }
|
|
|
|
// setCheckedLevels(newCheckedLevels);
|
|
|
|
// // Reset flag setelah sinkronisasi selesai
|
|
// setIsUpdatingFromMainCheckbox(false);
|
|
// setMainCheckboxChangeType("");
|
|
// }
|
|
// };
|
|
|
|
// const toFileId = (f: any): string => String(f?.id ?? "");
|
|
// const ensureFileUnit = (fid: any) => {
|
|
// setFileUnitSelections((prev) => {
|
|
// if (prev[fid]) return prev;
|
|
// return {
|
|
// ...prev,
|
|
// [fid]: {
|
|
// semua: false,
|
|
// nasional: false,
|
|
// wilayah: false,
|
|
// international: false,
|
|
// polda: false,
|
|
// satker: false,
|
|
// },
|
|
// };
|
|
// });
|
|
// };
|
|
// const ensureFilePlacement = (fid: any) => {
|
|
// setFilePlacements((prev) => (prev[fid] ? prev : { ...prev, [fid]: [] }));
|
|
// };
|
|
// const ensureFileCheckedLevels = (fid: any) => {
|
|
// setFileCheckedLevels((prev) =>
|
|
// prev[fid] ? prev : { ...prev, [fid]: new Set<number>() }
|
|
// );
|
|
// };
|
|
|
|
// const buildPlacementsPayload = () => {
|
|
// return files.map((file, index) => {
|
|
// const fid = index;
|
|
|
|
// const levelSet = fileCheckedLevels[fid]
|
|
// ? Array.from(fileCheckedLevels[fid])
|
|
// : [];
|
|
|
|
// const unit = fileUnitSelections[fid] || {
|
|
// nasional: false,
|
|
// wilayah: false,
|
|
// international: false,
|
|
// polda: false,
|
|
// satker: false,
|
|
// semua: false,
|
|
// };
|
|
|
|
// let placements: string[] = [];
|
|
|
|
// if (unit.nasional) placements.push("mabes");
|
|
// // if (unit.wilayah) placements.push("polda");
|
|
// if (unit.international) placements.push("international");
|
|
// if (unit.polda) placements.push("polda");
|
|
// if (unit.satker) placements.push("satker");
|
|
|
|
// placements = Array.from(new Set(placements));
|
|
|
|
// return {
|
|
// mediaFileId: file.contentId,
|
|
// placements: placements.join(","), // ✅ contoh: "polda,satker"
|
|
// customLocationPlacements: levelSet.join(","), // ✅ contoh: "219,220,..."
|
|
// };
|
|
// });
|
|
// };
|
|
|
|
// const handleGlobalPlacement = (
|
|
// type: "semua" | "nasional" | "wilayah" | "international",
|
|
// checked: boolean
|
|
// ) => {
|
|
// const count = files.length;
|
|
|
|
// // helper untuk ambil semua id POLDA + SATKER + sub
|
|
// const getAllLevelIds = () => {
|
|
// const set = new Set<number>();
|
|
// listDest.forEach((d: any) => {
|
|
// if (d?.id != null) set.add(Number(d.id));
|
|
// d?.subDestination?.forEach((sub: any) => {
|
|
// if (sub?.id != null) set.add(Number(sub.id));
|
|
// });
|
|
// });
|
|
// return set;
|
|
// };
|
|
|
|
// // atur visibilitas panel detail wilayah
|
|
// if ((type === "wilayah" || type === "semua") && checked) {
|
|
// setIsDetailOfRegionShowed(true);
|
|
// }
|
|
// if (type === "semua" && !checked) {
|
|
// setIsDetailOfRegionShowed(false);
|
|
// }
|
|
|
|
// // ======= fileUnitSelections (checkbox per-bar isian) =======
|
|
// setFileUnitSelections(
|
|
// Array.from({ length: count }, () => {
|
|
// if (type === "semua") {
|
|
// // ✅ AllSemua menyalakan SEMUA checkbox
|
|
// return {
|
|
// semua: checked,
|
|
// nasional: checked,
|
|
// wilayah: checked,
|
|
// international: checked,
|
|
// polda: checked,
|
|
// satker: checked,
|
|
// };
|
|
// }
|
|
// if (type === "nasional") {
|
|
// return {
|
|
// semua: false,
|
|
// nasional: checked,
|
|
// wilayah: false,
|
|
// international: false,
|
|
// polda: false,
|
|
// satker: false,
|
|
// };
|
|
// }
|
|
// if (type === "wilayah") {
|
|
// return {
|
|
// semua: false,
|
|
// nasional: false,
|
|
// wilayah: checked,
|
|
// international: false,
|
|
// polda: checked, // wilayah mengaktifkan polda
|
|
// satker: checked, // dan satker
|
|
// };
|
|
// }
|
|
// // type === "international"
|
|
// return {
|
|
// semua: false,
|
|
// nasional: false,
|
|
// wilayah: false,
|
|
// international: checked,
|
|
// polda: false,
|
|
// satker: false,
|
|
// };
|
|
// })
|
|
// );
|
|
|
|
// // ======= fileCheckedLevels (customLocationPlacements) =======
|
|
// setFileCheckedLevels(
|
|
// Array.from({ length: count }, () => {
|
|
// // AllSemua atau AllWilayah: checklist semua level
|
|
// if (checked && (type === "semua" || type === "wilayah")) {
|
|
// return getAllLevelIds();
|
|
// }
|
|
// // lainnya: kosongkan (tidak memilih level spesifik)
|
|
// return new Set<number>();
|
|
// })
|
|
// );
|
|
|
|
// // ======= (opsional) sinkron UI lain yang masih pakai filePlacements =======
|
|
// setFilePlacements(
|
|
// Array.from({ length: count }, () => {
|
|
// if (!checked) return [];
|
|
// if (type === "semua") {
|
|
// return ["all", "mabes", "polda", "international", "satker"];
|
|
// }
|
|
// if (type === "nasional") {
|
|
// return ["mabes"];
|
|
// }
|
|
// if (type === "wilayah") {
|
|
// return ["polda", "satker"];
|
|
// }
|
|
// // international
|
|
// return ["international"];
|
|
// })
|
|
// );
|
|
// };
|
|
|
|
// return (
|
|
// <div className="mx-auto py-6 space-y-6">
|
|
// {/* Header */}
|
|
// <div className="flex items-center justify-between">
|
|
// <div>
|
|
// <h1 className="text-3xl font-bold tracking-tight">
|
|
// SPIT Convert Form
|
|
// </h1>
|
|
// <p className="text-muted-foreground">
|
|
// Convert and manage your SPIT content
|
|
// </p>
|
|
// </div>
|
|
// <div className="flex items-center gap-2">
|
|
// <Button variant="outline" size="sm" onClick={() => router.back()}>
|
|
// <XCircle className="h-4 w-4 mr-2" />
|
|
// Cancel
|
|
// </Button>
|
|
// <Button
|
|
// variant="default"
|
|
// color="destructive"
|
|
// size="sm"
|
|
// onClick={handleDelete}
|
|
// disabled={isDeleting}
|
|
// >
|
|
// {isDeleting ? (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// ) : (
|
|
// <Trash2 className="h-4 w-4 mr-2" />
|
|
// )}
|
|
// Delete
|
|
// </Button>
|
|
// </div>
|
|
// </div>
|
|
|
|
// <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
// <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
// {/* Main Content */}
|
|
// <div className="lg:col-span-3 space-y-6">
|
|
// {/* Basic Information */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <FileText className="h-5 w-5" />
|
|
// Basic Information
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// {/* Title */}
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="title">Title *</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentTitle"
|
|
// render={({ field }) => (
|
|
// <Input
|
|
// id="title"
|
|
// placeholder="Enter content title"
|
|
// {...field}
|
|
// />
|
|
// )}
|
|
// />
|
|
// {errors.contentTitle && (
|
|
// <Alert variant="soft">
|
|
// <AlertCircle className="h-4 w-4" />
|
|
// <AlertDescription>
|
|
// {errors.contentTitle.message}
|
|
// </AlertDescription>
|
|
// </Alert>
|
|
// )}
|
|
// </div>
|
|
|
|
// {/* Category */}
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="category">Category</Label>
|
|
// <Select
|
|
// value={selectedCategoryId?.toString()}
|
|
// onValueChange={handleCategoryChange}
|
|
// >
|
|
// <SelectTrigger>
|
|
// <SelectValue placeholder="Select category" />
|
|
// </SelectTrigger>
|
|
// <SelectContent>
|
|
// {categories.map((category) => (
|
|
// <SelectItem
|
|
// key={category.id}
|
|
// value={category.id.toString()}
|
|
// >
|
|
// {category.name}
|
|
// </SelectItem>
|
|
// ))}
|
|
// </SelectContent>
|
|
// </Select>
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Content Editor */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Edit3 className="h-5 w-5" />
|
|
// Content Editor
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-8">
|
|
// {/* Pilih Upload Type */}
|
|
// <div className="space-y-2">
|
|
// <Label>Upload Type</Label>
|
|
// <RadioGroup
|
|
// value={selectedFileType}
|
|
// onValueChange={(value: "original" | "rewrite") =>
|
|
// setSelectedFileType(value)
|
|
// }
|
|
// className="grid grid-cols-2 gap-4"
|
|
// >
|
|
// <div className="flex items-center space-x-2">
|
|
// <RadioGroupItem value="original" id="original" />
|
|
// <Label htmlFor="original">Original Content</Label>
|
|
// </div>
|
|
// <div className="flex items-center space-x-2">
|
|
// <RadioGroupItem value="rewrite" id="rewrite" />
|
|
// <Label htmlFor="rewrite">Rewritten Content</Label>
|
|
// </div>
|
|
// </RadioGroup>
|
|
// </div>
|
|
|
|
// {/* Tampilkan keduanya berdampingan */}
|
|
// <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
// {/* Original Content */}
|
|
// <div
|
|
// className={`space-y-2 p-4 rounded-lg border ${
|
|
// selectedFileType === "original"
|
|
// ? "border-blue-600"
|
|
// : "border-gray-300"
|
|
// }`}
|
|
// >
|
|
// <Label className="text-lg text-black">
|
|
// Original Content
|
|
// </Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentDescription"
|
|
// render={({ field }) => (
|
|
// <CustomEditor
|
|
// onChange={field.onChange}
|
|
// initialData={field.value}
|
|
// />
|
|
// )}
|
|
// />
|
|
// </div>
|
|
|
|
// {/* Rewrite Content */}
|
|
// <div
|
|
// className={`space-y-4 p-4 rounded-lg border ${
|
|
// selectedFileType === "rewrite"
|
|
// ? "border-blue-600"
|
|
// : "border-gray-300"
|
|
// }`}
|
|
// >
|
|
// <Label className="text-lg text-black">
|
|
// Rewritten Content
|
|
// </Label>
|
|
|
|
// <div className="flex items-center justify-between">
|
|
// <div className="space-y-2">
|
|
// <Label>Writing Style</Label>
|
|
// <Select
|
|
// value={selectedWritingStyle}
|
|
// onValueChange={setSelectedWritingStyle}
|
|
// >
|
|
// <SelectTrigger className="w-48">
|
|
// <SelectValue placeholder="Select style" />
|
|
// </SelectTrigger>
|
|
// <SelectContent>
|
|
// {WRITING_STYLES.map((style) => (
|
|
// <SelectItem key={style.value} value={style.value}>
|
|
// {style.label}
|
|
// </SelectItem>
|
|
// ))}
|
|
// </SelectContent>
|
|
// </Select>
|
|
// </div>
|
|
// <Button
|
|
// type="button"
|
|
// onClick={handleRewriteClick}
|
|
// disabled={
|
|
// isGeneratingRewrite || !detail?.contentDescription
|
|
// }
|
|
// className="bg-blue-600 hover:bg-blue-700"
|
|
// >
|
|
// {isGeneratingRewrite ? (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// ) : (
|
|
// <Edit3 className="h-4 w-4 mr-2" />
|
|
// )}
|
|
// Generate Rewrite
|
|
// </Button>
|
|
// </div>
|
|
|
|
// {showRewriteEditor && (
|
|
// <div className="space-y-4">
|
|
// {articleIds.length > 0 && (
|
|
// <div className="flex gap-2">
|
|
// {articleIds.map((articleId, index) => (
|
|
// <Button
|
|
// key={articleId}
|
|
// type="button"
|
|
// variant={
|
|
// selectedArticleId === articleId
|
|
// ? "default"
|
|
// : "outline"
|
|
// }
|
|
// size="sm"
|
|
// onClick={() => handleArticleSelect(articleId)}
|
|
// disabled={isLoadingRewrite}
|
|
// >
|
|
// {isLoadingRewrite &&
|
|
// selectedArticleId === articleId && (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// )}
|
|
// Narrative {index + 1}
|
|
// </Button>
|
|
// ))}
|
|
// </div>
|
|
// )}
|
|
|
|
// <div className="space-y-2">
|
|
// <Label>Rewritten Content</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentRewriteDescription"
|
|
// render={({ field }) => (
|
|
// <CustomEditor
|
|
// onChange={field.onChange}
|
|
// initialData={articleBody || field.value}
|
|
// />
|
|
// )}
|
|
// />
|
|
// </div>
|
|
// </div>
|
|
// )}
|
|
// </div>
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Media Files */}
|
|
// <div className="flex flex-col lg:flex-row gap-6">
|
|
// <div className="flex-1 space-y-6">
|
|
// {detailThumb.length > 0 && (
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Image className="h-5 w-5" />
|
|
// Media Files
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <div className="space-y-4 overflow-hidden">
|
|
// <Swiper
|
|
// key={`main-swiper-${detailThumb.length}`}
|
|
// thumbs={{ swiper: thumbsSwiper }}
|
|
// modules={[FreeMode, Navigation, Thumbs]}
|
|
// navigation={true}
|
|
// className="w-full h-96"
|
|
// spaceBetween={10}
|
|
// loop={false}
|
|
// watchSlidesProgress={true}
|
|
// allowTouchMove={true}
|
|
// resistance={true}
|
|
// resistanceRatio={0.85}
|
|
// preventClicks={false}
|
|
// preventClicksPropagation={false}
|
|
// slidesPerView={1}
|
|
// centeredSlides={true}
|
|
// >
|
|
// {detailThumb.map((item) => (
|
|
// <SwiperSlide
|
|
// key={item.contentId}
|
|
// className="!w-full"
|
|
// >
|
|
// {item.contentType === "VIDEO" ? (
|
|
// <div className="relative w-full h-96 overflow-hidden rounded-lg">
|
|
// <video
|
|
// className="w-full h-full object-contain rounded-lg"
|
|
// src={item.contentFile}
|
|
// controls
|
|
// playsInline
|
|
// poster={item.thumbnailFileUrl || undefined}
|
|
// title={`Video ${item.contentId}`}
|
|
// onError={(e) => {
|
|
// console.error("Video load error:", e);
|
|
// e.currentTarget.style.display = "none";
|
|
// }}
|
|
// />
|
|
// </div>
|
|
// ) : (
|
|
// <div className="w-full h-96 flex items-center justify-center overflow-hidden rounded-lg">
|
|
// <img
|
|
// src={item.contentFile}
|
|
// alt={`Media ${item.contentId}`}
|
|
// className="max-w-full max-h-full object-contain rounded-lg"
|
|
// style={{
|
|
// maxWidth: "100%",
|
|
// maxHeight: "100%",
|
|
// }}
|
|
// onError={(e) => {
|
|
// console.error("Image load error:", e);
|
|
// e.currentTarget.style.display = "none";
|
|
// }}
|
|
// />
|
|
// </div>
|
|
// )}
|
|
// </SwiperSlide>
|
|
// ))}
|
|
// </Swiper>
|
|
|
|
// <Swiper
|
|
// key={`thumbs-swiper-${detailThumb.length}`}
|
|
// onSwiper={setThumbsSwiper}
|
|
// slidesPerView={8}
|
|
// spaceBetween={8}
|
|
// freeMode={true}
|
|
// watchSlidesProgress={true}
|
|
// modules={[FreeMode, Thumbs]}
|
|
// breakpoints={{
|
|
// 320: {
|
|
// slidesPerView: 3,
|
|
// spaceBetween: 4,
|
|
// },
|
|
// 640: {
|
|
// slidesPerView: 5,
|
|
// spaceBetween: 6,
|
|
// },
|
|
// 768: {
|
|
// slidesPerView: 6,
|
|
// spaceBetween: 8,
|
|
// },
|
|
// 1024: {
|
|
// slidesPerView: 8,
|
|
// spaceBetween: 8,
|
|
// },
|
|
// }}
|
|
// className="w-full"
|
|
// loop={false}
|
|
// allowTouchMove={true}
|
|
// resistance={true}
|
|
// resistanceRatio={0.85}
|
|
// preventClicks={false}
|
|
// preventClicksPropagation={false}
|
|
// >
|
|
// {detailThumb.map((item) => (
|
|
// <SwiperSlide
|
|
// key={`thumb-${item.contentId}`}
|
|
// className="!w-auto flex-shrink-0"
|
|
// >
|
|
// {item.contentType === "VIDEO" ? (
|
|
// <div className="relative w-20 h-16 rounded cursor-pointer overflow-hidden flex-shrink-0">
|
|
// {/* use preload metadata so browser doesn't download full video */}
|
|
// <video
|
|
// src={item.contentFile}
|
|
// className="w-full h-full object-cover"
|
|
// muted
|
|
// preload="metadata"
|
|
// playsInline
|
|
// tabIndex={-1}
|
|
// onError={(e) => {
|
|
// console.error(
|
|
// "Thumbnail video load error:",
|
|
// e
|
|
// );
|
|
// e.currentTarget.style.display = "none";
|
|
// }}
|
|
// />
|
|
// <div className="absolute inset-0 flex items-center justify-center bg-black/30 pointer-events-none">
|
|
// <svg
|
|
// xmlns="http://www.w3.org/2000/svg"
|
|
// className="w-4 h-4 text-white"
|
|
// fill="currentColor"
|
|
// viewBox="0 0 24 24"
|
|
// >
|
|
// <path d="M8 5v14l11-7z" />
|
|
// </svg>
|
|
// </div>
|
|
// </div>
|
|
// ) : (
|
|
// <div className="w-20 h-16 rounded cursor-pointer overflow-hidden flex-shrink-0">
|
|
// <img
|
|
// src={item.contentFile}
|
|
// alt={`Thumbnail ${item.contentId}`}
|
|
// className="w-full h-full object-cover"
|
|
// onError={(e) => {
|
|
// console.error(
|
|
// "Thumbnail image load error:",
|
|
// e
|
|
// );
|
|
// e.currentTarget.style.display = "none";
|
|
// }}
|
|
// />
|
|
// </div>
|
|
// )}
|
|
// </SwiperSlide>
|
|
// ))}
|
|
// </Swiper>
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
// )}
|
|
|
|
// {/* File Placement */}
|
|
// {files.length > 0 && isUserMabesApprover && (
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Globe className="h-5 w-5" />
|
|
// File Placement
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// {files.length > 1 && (
|
|
// <div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
|
// {PLACEMENT_OPTIONS.map((option) => (
|
|
// <div
|
|
// key={option.value}
|
|
// className="flex items-center space-x-2"
|
|
// >
|
|
// <Checkbox
|
|
// id={`select-all-${option.value}`}
|
|
// onCheckedChange={(checked) =>
|
|
// handleGlobalPlacement(
|
|
// option.value as any,
|
|
// Boolean(checked)
|
|
// )
|
|
// }
|
|
// />
|
|
// <Label
|
|
// htmlFor={`select-all-${option.value}`}
|
|
// className="text-sm"
|
|
// >
|
|
// All {option.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
// )}
|
|
|
|
// <div className="space-y-4">
|
|
// {files.map((file, index) => (
|
|
// <div
|
|
// key={file.contentId}
|
|
// className="flex gap-4 p-4 border rounded-lg"
|
|
// >
|
|
// {file.contentType === "VIDEO" ? (
|
|
// <video
|
|
// src={file.contentFile}
|
|
// className="w-32 h-24 object-cover rounded"
|
|
// muted
|
|
// preload="metadata"
|
|
// playsInline
|
|
// />
|
|
// ) : (
|
|
// <img
|
|
// src={file.contentFile}
|
|
// alt={file.fileName || `file-${file.contentId}`}
|
|
// className="w-32 h-24 object-cover rounded"
|
|
// />
|
|
// )}
|
|
|
|
// <div className="flex-1 space-y-3">
|
|
// <p className="font-medium text-sm">
|
|
// {file.fileName ||
|
|
// file.contentFileName ||
|
|
// `File ${file.contentId}`}
|
|
// </p>
|
|
// <div className="bg-white rounded-md p-4 border">
|
|
// {/* <h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
|
|
// <Icon
|
|
// icon="material-symbols:settings-outline"
|
|
// width={18}
|
|
// height={18}
|
|
// />
|
|
// Pengaturan Distribusi
|
|
// </h5> */}
|
|
|
|
// {/* Checkbox Tingkat Utama */}
|
|
// <div className="space-y-4">
|
|
// <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
// {[
|
|
// { key: "semua", label: "Semua" },
|
|
// {
|
|
// key: "nasional",
|
|
// label: "Nasional",
|
|
// },
|
|
// { key: "wilayah", label: "Wilayah" },
|
|
// {
|
|
// key: "international",
|
|
// label: "Internasional",
|
|
// },
|
|
// ].map((item, idx) => (
|
|
// <div
|
|
// key={item.key}
|
|
// className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
|
|
// >
|
|
// <Checkbox
|
|
// // id={`${item.key}-${index}`}
|
|
// checked={
|
|
// fileUnitSelections[index]?.[
|
|
// item.key as keyof typeof unitSelection
|
|
// ] || false
|
|
// }
|
|
// onCheckedChange={(value) => {
|
|
// handleFileUnitChange(
|
|
// index,
|
|
// item.key as keyof typeof unitSelection,
|
|
// value as boolean
|
|
// );
|
|
// setupPlacement(
|
|
// index,
|
|
// item.key,
|
|
// Boolean(value)
|
|
// );
|
|
// }}
|
|
// />
|
|
// <Label
|
|
// htmlFor={`${item.key}-${index}`}
|
|
// className="text-sm font-medium cursor-pointer"
|
|
// >
|
|
// {item.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
|
|
// {/* Detail Wilayah */}
|
|
// {fileUnitSelections[index]?.wilayah &&
|
|
// isDetailOfRegionShowed && (
|
|
// <div className="border-t border-gray-200 pt-2">
|
|
// <p className="text-sm font-medium text-gray-700 mb-2">
|
|
// Detail Wilayah:
|
|
// </p>
|
|
|
|
// {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
|
// <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
|
// {[
|
|
// { key: "polda", label: "POLDA" },
|
|
// {
|
|
// key: "satker",
|
|
// label: "SATKER",
|
|
// },
|
|
// ].map((item, idx) => (
|
|
// <div
|
|
// key={item.key}
|
|
// className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
|
// >
|
|
// <Checkbox
|
|
// id={`${item.key}-${index}`}
|
|
// checked={
|
|
// fileUnitSelections[index]?.[
|
|
// item.key as keyof typeof unitSelection
|
|
// ] || false
|
|
// }
|
|
// onCheckedChange={(value) => {
|
|
// handleFileUnitChange(
|
|
// index,
|
|
// item.key as keyof typeof unitSelection,
|
|
// value as boolean
|
|
// );
|
|
// setupPlacement(
|
|
// index,
|
|
// item.key,
|
|
// Boolean(value)
|
|
// );
|
|
// }}
|
|
// />
|
|
// <Label
|
|
// htmlFor={`${item.key}-${index}`}
|
|
// className="text-sm font-medium cursor-pointer"
|
|
// >
|
|
// {item.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
|
|
// {/* Tombol Kustom sejajar dengan checkbox */}
|
|
// <div className="flex items-center justify-center p-3">
|
|
// <Dialog>
|
|
// <DialogTrigger asChild>
|
|
// <Button
|
|
// variant="outline"
|
|
// size="sm"
|
|
// className="gap-2"
|
|
// >
|
|
// <Icon
|
|
// icon="material-symbols:tune"
|
|
// width={16}
|
|
// height={16}
|
|
// />
|
|
// {t("custom", {
|
|
// defaultValue: "Kustom",
|
|
// })}
|
|
// </Button>
|
|
// </DialogTrigger>
|
|
// <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
|
// <DialogHeader className="border-b border-gray-200 pb-4">
|
|
// <DialogTitle className="text-lg font-semibold">
|
|
// Daftar Wilayah POLDA dan
|
|
// SATKER
|
|
// </DialogTitle>
|
|
// </DialogHeader>
|
|
// <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
|
// {listDest.map(
|
|
// (polda: any) => (
|
|
// <div
|
|
// key={polda.id}
|
|
// className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
|
// >
|
|
// {/* Header POLDA */}
|
|
// <div className="flex items-center justify-between">
|
|
// <Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
|
// <Checkbox
|
|
// checked={
|
|
// fileCheckedLevels[
|
|
// index
|
|
// ]?.has(
|
|
// Number(
|
|
// polda.id
|
|
// )
|
|
// ) || false
|
|
// }
|
|
// onCheckedChange={() =>
|
|
// handleFileCheckboxChangePlacement(
|
|
// index,
|
|
// Number(
|
|
// polda.id
|
|
// )
|
|
// )
|
|
// }
|
|
// />
|
|
// <span className="font-semibold text-gray-900 text-sm">
|
|
// {polda.name}
|
|
// </span>
|
|
// </Label>
|
|
// {/* Tombol expand hanya untuk SATKER POLRI */}
|
|
// {polda.name ===
|
|
// "SATKER POLRI" &&
|
|
// polda.subDestination && (
|
|
// <button
|
|
// onClick={(
|
|
// e
|
|
// ) => {
|
|
// e.preventDefault();
|
|
// e.stopPropagation();
|
|
// toggleExpand(
|
|
// polda.id
|
|
// );
|
|
// }}
|
|
// className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
|
// >
|
|
// <Icon
|
|
// icon={
|
|
// expandedPolda[
|
|
// polda.id
|
|
// ]
|
|
// ? "mdi:chevron-up"
|
|
// : "mdi:chevron-down"
|
|
// }
|
|
// width={16}
|
|
// height={16}
|
|
// />
|
|
// </button>
|
|
// )}
|
|
// </div>
|
|
|
|
// {/* Sub-items hanya untuk SATKER POLRI */}
|
|
// {polda.name ===
|
|
// "SATKER POLRI" &&
|
|
// polda.subDestination &&
|
|
// expandedPolda[
|
|
// polda.id
|
|
// ] && (
|
|
// <div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
|
// {/* Tombol Pilih Semua untuk sub-items */}
|
|
// <div className="mb-2 flex justify-start">
|
|
// {(() => {
|
|
// const allSubItemsChecked =
|
|
// polda.subDestination?.every(
|
|
// (
|
|
// sub: any
|
|
// ) =>
|
|
// fileCheckedLevels[
|
|
// index
|
|
// ]?.has(
|
|
// Number(
|
|
// sub.id
|
|
// )
|
|
// )
|
|
// );
|
|
// return (
|
|
// <Button
|
|
// size="sm"
|
|
// variant="outline"
|
|
// className="text-xs h-6 px-2"
|
|
// onClick={() =>
|
|
// handleSelectAllSubItems(
|
|
// index,
|
|
// polda
|
|
// )
|
|
// }
|
|
// >
|
|
// {allSubItemsChecked ? (
|
|
// <>
|
|
// <Icon
|
|
// icon="material-symbols:check-indeterminate-small"
|
|
// width={
|
|
// 12
|
|
// }
|
|
// height={
|
|
// 12
|
|
// }
|
|
// className="mr-1"
|
|
// />
|
|
// Batal
|
|
// Semua
|
|
// </>
|
|
// ) : (
|
|
// <>
|
|
// <Icon
|
|
// icon="material-symbols:check-all"
|
|
// width={
|
|
// 12
|
|
// }
|
|
// height={
|
|
// 12
|
|
// }
|
|
// className="mr-1"
|
|
// />
|
|
// Pilih
|
|
// Semua
|
|
// </>
|
|
// )}
|
|
// </Button>
|
|
// );
|
|
// })()}
|
|
// </div>
|
|
// <div className="space-y-1">
|
|
// {polda.subDestination.map(
|
|
// (
|
|
// sub: any
|
|
// ) => (
|
|
// <Label
|
|
// key={
|
|
// sub.id
|
|
// }
|
|
// className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
|
// >
|
|
// <Checkbox
|
|
// checked={
|
|
// fileCheckedLevels[
|
|
// index
|
|
// ]?.has(
|
|
// Number(
|
|
// sub.id
|
|
// )
|
|
// ) ||
|
|
// false
|
|
// }
|
|
// onCheckedChange={() =>
|
|
// handleFileCheckboxChangePlacement(
|
|
// index,
|
|
// Number(
|
|
// sub.id
|
|
// )
|
|
// )
|
|
// }
|
|
// />
|
|
// <span className="text-gray-700">
|
|
// {
|
|
// sub.name
|
|
// }
|
|
// </span>
|
|
// </Label>
|
|
// )
|
|
// )}
|
|
// </div>
|
|
// </div>
|
|
// )}
|
|
// </div>
|
|
// )
|
|
// )}
|
|
// </div>
|
|
// <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
|
// <DialogClose asChild>
|
|
// <Button variant="outline">
|
|
// {t("cancel", {
|
|
// defaultValue: "Batal",
|
|
// })}
|
|
// </Button>
|
|
// </DialogClose>
|
|
// <DialogClose asChild>
|
|
// <Button>Simpan</Button>
|
|
// </DialogClose>
|
|
// </div>
|
|
// </DialogContent>
|
|
// </Dialog>
|
|
// </div>
|
|
// </div>
|
|
// </div>
|
|
// )}
|
|
// </div>
|
|
// </div>
|
|
|
|
// {/* <div className="flex flex-wrap gap-3">
|
|
// {PLACEMENT_OPTIONS.map((option) => (
|
|
// <div
|
|
// key={option.value}
|
|
// className="flex items-center space-x-2"
|
|
// >
|
|
// <Checkbox
|
|
// id={`${file.contentId}-${option.value}`}
|
|
// checked={filePlacements[index]?.includes(
|
|
// option.value
|
|
// )}
|
|
// onCheckedChange={(checked) =>
|
|
// handleFilePlacementChange(
|
|
// index,
|
|
// option.value,
|
|
// Boolean(checked)
|
|
// )
|
|
// }
|
|
// />
|
|
// <Label
|
|
// htmlFor={`${file.contentId}-${option.value}`}
|
|
// className="text-sm"
|
|
// >
|
|
// {option.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </div> */}
|
|
// </div>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
// )}
|
|
// </div>
|
|
|
|
// {/* Sidebar */}
|
|
// <div className="w-full lg:w-[30%] space-y-6">
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Users className="h-5 w-5" />
|
|
// Creator Information
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="creator">Creator *</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentCreator"
|
|
// render={({ field }) => (
|
|
// <Input
|
|
// id="creator"
|
|
// placeholder="Enter creator name"
|
|
// {...field}
|
|
// />
|
|
// )}
|
|
// />
|
|
// {errors.contentCreator && (
|
|
// <Alert variant="soft">
|
|
// <AlertCircle className="h-4 w-4" />
|
|
// <AlertDescription>
|
|
// {errors.contentCreator.message}
|
|
// </AlertDescription>
|
|
// </Alert>
|
|
// )}
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Preview */}
|
|
// {detail?.contentThumbnail && (
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Eye className="h-5 w-5" />
|
|
// Preview
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent>
|
|
// <img
|
|
// src={detail.contentThumbnail}
|
|
// alt="Content thumbnail"
|
|
// className="w-full h-auto rounded-lg"
|
|
// />
|
|
// </CardContent>
|
|
// </Card>
|
|
// )}
|
|
|
|
// {/* Tags */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Tag className="h-5 w-5" />
|
|
// Tags
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="tag-input">Add Tags</Label>
|
|
// <Input
|
|
// id="tag-input"
|
|
// placeholder="Type a tag and press Enter"
|
|
// onKeyDown={handleAddTag}
|
|
// ref={inputRef}
|
|
// />
|
|
// </div>
|
|
// {tags.length > 0 && (
|
|
// <div className="flex flex-wrap gap-2">
|
|
// {tags.map((tag, index) => (
|
|
// <Badge
|
|
// key={index}
|
|
// className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
|
|
// onClick={() => handleRemoveTag(index)}
|
|
// >
|
|
// {tag}
|
|
// <XCircle className="h-3 w-3 ml-1" />
|
|
// </Badge>
|
|
// ))}
|
|
// </div>
|
|
// )}
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Publish Targets */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Settings className="h-5 w-5" />
|
|
// Publish Targets
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-3">
|
|
// {PUBLISH_OPTIONS.map((option) => (
|
|
// <div
|
|
// key={option.id}
|
|
// className="flex items-center space-x-2"
|
|
// >
|
|
// <Checkbox
|
|
// id={option.id}
|
|
// checked={
|
|
// option.id === "all"
|
|
// ? publishedFor.length ===
|
|
// PUBLISH_OPTIONS.filter(
|
|
// (opt) => opt.id !== "all"
|
|
// ).length
|
|
// : publishedFor.includes(option.id)
|
|
// }
|
|
// onCheckedChange={() =>
|
|
// handlePublishTargetChange(option.id)
|
|
// }
|
|
// />
|
|
// <Label htmlFor={option.id} className="text-sm">
|
|
// {option.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Submit Button */}
|
|
// <Card>
|
|
// <CardContent className="pt-6">
|
|
// <Button
|
|
// type="submit"
|
|
// className="w-full mb-4"
|
|
// disabled={isSubmitting || isSaving || isAlreadySaved}
|
|
// >
|
|
// {isSubmitting || isSaving ? (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// ) : (
|
|
// <Save className="h-4 w-4 mr-2" />
|
|
// )}
|
|
// {isAlreadySaved
|
|
// ? "Already Saved"
|
|
// : isSubmitting || isSaving
|
|
// ? "Saving..."
|
|
// : "Save Changes"}
|
|
// </Button>
|
|
|
|
// {isAlreadySaved && (
|
|
// <Alert variant="soft">
|
|
// <CheckCircle className="h-4 w-4 text-red-500" />
|
|
// <AlertDescription className="text-red-500">
|
|
// Konten sudah disimpan. Anda tidak dapat menyimpan
|
|
// ulang.
|
|
// </AlertDescription>
|
|
// </Alert>
|
|
// )}
|
|
// </CardContent>
|
|
// </Card>
|
|
// </div>
|
|
// </div>
|
|
// </div>
|
|
// </div>
|
|
// </form>
|
|
// </div>
|
|
// );
|
|
// }
|
|
|
|
// "use client";
|
|
// import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
|
// import { useForm, Controller } from "react-hook-form";
|
|
// import { zodResolver } from "@hookform/resolvers/zod";
|
|
// import * as z from "zod";
|
|
// import { useParams, useRouter } from "next/navigation";
|
|
// import { useTranslations } from "next-intl";
|
|
// import dynamic from "next/dynamic";
|
|
// import Cookies from "js-cookie";
|
|
|
|
// // UI Components
|
|
// import { Input } from "@/components/ui/input";
|
|
// import { Button } from "@/components/ui/button";
|
|
// import { Label } from "@/components/ui/label";
|
|
// import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
// import { Badge } from "@/components/ui/badge";
|
|
// import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
// import { Separator } from "@/components/ui/separator";
|
|
// import { ScrollArea } from "@/components/ui/scroll-area";
|
|
// import {
|
|
// Select,
|
|
// SelectContent,
|
|
// SelectItem,
|
|
// SelectTrigger,
|
|
// SelectValue,
|
|
// } from "@/components/ui/select";
|
|
// import { Checkbox } from "@/components/ui/checkbox";
|
|
// import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
|
|
// // Icons
|
|
// import {
|
|
// AlertCircle,
|
|
// FileText,
|
|
// Image,
|
|
// Loader2,
|
|
// Save,
|
|
// Trash2,
|
|
// Edit3,
|
|
// Globe,
|
|
// Users,
|
|
// Tag,
|
|
// Eye,
|
|
// Settings,
|
|
// CheckCircle,
|
|
// XCircle,
|
|
// } from "lucide-react";
|
|
|
|
// // Swiper
|
|
// import { Swiper, SwiperSlide } from "swiper/react";
|
|
// import "swiper/css";
|
|
// import "swiper/css/free-mode";
|
|
// import "swiper/css/navigation";
|
|
// import "swiper/css/pagination";
|
|
// import "swiper/css/thumbs";
|
|
// import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
|
|
|
// // Services
|
|
// import {
|
|
// convertSPIT,
|
|
// deleteSPIT,
|
|
// detailSPIT,
|
|
// getTagsBySubCategoryId,
|
|
// listEnableCategory,
|
|
// } from "@/service/content/content";
|
|
// import { generateDataRewrite, getDetailArticle } from "@/service/content/ai";
|
|
|
|
// // Utils
|
|
// import { getCookiesDecrypt } from "@/lib/utils";
|
|
// import { error } from "@/lib/swal";
|
|
// import Swal from "sweetalert2";
|
|
// import withReactContent from "sweetalert2-react-content";
|
|
// import { close, loading } from "@/config/swal";
|
|
|
|
// // Types
|
|
// interface Category {
|
|
// id: number;
|
|
// name: string;
|
|
// }
|
|
|
|
// interface Detail {
|
|
// id: string;
|
|
// contentTitle: string;
|
|
// contentDescription: string;
|
|
// slug: string;
|
|
// content: {
|
|
// id: number;
|
|
// name: string;
|
|
// };
|
|
// contentCreator: string;
|
|
// creatorName: string;
|
|
// contentThumbnail: string;
|
|
// contentTag: string;
|
|
// categoryId?: number;
|
|
// contentList?: FileType[];
|
|
// }
|
|
|
|
// interface Option {
|
|
// id: string;
|
|
// label: string;
|
|
// }
|
|
|
|
// interface FileType {
|
|
// contentId: number;
|
|
// contentFile: string;
|
|
// thumbnailFileUrl: string;
|
|
// fileName: string;
|
|
// }
|
|
|
|
// interface PlacementData {
|
|
// mediaFileId: number;
|
|
// placements: string;
|
|
// }
|
|
|
|
// // Schema
|
|
// const formSchema = z.object({
|
|
// contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
|
|
// contentDescription: z.string().optional(),
|
|
// contentRewriteDescription: z.string().optional(),
|
|
// contentCreator: z.string().min(1, { message: "Creator diperlukan" }),
|
|
// });
|
|
|
|
// type FormData = z.infer<typeof formSchema>;
|
|
|
|
// // Constants
|
|
// const PUBLISH_OPTIONS: Option[] = [
|
|
// { id: "all", label: "SEMUA" },
|
|
// { id: "5", label: "UMUM" },
|
|
// { id: "6", label: "JOURNALIS" },
|
|
// { id: "7", label: "POLRI" },
|
|
// { id: "8", label: "KSP" },
|
|
// ];
|
|
|
|
// const WRITING_STYLES = [
|
|
// { value: "friendly", label: "Friendly" },
|
|
// { value: "professional", label: "Profesional" },
|
|
// { value: "informational", label: "Informational" },
|
|
// { value: "neutral", label: "Neutral" },
|
|
// { value: "witty", label: "Witty" },
|
|
// ];
|
|
|
|
// const PLACEMENT_OPTIONS = [
|
|
// { value: "all", label: "Semua" },
|
|
// { value: "mabes", label: "Nasional" },
|
|
// { value: "polda", label: "Wilayah" },
|
|
// { value: "international", label: "Internasional" },
|
|
// ];
|
|
|
|
// // Dynamic imports
|
|
// const CustomEditor = dynamic(
|
|
// () => import("@/components/editor/custom-editor"),
|
|
// {
|
|
// ssr: false,
|
|
// loading: () => (
|
|
// <div className="flex items-center justify-center h-32 border rounded-md bg-muted/50">
|
|
// <Loader2 className="h-6 w-6 animate-spin" />
|
|
// <span className="ml-2">Loading editor...</span>
|
|
// </div>
|
|
// ),
|
|
// }
|
|
// );
|
|
|
|
// export default function FormConvertSPIT() {
|
|
// const MySwal = withReactContent(Swal);
|
|
// const router = useRouter();
|
|
// const t = useTranslations("Form");
|
|
// const { id } = useParams() as { id: string };
|
|
// const [isAlreadySaved, setIsAlreadySaved] = useState(false);
|
|
|
|
// // Form state
|
|
// const {
|
|
// control,
|
|
// handleSubmit,
|
|
// setValue,
|
|
// formState: { errors, isSubmitting },
|
|
// watch,
|
|
// } = useForm<FormData>({
|
|
// resolver: zodResolver(formSchema),
|
|
// defaultValues: {
|
|
// contentTitle: "",
|
|
// contentDescription: "",
|
|
// contentCreator: "",
|
|
// contentRewriteDescription: "",
|
|
// },
|
|
// });
|
|
|
|
// // Component state
|
|
// const [isLoading, setIsLoading] = useState(true);
|
|
// const [isSaving, setIsSaving] = useState(false);
|
|
// const [isDeleting, setIsDeleting] = useState(false);
|
|
// const [detail, setDetail] = useState<Detail | null>(null);
|
|
// const [categories, setCategories] = useState<Category[]>([]);
|
|
// const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(
|
|
// null
|
|
// );
|
|
// const [selectedFileType, setSelectedFileType] = useState<
|
|
// "original" | "rewrite"
|
|
// >("original");
|
|
// const [selectedWritingStyle, setSelectedWritingStyle] =
|
|
// useState("professional");
|
|
// const [showRewriteEditor, setShowRewriteEditor] = useState(false);
|
|
// const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false);
|
|
// const [isLoadingRewrite, setIsLoadingRewrite] = useState(false);
|
|
// const [detailThumb, setDetailThumb] = useState<string[]>([]);
|
|
// const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
|
// const [files, setFiles] = useState<FileType[]>([]);
|
|
// const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
|
// const [articleIds, setArticleIds] = useState<string[]>([]);
|
|
// const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
|
// null
|
|
// );
|
|
// const [articleBody, setArticleBody] = useState<string>("");
|
|
// const [tags, setTags] = useState<string[]>([]);
|
|
// const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
|
// const [inputRef] = useState(useRef<HTMLInputElement>(null));
|
|
// const userLevelId = getCookiesDecrypt("ulie");
|
|
// const roleId = getCookiesDecrypt("urie");
|
|
// const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
|
|
|
// useEffect(() => {
|
|
// initializeComponent();
|
|
// }, []);
|
|
|
|
// // useEffect(() => {
|
|
// // const savedFlag = localStorage.getItem(`spit_saved_${id}`);
|
|
// // if (savedFlag === "true") {
|
|
// // setIsAlreadySaved(true);
|
|
// // }
|
|
// // }, [id]);
|
|
|
|
// useEffect(() => {
|
|
// checkUserPermissions();
|
|
// }, [userLevelId, roleId]);
|
|
|
|
// const initializeComponent = async () => {
|
|
// try {
|
|
// setIsLoading(true);
|
|
// await Promise.all([
|
|
// loadCategories(),
|
|
// id ? loadDetail() : Promise.resolve(),
|
|
// ]);
|
|
// } catch (error) {
|
|
// console.error("Failed to initialize component:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to load data. Please try again.",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsLoading(false);
|
|
// }
|
|
// };
|
|
|
|
// const checkUserPermissions = () => {
|
|
// if (userLevelId === "216" && roleId === "3") {
|
|
// setIsUserMabesApprover(true);
|
|
// }
|
|
// };
|
|
|
|
// const loadCategories = async () => {
|
|
// try {
|
|
// const response = await listEnableCategory("1");
|
|
// const categories = response?.data?.data?.content || [];
|
|
// setCategories(categories);
|
|
|
|
// // Auto-select "Pers Rilis" category if schedule type is 3
|
|
// const scheduleId = Cookies.get("scheduleId");
|
|
// const scheduleType = Cookies.get("scheduleType");
|
|
|
|
// if (scheduleId && scheduleType === "3") {
|
|
// const persRilisCategory = categories.find((cat: Category) =>
|
|
// cat.name.toLowerCase().includes("pers rilis")
|
|
// );
|
|
// if (persRilisCategory) {
|
|
// setSelectedCategoryId(persRilisCategory.id);
|
|
// await loadTags(persRilisCategory.id);
|
|
// }
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load categories:", error);
|
|
// }
|
|
// };
|
|
|
|
// const loadTags = async (categoryId: number) => {
|
|
// try {
|
|
// const response = await getTagsBySubCategoryId(categoryId);
|
|
// if (response?.data?.data?.length > 0) {
|
|
// const tagsMerge = [...tags, response?.data?.data];
|
|
// setTags(tagsMerge);
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load tags:", error);
|
|
// }
|
|
// };
|
|
|
|
// const loadDetail = async () => {
|
|
// loading();
|
|
// try {
|
|
// const response = await detailSPIT(id);
|
|
// const details = response?.data?.data;
|
|
// setIsAlreadySaved(details?.isPublish ? true : false);
|
|
|
|
// if (!details) {
|
|
// throw new Error("Detail not found");
|
|
// }
|
|
|
|
// setDetail(details);
|
|
// setFiles(details.contentList || []);
|
|
// setDetailThumb(
|
|
// (details.contentList || []).map(
|
|
// (file: FileType) => file.contentFile || "default-image.jpg"
|
|
// )
|
|
// );
|
|
|
|
// // Initialize file placements
|
|
// const fileCount = details.contentList?.length || 0;
|
|
// setFilePlacements(Array(fileCount).fill([]));
|
|
|
|
// // Set form values
|
|
// setValue("contentTitle", details.contentTitle || "");
|
|
// setValue("contentDescription", details.contentDescription || "");
|
|
// setValue("contentCreator", details.contentCreator || "");
|
|
// setValue(
|
|
// "contentRewriteDescription",
|
|
// details.contentRewriteDescription || ""
|
|
// );
|
|
|
|
// if (details.categoryId) {
|
|
// setSelectedCategoryId(details.categoryId);
|
|
// await loadTags(details.categoryId);
|
|
// }
|
|
|
|
// if (details.contentTag) {
|
|
// const initialTags = details.contentTag
|
|
// .split(",")
|
|
// .map((tag: string) => tag.trim());
|
|
// setTags(initialTags);
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load detail:", error);
|
|
// throw error;
|
|
// }
|
|
// close();
|
|
// };
|
|
|
|
// const handleCategoryChange = async (categoryId: string) => {
|
|
// const id = Number(categoryId);
|
|
// setSelectedCategoryId(id);
|
|
// await loadTags(id);
|
|
// };
|
|
|
|
// const handleRewriteClick = async () => {
|
|
// if (!detail?.contentDescription) {
|
|
// MySwal.fire({
|
|
// title: "Warning",
|
|
// text: "Please add content description first",
|
|
// icon: "warning",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// return;
|
|
// }
|
|
|
|
// try {
|
|
// setIsGeneratingRewrite(true);
|
|
// const request = {
|
|
// style: selectedWritingStyle,
|
|
// lang: "id",
|
|
// contextType: "text",
|
|
// urlContext: null,
|
|
// context: detail.contentDescription,
|
|
// createdBy: roleId,
|
|
// sentiment: "Humorous",
|
|
// clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
|
|
// };
|
|
|
|
// const response = await generateDataRewrite(request);
|
|
|
|
// if (response?.error) {
|
|
// throw new Error(response.message);
|
|
// }
|
|
|
|
// const newArticleId = response?.data?.data?.id;
|
|
// setArticleIds((prev) => {
|
|
// const updated = [...prev];
|
|
// if (updated.length < 3) {
|
|
// updated.push(newArticleId);
|
|
// } else {
|
|
// updated[2] = newArticleId;
|
|
// }
|
|
// return updated;
|
|
// });
|
|
|
|
// setShowRewriteEditor(true);
|
|
// MySwal.fire({
|
|
// title: "Success",
|
|
// text: "Content rewrite generated successfully",
|
|
// icon: "success",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Failed to generate rewrite:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to generate content rewrite",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsGeneratingRewrite(false);
|
|
// }
|
|
// };
|
|
|
|
// const handleArticleSelect = async (articleId: string) => {
|
|
// try {
|
|
// setIsLoadingRewrite(true);
|
|
// setSelectedArticleId(articleId);
|
|
|
|
// let retryCount = 0;
|
|
// const maxRetries = 20;
|
|
|
|
// while (retryCount < maxRetries) {
|
|
// const response = await getDetailArticle(articleId);
|
|
// const articleData = response?.data?.data;
|
|
|
|
// if (articleData?.status === 2) {
|
|
// const cleanArticleBody = articleData.articleBody?.replace(
|
|
// /<img[^>]*>/g,
|
|
// ""
|
|
// );
|
|
// setArticleBody(cleanArticleBody || "");
|
|
// setValue("contentRewriteDescription", cleanArticleBody || "");
|
|
// break;
|
|
// }
|
|
|
|
// retryCount++;
|
|
// await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
// }
|
|
|
|
// if (retryCount >= maxRetries) {
|
|
// throw new Error("Timeout: Article processing took too long");
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Failed to load article:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to load article content",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsLoadingRewrite(false);
|
|
// }
|
|
// };
|
|
|
|
// const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
// if (e.key === "Enter" && inputRef.current?.value.trim()) {
|
|
// e.preventDefault();
|
|
// const newTag = inputRef.current.value.trim();
|
|
|
|
// if (!tags.includes(newTag)) {
|
|
// setTags((prev) => [...prev, newTag]);
|
|
// }
|
|
|
|
// inputRef.current.value = "";
|
|
// }
|
|
// };
|
|
|
|
// const handleRemoveTag = (index: number) => {
|
|
// setTags((prev) => prev.filter((_, i) => i !== index));
|
|
// };
|
|
|
|
// const handlePublishTargetChange = (optionId: string) => {
|
|
// if (optionId === "all") {
|
|
// setPublishedFor((prev) =>
|
|
// prev.length === PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").length
|
|
// ? []
|
|
// : PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").map(
|
|
// (opt) => opt.id
|
|
// )
|
|
// );
|
|
// } else {
|
|
// setPublishedFor((prev) =>
|
|
// prev.includes(optionId)
|
|
// ? prev.filter((id) => id !== optionId && id !== "all")
|
|
// : [...prev.filter((id) => id !== "all"), optionId]
|
|
// );
|
|
// }
|
|
// };
|
|
|
|
// const handleFilePlacementChange = (
|
|
// fileIndex: number,
|
|
// placement: string,
|
|
// checked: boolean
|
|
// ) => {
|
|
// setFilePlacements((prev) => {
|
|
// const updated = [...prev];
|
|
// const currentPlacements = updated[fileIndex] || [];
|
|
|
|
// if (checked) {
|
|
// if (placement === "all") {
|
|
// updated[fileIndex] = ["all", "mabes", "polda", "international"];
|
|
// } else {
|
|
// const newPlacements = [...currentPlacements, placement];
|
|
// if (newPlacements.length === 3 && !newPlacements.includes("all")) {
|
|
// newPlacements.push("all");
|
|
// }
|
|
// updated[fileIndex] = newPlacements;
|
|
// }
|
|
// } else {
|
|
// if (placement === "all") {
|
|
// updated[fileIndex] = [];
|
|
// } else {
|
|
// const newPlacements = currentPlacements.filter(
|
|
// (p) => p !== placement
|
|
// );
|
|
// if (newPlacements.length === 3 && newPlacements.includes("all")) {
|
|
// updated[fileIndex] = newPlacements.filter((p) => p !== "all");
|
|
// } else {
|
|
// updated[fileIndex] = newPlacements;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return updated;
|
|
// });
|
|
// };
|
|
|
|
// const handleSelectAllPlacements = (placement: string, checked: boolean) => {
|
|
// setFilePlacements((prev) =>
|
|
// prev.map((filePlacements) =>
|
|
// checked
|
|
// ? Array.from(new Set([...filePlacements, placement]))
|
|
// : filePlacements.filter((p) => p !== placement)
|
|
// )
|
|
// );
|
|
// };
|
|
|
|
// const getPlacementData = () => {
|
|
// const placementData = [];
|
|
// console.log("filePlacements : ", filePlacements);
|
|
// for (let i = 0; i < filePlacements.length; i++) {
|
|
// if (filePlacements[i].length > 0) {
|
|
// const placements = filePlacements[i];
|
|
// placementData.push({
|
|
// mediaFileId: files[i].contentId,
|
|
// placements: placements.join(","),
|
|
// });
|
|
// }
|
|
// }
|
|
// return placementData;
|
|
// };
|
|
|
|
// const checkPlacement = (data: any) => {
|
|
// let temp = true;
|
|
// for (const element of data) {
|
|
// if (element.length < 1) {
|
|
// temp = false;
|
|
// break;
|
|
// }
|
|
// }
|
|
|
|
// return temp;
|
|
// };
|
|
|
|
// // Form submission
|
|
// const onSubmit = async (data: FormData) => {
|
|
// const pnmhTags = tags.filter((tag) => tag.toLowerCase().includes("pnmh"));
|
|
|
|
// if (pnmhTags.length > 1) {
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Tags penugasan hanya diperbolehkan 1 (satu) saja.",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// return false;
|
|
// }
|
|
|
|
// if (!checkPlacement(filePlacements)) {
|
|
// error("Select File Placement");
|
|
// return false;
|
|
// }
|
|
|
|
// if (!selectedCategoryId) {
|
|
// error("Select a category");
|
|
// return false;
|
|
// }
|
|
|
|
// if (publishedFor.length < 1) {
|
|
// error("Select Publish Target");
|
|
// return false;
|
|
// }
|
|
|
|
// try {
|
|
// setIsSaving(true);
|
|
|
|
// const description =
|
|
// selectedFileType === "original"
|
|
// ? data.contentDescription
|
|
// : data.contentRewriteDescription;
|
|
|
|
// const requestData = {
|
|
// spitId: id,
|
|
// title: data.contentTitle,
|
|
// description,
|
|
// htmlDescription: description,
|
|
// tags: tags.join(", "),
|
|
// categoryId: selectedCategoryId,
|
|
// publishedFor: publishedFor.join(","),
|
|
// creator: data.contentCreator,
|
|
// files: isUserMabesApprover ? getPlacementData() : [],
|
|
// };
|
|
|
|
// await convertSPIT(requestData);
|
|
|
|
// // localStorage.setItem(`spit_saved_${id}`, "true");
|
|
// // setIsAlreadySaved(true);
|
|
|
|
// MySwal.fire({
|
|
// title: "Success",
|
|
// text: "Data saved successfully",
|
|
// icon: "success",
|
|
// confirmButtonColor: "#3085d6",
|
|
// }).then(() => {
|
|
// // router.push("/in/contributor/content/spit");
|
|
// // router.replace(`${window.location.pathname}?id=${id}`);
|
|
// loadDetail();
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Failed to save:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to save data",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsSaving(false);
|
|
// }
|
|
// };
|
|
|
|
// const handleDelete = async () => {
|
|
// const result = await MySwal.fire({
|
|
// title: "Delete Content",
|
|
// text: "Are you sure you want to delete this content?",
|
|
// icon: "warning",
|
|
// showCancelButton: true,
|
|
// confirmButtonColor: "#dc3545",
|
|
// cancelButtonColor: "#6c757d",
|
|
// confirmButtonText: "Delete",
|
|
// cancelButtonText: "Cancel",
|
|
// });
|
|
|
|
// if (result.isConfirmed) {
|
|
// try {
|
|
// setIsDeleting(true);
|
|
// await deleteSPIT(id);
|
|
|
|
// MySwal.fire({
|
|
// title: "Success",
|
|
// text: "Content deleted successfully",
|
|
// icon: "success",
|
|
// confirmButtonColor: "#3085d6",
|
|
// }).then(() => {
|
|
// router.back();
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Failed to delete:", error);
|
|
// MySwal.fire({
|
|
// title: "Error",
|
|
// text: "Failed to delete content",
|
|
// icon: "error",
|
|
// confirmButtonColor: "#3085d6",
|
|
// });
|
|
// } finally {
|
|
// setIsDeleting(false);
|
|
// }
|
|
// }
|
|
// };
|
|
|
|
// // Loading state
|
|
// if (isLoading) {
|
|
// return (
|
|
// <div className="flex items-center justify-center min-h-[400px]">
|
|
// <div className="text-center">
|
|
// <Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
|
|
// <p className="text-muted-foreground">Loading form data...</p>
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// }
|
|
|
|
// return (
|
|
// <div className="mx-auto py-6 space-y-6">
|
|
// {/* Header */}
|
|
// <div className="flex items-center justify-between">
|
|
// <div>
|
|
// <h1 className="text-3xl font-bold tracking-tight">
|
|
// SPIT Convert Form
|
|
// </h1>
|
|
// <p className="text-muted-foreground">
|
|
// Convert and manage your SPIT content
|
|
// </p>
|
|
// </div>
|
|
// <div className="flex items-center gap-2">
|
|
// <Button variant="outline" size="sm" onClick={() => router.back()}>
|
|
// <XCircle className="h-4 w-4 mr-2" />
|
|
// Cancel
|
|
// </Button>
|
|
// <Button
|
|
// variant="default"
|
|
// color="destructive"
|
|
// size="sm"
|
|
// onClick={handleDelete}
|
|
// disabled={isDeleting}
|
|
// >
|
|
// {isDeleting ? (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// ) : (
|
|
// <Trash2 className="h-4 w-4 mr-2" />
|
|
// )}
|
|
// Delete
|
|
// </Button>
|
|
// </div>
|
|
// </div>
|
|
|
|
// <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
// <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
// {/* Main Content */}
|
|
// <div className="lg:col-span-2 space-y-6">
|
|
// {/* Basic Information */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <FileText className="h-5 w-5" />
|
|
// Basic Information
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// {/* Title */}
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="title">Title *</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentTitle"
|
|
// render={({ field }) => (
|
|
// <Input
|
|
// id="title"
|
|
// placeholder="Enter content title"
|
|
// {...field}
|
|
// />
|
|
// )}
|
|
// />
|
|
// {errors.contentTitle && (
|
|
// <Alert variant="soft">
|
|
// <AlertCircle className="h-4 w-4" />
|
|
// <AlertDescription>
|
|
// {errors.contentTitle.message}
|
|
// </AlertDescription>
|
|
// </Alert>
|
|
// )}
|
|
// </div>
|
|
|
|
// {/* Category */}
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="category">Category</Label>
|
|
// <Select
|
|
// value={selectedCategoryId?.toString()}
|
|
// onValueChange={handleCategoryChange}
|
|
// >
|
|
// <SelectTrigger>
|
|
// <SelectValue placeholder="Select category" />
|
|
// </SelectTrigger>
|
|
// <SelectContent>
|
|
// {categories.map((category) => (
|
|
// <SelectItem
|
|
// key={category.id}
|
|
// value={category.id.toString()}
|
|
// >
|
|
// {category.name}
|
|
// </SelectItem>
|
|
// ))}
|
|
// </SelectContent>
|
|
// </Select>
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Content Editor */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Edit3 className="h-5 w-5" />
|
|
// Content Editor
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <RadioGroup
|
|
// value={selectedFileType}
|
|
// onValueChange={(value: "original" | "rewrite") =>
|
|
// setSelectedFileType(value)
|
|
// }
|
|
// className="grid grid-cols-2 gap-4"
|
|
// >
|
|
// <div className="flex items-center space-x-2">
|
|
// <RadioGroupItem value="original" id="original" />
|
|
// <Label htmlFor="original">Original Content</Label>
|
|
// </div>
|
|
// <div className="flex items-center space-x-2">
|
|
// <RadioGroupItem value="rewrite" id="rewrite" />
|
|
// <Label htmlFor="rewrite">Rewritten Content</Label>
|
|
// </div>
|
|
// </RadioGroup>
|
|
|
|
// {/* Original Content */}
|
|
// {selectedFileType === "original" && (
|
|
// <div className="space-y-2">
|
|
// <Label>Content Description</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentDescription"
|
|
// render={({ field }) => (
|
|
// <CustomEditor
|
|
// onChange={field.onChange}
|
|
// initialData={field.value}
|
|
// />
|
|
// )}
|
|
// />
|
|
// </div>
|
|
// )}
|
|
|
|
// {/* Content Rewrite */}
|
|
// {selectedFileType === "rewrite" && (
|
|
// <div className="space-y-4">
|
|
// <div className="flex items-center justify-between">
|
|
// <div className="space-y-2">
|
|
// <Label>Writing Style</Label>
|
|
// <Select
|
|
// value={selectedWritingStyle}
|
|
// onValueChange={setSelectedWritingStyle}
|
|
// >
|
|
// <SelectTrigger className="w-48">
|
|
// <SelectValue />
|
|
// </SelectTrigger>
|
|
// <SelectContent>
|
|
// {WRITING_STYLES.map((style) => (
|
|
// <SelectItem key={style.value} value={style.value}>
|
|
// {style.label}
|
|
// </SelectItem>
|
|
// ))}
|
|
// </SelectContent>
|
|
// </Select>
|
|
// </div>
|
|
// <Button
|
|
// type="button"
|
|
// onClick={handleRewriteClick}
|
|
// disabled={
|
|
// isGeneratingRewrite || !detail?.contentDescription
|
|
// }
|
|
// className="bg-blue-600 hover:bg-blue-700"
|
|
// >
|
|
// {isGeneratingRewrite ? (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// ) : (
|
|
// <Edit3 className="h-4 w-4 mr-2" />
|
|
// )}
|
|
// Generate Rewrite
|
|
// </Button>
|
|
// </div>
|
|
|
|
// {showRewriteEditor && (
|
|
// <div className="space-y-4">
|
|
// {articleIds.length > 0 && (
|
|
// <div className="flex gap-2">
|
|
// {articleIds.map((articleId, index) => (
|
|
// <Button
|
|
// key={articleId}
|
|
// type="button"
|
|
// variant={
|
|
// selectedArticleId === articleId
|
|
// ? "default"
|
|
// : "outline"
|
|
// }
|
|
// size="sm"
|
|
// onClick={() => handleArticleSelect(articleId)}
|
|
// disabled={isLoadingRewrite}
|
|
// >
|
|
// {isLoadingRewrite &&
|
|
// selectedArticleId === articleId && (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// )}
|
|
// Narrative {index + 1}
|
|
// </Button>
|
|
// ))}
|
|
// </div>
|
|
// )}
|
|
|
|
// <div className="space-y-2">
|
|
// <Label>Rewritten Content</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentRewriteDescription"
|
|
// render={({ field }) => (
|
|
// <CustomEditor
|
|
// onChange={field.onChange}
|
|
// initialData={articleBody || field.value}
|
|
// />
|
|
// )}
|
|
// />
|
|
// </div>
|
|
// </div>
|
|
// )}
|
|
// </div>
|
|
// )}
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Media Files */}
|
|
// {detailThumb.length > 0 && (
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Image className="h-5 w-5" />
|
|
// Media Files
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <div className="space-y-4">
|
|
// <Swiper
|
|
// thumbs={{ swiper: thumbsSwiper }}
|
|
// modules={[FreeMode, Navigation, Thumbs]}
|
|
// navigation={true}
|
|
// className="w-full h-96"
|
|
// >
|
|
// {detailThumb.map((image, index) => (
|
|
// <SwiperSlide key={index}>
|
|
// <img
|
|
// src={image}
|
|
// alt={`Media ${index + 1}`}
|
|
// className="w-full h-full object-cover rounded-lg"
|
|
// />
|
|
// </SwiperSlide>
|
|
// ))}
|
|
// </Swiper>
|
|
|
|
// <Swiper
|
|
// onSwiper={setThumbsSwiper}
|
|
// slidesPerView={8}
|
|
// spaceBetween={8}
|
|
// modules={[Pagination, Thumbs]}
|
|
// className="w-full"
|
|
// >
|
|
// {detailThumb.map((image, index) => (
|
|
// <SwiperSlide key={index}>
|
|
// <img
|
|
// src={image}
|
|
// alt={`Thumbnail ${index + 1}`}
|
|
// className="w-full h-16 object-cover rounded cursor-pointer"
|
|
// />
|
|
// </SwiperSlide>
|
|
// ))}
|
|
// </Swiper>
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
// )}
|
|
|
|
// {/* File Placement */}
|
|
// {files.length > 0 && isUserMabesApprover && (
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Globe className="h-5 w-5" />
|
|
// File Placement
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// {files.length > 1 && (
|
|
// <div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
|
// {PLACEMENT_OPTIONS.map((option) => (
|
|
// <div
|
|
// key={option.value}
|
|
// className="flex items-center space-x-2"
|
|
// >
|
|
// <Checkbox
|
|
// id={`select-all-${option.value}`}
|
|
// onCheckedChange={(checked) =>
|
|
// handleSelectAllPlacements(
|
|
// option.value,
|
|
// Boolean(checked)
|
|
// )
|
|
// }
|
|
// />
|
|
// <Label
|
|
// htmlFor={`select-all-${option.value}`}
|
|
// className="text-sm"
|
|
// >
|
|
// All {option.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
// )}
|
|
|
|
// <div className="space-y-4">
|
|
// {files.map((file, index) => (
|
|
// <div
|
|
// key={file.contentId}
|
|
// className="flex gap-4 p-4 border rounded-lg"
|
|
// >
|
|
// <img
|
|
// src={file.contentFile}
|
|
// alt={file.fileName}
|
|
// className="w-32 h-24 object-cover rounded"
|
|
// />
|
|
// <div className="flex-1 space-y-3">
|
|
// <p className="font-medium text-sm">{file.fileName}</p>
|
|
// <div className="flex flex-wrap gap-3">
|
|
// {PLACEMENT_OPTIONS.map((option) => (
|
|
// <div
|
|
// key={option.value}
|
|
// className="flex items-center space-x-2"
|
|
// >
|
|
// <Checkbox
|
|
// id={`${file.contentId}-${option.value}`}
|
|
// checked={filePlacements[index]?.includes(
|
|
// option.value
|
|
// )}
|
|
// onCheckedChange={(checked) =>
|
|
// handleFilePlacementChange(
|
|
// index,
|
|
// option.value,
|
|
// Boolean(checked)
|
|
// )
|
|
// }
|
|
// />
|
|
// <Label
|
|
// htmlFor={`${file.contentId}-${option.value}`}
|
|
// className="text-sm"
|
|
// >
|
|
// {option.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
// </div>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
// )}
|
|
// </div>
|
|
|
|
// {/* Sidebar */}
|
|
// <div className="space-y-6">
|
|
// {/* Creator Information */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Users className="h-5 w-5" />
|
|
// Creator Information
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="creator">Creator *</Label>
|
|
// <Controller
|
|
// control={control}
|
|
// name="contentCreator"
|
|
// render={({ field }) => (
|
|
// <Input
|
|
// id="creator"
|
|
// placeholder="Enter creator name"
|
|
// {...field}
|
|
// />
|
|
// )}
|
|
// />
|
|
// {errors.contentCreator && (
|
|
// <Alert variant="soft">
|
|
// <AlertCircle className="h-4 w-4" />
|
|
// <AlertDescription>
|
|
// {errors.contentCreator.message}
|
|
// </AlertDescription>
|
|
// </Alert>
|
|
// )}
|
|
// </div>
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Preview */}
|
|
// {detail?.contentThumbnail && (
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Eye className="h-5 w-5" />
|
|
// Preview
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent>
|
|
// <img
|
|
// src={detail.contentThumbnail}
|
|
// alt="Content thumbnail"
|
|
// className="w-full h-auto rounded-lg"
|
|
// />
|
|
// </CardContent>
|
|
// </Card>
|
|
// )}
|
|
|
|
// {/* Tags */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Tag className="h-5 w-5" />
|
|
// Tags
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-4">
|
|
// <div className="space-y-2">
|
|
// <Label htmlFor="tag-input">Add Tags</Label>
|
|
// <Input
|
|
// id="tag-input"
|
|
// placeholder="Type a tag and press Enter"
|
|
// onKeyDown={handleAddTag}
|
|
// ref={inputRef}
|
|
// />
|
|
// </div>
|
|
// {tags.length > 0 && (
|
|
// <div className="flex flex-wrap gap-2">
|
|
// {tags.map((tag, index) => (
|
|
// <Badge
|
|
// key={index}
|
|
// className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
|
|
// onClick={() => handleRemoveTag(index)}
|
|
// >
|
|
// {tag}
|
|
// <XCircle className="h-3 w-3 ml-1" />
|
|
// </Badge>
|
|
// ))}
|
|
// </div>
|
|
// )}
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Publish Targets */}
|
|
// <Card>
|
|
// <CardHeader>
|
|
// <CardTitle className="flex items-center gap-2">
|
|
// <Settings className="h-5 w-5" />
|
|
// Publish Targets
|
|
// </CardTitle>
|
|
// </CardHeader>
|
|
// <CardContent className="space-y-3">
|
|
// {PUBLISH_OPTIONS.map((option) => (
|
|
// <div key={option.id} className="flex items-center space-x-2">
|
|
// <Checkbox
|
|
// id={option.id}
|
|
// checked={
|
|
// option.id === "all"
|
|
// ? publishedFor.length ===
|
|
// PUBLISH_OPTIONS.filter((opt) => opt.id !== "all")
|
|
// .length
|
|
// : publishedFor.includes(option.id)
|
|
// }
|
|
// onCheckedChange={() =>
|
|
// handlePublishTargetChange(option.id)
|
|
// }
|
|
// />
|
|
// <Label htmlFor={option.id} className="text-sm">
|
|
// {option.label}
|
|
// </Label>
|
|
// </div>
|
|
// ))}
|
|
// </CardContent>
|
|
// </Card>
|
|
|
|
// {/* Submit Button */}
|
|
// <Card>
|
|
// <CardContent className="pt-6">
|
|
// <Button
|
|
// type="submit"
|
|
// className="w-full mb-4"
|
|
// disabled={isSubmitting || isSaving || isAlreadySaved}
|
|
// >
|
|
// {isSubmitting || isSaving ? (
|
|
// <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
// ) : (
|
|
// <Save className="h-4 w-4 mr-2" />
|
|
// )}
|
|
// {isAlreadySaved
|
|
// ? "Already Saved"
|
|
// : isSubmitting || isSaving
|
|
// ? "Saving..."
|
|
// : "Save Changes"}
|
|
// </Button>
|
|
|
|
// {isAlreadySaved && (
|
|
// <Alert variant="soft">
|
|
// <CheckCircle className="h-4 w-4 text-red-500" />
|
|
// <AlertDescription className="text-red-500">
|
|
// Konten sudah disimpan. Anda tidak dapat menyimpan ulang.
|
|
// </AlertDescription>
|
|
// </Alert>
|
|
// )}
|
|
// </CardContent>
|
|
// </Card>
|
|
// </div>
|
|
// </div>
|
|
// </form>
|
|
// </div>
|
|
// );
|
|
// }
|