fix: create content imgae

This commit is contained in:
Sabda Yagra 2025-07-18 09:33:32 +07:00
parent b8827ba8a8
commit 377c22e083
4 changed files with 88 additions and 38 deletions

View File

@ -60,13 +60,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useTranslations } from "next-intl";
type StatusFilter = string[]; type StatusFilter = string[];
const ContentTable = () => { const ContentTable = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<any[]>([]); const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
@ -85,7 +85,6 @@ const ContentTable = () => {
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
const [categories, setCategories] = React.useState<string[]>(); const [categories, setCategories] = React.useState<string[]>();
const [categoryFilter, setCategoryFilter] = React.useState<string[]>([]); const [categoryFilter, setCategoryFilter] = React.useState<string[]>([]);
const [statusFilter, setStatusFilter] = React.useState<StatusFilter>([]); const [statusFilter, setStatusFilter] = React.useState<StatusFilter>([]);
@ -95,8 +94,8 @@ const ContentTable = () => {
const [fileTypeFilter, setFileTypeFilter] = React.useState<string[]>([]); const [fileTypeFilter, setFileTypeFilter] = React.useState<string[]>([]);
const [filterBySource, setFilterBySource] = React.useState<string>(""); const [filterBySource, setFilterBySource] = React.useState<string>("");
const [search, setSearch] = React.useState(""); const [search, setSearch] = React.useState("");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const t = useTranslations("Form");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -179,7 +178,7 @@ const ContentTable = () => {
</InputGroupText> </InputGroupText>
<Input <Input
type="text" type="text"
placeholder="Search Judul..." placeholder={t("searchTitle")}
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search} value={search}
onChange={handleSearch} onChange={handleSearch}
@ -211,14 +210,14 @@ const ContentTable = () => {
}} }}
> >
<SelectTrigger className="w-full lg:w-[180px]"> <SelectTrigger className="w-full lg:w-[180px]">
<SelectValue placeholder="Select a Filter" /> <SelectValue placeholder={t("selectFilter")} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel>Filter</SelectLabel> <SelectLabel>Filter</SelectLabel>
<SelectItem value="1">Foto</SelectItem> <SelectItem value="1">{t("image")}</SelectItem>
<SelectItem value="2">Audio Visual</SelectItem> <SelectItem value="2">{t("audio-visual")}</SelectItem>
<SelectItem value="3">Teks</SelectItem> <SelectItem value="3">{t("text")}</SelectItem>
<SelectItem value="4">Audio</SelectItem> <SelectItem value="4">Audio</SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>

View File

@ -48,7 +48,7 @@ import {
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { CloudUpload } from "lucide-react"; import { CloudUpload, X } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Item } from "@radix-ui/react-dropdown-menu"; import { Item } from "@radix-ui/react-dropdown-menu";
@ -95,14 +95,12 @@ export default function FormImage() {
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original"); const [selectedFileType, setSelectedFileType] = useState("original");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null); const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState(""); const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>(""); const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
@ -119,18 +117,14 @@ export default function FormImage() {
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional"); useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor const [editorContent, setEditorContent] = useState("");
const [rewriteEditorContent, setRewriteEditorContent] = useState(""); const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [selectedSize, setSelectedSize] = useState(""); const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false); const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [content, setContent] = useState("");
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false); const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -147,7 +141,6 @@ export default function FormImage() {
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [showRewriteEditor, setShowRewriteEditor] = useState(false); const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [filesTemp, setFilesTemp] = useState<File[]>([]); const [filesTemp, setFilesTemp] = useState<File[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
@ -1246,6 +1239,7 @@ export default function FormImage() {
name="files" name="files"
render={({ field }) => { render={({ field }) => {
const maxSize = 100 * 1024 * 1024; const maxSize = 100 * 1024 * 1024;
const [previews, setPreviews] = useState<string[]>([]);
const { getRootProps, getInputProps, fileRejections } = const { getRootProps, getInputProps, fileRejections } =
useDropzone({ useDropzone({
@ -1254,12 +1248,40 @@ export default function FormImage() {
"image/png": [".png"], "image/png": [".png"],
}, },
maxSize, maxSize,
multiple: false, multiple: true, // <- Set true jika ingin lebih dari satu
onDrop: (acceptedFiles) => { onDrop: (acceptedFiles) => {
field.onChange(acceptedFiles); const currentFiles = [
...(field.value || []),
...acceptedFiles,
];
field.onChange(currentFiles);
const newPreviews = acceptedFiles.map((file) =>
URL.createObjectURL(file)
);
setPreviews((prev) => [...prev, ...newPreviews]);
}, },
}); });
// Clean up URL on unmount
useEffect(() => {
return () => {
previews.forEach((url) => URL.revokeObjectURL(url));
};
}, [previews]);
const handleRemoveFile = (indexToRemove: number) => {
const updatedFiles = [...field.value];
updatedFiles.splice(indexToRemove, 1);
const updatedPreviews = [...previews];
URL.revokeObjectURL(updatedPreviews[indexToRemove]); // Clean up
updatedPreviews.splice(indexToRemove, 1);
field.onChange(updatedFiles);
setPreviews(updatedPreviews);
};
return ( return (
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label> <Label>
@ -1281,28 +1303,52 @@ export default function FormImage() {
</div> </div>
</div> </div>
</div> </div>
{field.value && field.value.length > 0 && ( {field.value && field.value.length > 0 && (
<> <div className="mt-3 space-y-3">
<ul className="mt-2 text-sm"> {field.value.map((file: File, idx: number) => (
{field.value.map((file, idx) => ( <div
<li key={idx}> key={idx}
{file.name} ( className="flex items-center gap-4 border p-2 rounded-md relative"
{(file.size / (1024 * 1024)).toFixed(2)} MB) >
</li> <img
))} src={previews[idx]}
</ul> alt={`preview-${idx}`}
<div className="flex justify-end mt-2"> className="w-24 h-24 object-cover rounded border"
/>
<div className="flex-1 text-sm">
<div className="font-medium">{file.name}</div>
<div className="text-muted-foreground text-xs">
{(file.size / (1024 * 1024)).toFixed(2)} MB
</div>
</div>
<button
type="button"
className="absolute top-1 right-1 text-muted-foreground hover:text-red-500"
onClick={() => handleRemoveFile(idx)}
>
<X className="w-5 h-5" />
</button>
</div>
))}
<div className="flex justify-end">
<Button <Button
type="button" type="button"
color="destructive" variant="default"
onClick={() => field.onChange([])} onClick={() => {
field.onChange([]);
previews.forEach((url) =>
URL.revokeObjectURL(url)
);
setPreviews([]);
}}
> >
{t("remove-all", { {t("remove-all", {
defaultValue: "Remove All", defaultValue: "Remove All",
})} })}
</Button> </Button>
</div> </div>
</> </div>
)} )}
{errors.files?.message && ( {errors.files?.message && (

View File

@ -836,7 +836,7 @@
"output-task": "Task Output", "output-task": "Task Output",
"attachment": "Attachment", "attachment": "Attachment",
"image": "Image", "image": "Image",
"audio-visual": "Audio Visual", "audio-visual": "Video",
"text": "Text", "text": "Text",
"audio": "Audio", "audio": "Audio",
"voice-note": "Voice Note", "voice-note": "Voice Note",
@ -855,7 +855,9 @@
"coverage-area": "Coverage Area", "coverage-area": "Coverage Area",
"only": "Only .jpg, .jpeg, .png files are allowed", "only": "Only .jpg, .jpeg, .png files are allowed",
"size": "File too large. Max 100MB", "size": "File too large. Max 100MB",
"onlyVd": "Upload files with mp4 or mov Maximum size 100mb." "onlyVd": "Upload files with mp4 or mov Maximum size 100mb.",
"searchTitle": "Find Title...",
"selectFilter": "Select a Filter",
"noResult": "No results."
} }
} }

View File

@ -856,6 +856,9 @@
"coverage-area": "Cakupan Wilayah", "coverage-area": "Cakupan Wilayah",
"only": "Hanya file .jpg, .jpeg, .png yang diizinkan", "only": "Hanya file .jpg, .jpeg, .png yang diizinkan",
"size": "File terlalu besar. Maksimal 100MB", "size": "File terlalu besar. Maksimal 100MB",
"onlyVd": "Upload file dengan mp4 atau mov Ukuran maksimal 100mb." "onlyVd": "Upload file dengan mp4 atau mov Ukuran maksimal 100mb.",
"searchTitle": "Cari Judul...",
"selectFilter": "Pilih Filter",
"noResult": "Tidak ada hasil"
} }
} }