415 lines
17 KiB
TypeScript
415 lines
17 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import { useForm } from "@inertiajs/react";
|
|
import AuthenticatedLayout from "@/layouts/authenticated-layout";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { Head } from "@inertiajs/react";
|
|
import { Editor } from "@tinymce/tinymce-react";
|
|
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
|
|
interface Kategori {
|
|
KategoriId: number;
|
|
NamaKategori: string;
|
|
}
|
|
|
|
interface SubKategori {
|
|
SubKategoriId: number;
|
|
KategoriId: number;
|
|
NamaSubKategori: string;
|
|
}
|
|
|
|
interface Posting {
|
|
PostId: number;
|
|
KategoriId: number;
|
|
SubKategoriId: number;
|
|
JudulPost: string;
|
|
ImagePost: string;
|
|
SlugPost: string;
|
|
DescPost: string;
|
|
IsPublish: boolean;
|
|
}
|
|
|
|
interface EditPostProps {
|
|
posting: Posting;
|
|
kategori: Kategori[];
|
|
subkategori: SubKategori[];
|
|
}
|
|
|
|
interface PostFormData {
|
|
KategoriId: string;
|
|
SubKategoriId: string;
|
|
JudulPost: string;
|
|
SlugPost: string;
|
|
DescPost: string;
|
|
ImagePost: File | null;
|
|
IsPublish: boolean;
|
|
}
|
|
|
|
const slugify = (text: string) => text.toLowerCase().replace(/\s+/g, "-");
|
|
|
|
export default function EditPost({
|
|
posting,
|
|
kategori,
|
|
subkategori,
|
|
}: EditPostProps) {
|
|
const { toast } = useToast();
|
|
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
|
|
|
const { data, setData, post, processing, errors } = useForm<PostFormData>({
|
|
KategoriId: posting.KategoriId.toString(),
|
|
SubKategoriId: posting.SubKategoriId.toString(),
|
|
JudulPost: posting.JudulPost,
|
|
SlugPost: posting.SlugPost,
|
|
DescPost: posting.DescPost,
|
|
ImagePost: null,
|
|
IsPublish: posting.IsPublish,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (posting.ImagePost) {
|
|
const path = `${posting.ImagePost}`;
|
|
setImagePreview(path);
|
|
}
|
|
}, [posting.ImagePost]);
|
|
|
|
useEffect(() => {
|
|
if (data.JudulPost && data.JudulPost.trim() !== "") {
|
|
setData("SlugPost", slugify(data.JudulPost));
|
|
}
|
|
}, [data.JudulPost]);
|
|
|
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files && e.target.files[0]) {
|
|
const file = e.target.files[0];
|
|
setData("ImagePost", file);
|
|
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
setImagePreview(reader.result as string);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData();
|
|
formData.append("KategoriId", data.KategoriId);
|
|
formData.append("SubKategoriId", data.SubKategoriId);
|
|
formData.append("JudulPost", data.JudulPost);
|
|
formData.append("SlugPost", data.SlugPost);
|
|
formData.append("DescPost", data.DescPost);
|
|
// Send the boolean value directly
|
|
formData.append("IsPublish", data.IsPublish.toString());
|
|
if (data.ImagePost instanceof File) {
|
|
formData.append("ImagePost", data.ImagePost);
|
|
}
|
|
|
|
post(`/admin/post/${posting.PostId}`, {
|
|
data: formData,
|
|
headers: { "Content-Type": "multipart/form-data" },
|
|
onSuccess: () => {
|
|
toast({
|
|
title: "Berhasil",
|
|
description: "Post berhasil diperbarui",
|
|
variant: "default",
|
|
});
|
|
},
|
|
onError: () => {
|
|
toast({
|
|
title: "Gagal",
|
|
description: "Terjadi kesalahan saat memperbarui Post",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<AuthenticatedLayout header="Edit Post">
|
|
<Head title="Edit Post" />
|
|
<div className="container mx-auto p-4">
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<Input
|
|
type="text"
|
|
placeholder="Judul Post"
|
|
value={data.JudulPost}
|
|
onChange={(e) =>
|
|
setData("JudulPost", e.target.value)
|
|
}
|
|
/>
|
|
{errors.JudulPost && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.JudulPost}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<Input
|
|
type="text"
|
|
placeholder="Slug Post"
|
|
value={data.SlugPost}
|
|
readOnly
|
|
/>
|
|
{errors.SlugPost && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.SlugPost}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<Select
|
|
onValueChange={(value) =>
|
|
setData("KategoriId", value)
|
|
}
|
|
value={data.KategoriId}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Pilih Kategori" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{kategori.map((kat) => (
|
|
<SelectItem
|
|
key={kat.KategoriId}
|
|
value={kat.KategoriId.toString()}
|
|
>
|
|
{kat.NamaKategori}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.KategoriId && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.KategoriId}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{data.KategoriId && (
|
|
<div>
|
|
<Select
|
|
onValueChange={(value) =>
|
|
setData("SubKategoriId", value)
|
|
}
|
|
value={data.SubKategoriId}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Pilih SubKategori" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{subkategori
|
|
.filter(
|
|
(sub) =>
|
|
sub.KategoriId ===
|
|
Number(data.KategoriId)
|
|
)
|
|
.map((sub) => (
|
|
<SelectItem
|
|
key={sub.SubKategoriId}
|
|
value={sub.SubKategoriId.toString()}
|
|
>
|
|
{sub.NamaSubKategori}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.SubKategoriId && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.SubKategoriId}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<Editor
|
|
apiKey={import.meta.env.VITE_KEY_TINY_MCE}
|
|
value={data.DescPost}
|
|
onEditorChange={(content) =>
|
|
setData("DescPost", content)
|
|
}
|
|
init={{
|
|
plugins: [
|
|
// Free plugins only
|
|
"anchor",
|
|
"autolink",
|
|
"charmap",
|
|
"codesample",
|
|
"emoticons",
|
|
"image",
|
|
"link",
|
|
"lists",
|
|
"media",
|
|
"searchreplace",
|
|
"table",
|
|
"visualblocks",
|
|
"wordcount",
|
|
"code",
|
|
"fullscreen",
|
|
"preview",
|
|
],
|
|
toolbar:
|
|
"undo redo | blocks | bold italic underline strikethrough | link image media table | align | bullist numlist | emoticons charmap | fullscreen preview code | removeformat",
|
|
height: 300,
|
|
menubar:
|
|
"file edit view insert format tools table help",
|
|
image_caption: true,
|
|
quickbars_selection_toolbar:
|
|
"bold italic | quicklink h2 h3 blockquote",
|
|
contextmenu: "link image table",
|
|
}}
|
|
initialValue="Isi Artikel"
|
|
/>
|
|
{/* <div className="mt-2 p-2 bg-gray-50 text-xs text-gray-600 rounded">
|
|
<p className="font-medium">SEO Tips:</p>
|
|
<ul className="list-disc pl-4 mt-1">
|
|
<li>
|
|
Gunakan heading tags (H1, H2, H3) dengan
|
|
struktur yang tepat
|
|
</li>
|
|
<li>Tambahkan alt text pada gambar</li>
|
|
<li>
|
|
Gunakan link dengan atribut rel yang sesuai
|
|
</li>
|
|
<li>
|
|
Sertakan kata kunci utama dalam paragraf
|
|
awal
|
|
</li>
|
|
</ul>
|
|
</div> */}
|
|
{errors.DescPost && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.DescPost}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-center w-full">
|
|
<label className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100">
|
|
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
|
{imagePreview ? (
|
|
<img
|
|
src={imagePreview}
|
|
alt="Preview"
|
|
className="max-h-52 object-contain"
|
|
onError={() => {
|
|
console.error(
|
|
"Error loading image"
|
|
);
|
|
setImagePreview(null);
|
|
}}
|
|
/>
|
|
) : (
|
|
<>
|
|
<svg
|
|
className="w-8 h-8 mb-4 text-gray-500"
|
|
aria-hidden="true"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 20 16"
|
|
>
|
|
<path
|
|
stroke="currentColor"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
|
|
/>
|
|
</svg>
|
|
<p className="mb-2 text-sm text-gray-500">
|
|
<span className="font-semibold">
|
|
Klik untuk upload
|
|
</span>{" "}
|
|
atau drag and drop
|
|
</p>
|
|
<p className="text-xs text-gray-500">
|
|
PNG, JPG, JPEG atau WEBP (MAX.
|
|
800x400px)
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
<Input
|
|
type="file"
|
|
className="hidden"
|
|
accept="image/png, image/jpg, image/jpeg, image/webp"
|
|
onChange={handleImageChange}
|
|
/>
|
|
</label>
|
|
</div>
|
|
{errors.ImagePost && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.ImagePost}
|
|
</p>
|
|
)}
|
|
{imagePreview && (
|
|
<Button
|
|
type="button"
|
|
variant="destructive"
|
|
onClick={() => {
|
|
setData("ImagePost", null);
|
|
setImagePreview(null);
|
|
}}
|
|
>
|
|
Hapus Gambar
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<Select
|
|
value={data.IsPublish.toString()}
|
|
onValueChange={(value) =>
|
|
setData("IsPublish", value === "true")
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Pilih Status Publikasi" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="true">Publish</SelectItem>
|
|
<SelectItem value="false">Draft</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.IsPublish && (
|
|
<p className="text-red-500 text-sm">
|
|
{errors.IsPublish}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-start gap-4">
|
|
<Button
|
|
type="button"
|
|
className="bg-gray-600 text-white"
|
|
onClick={() => window.history.back()}
|
|
>
|
|
Kembali
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
className="bg-blue-600 text-white"
|
|
disabled={processing}
|
|
>
|
|
{processing ? "Memperbarui..." : "Perbarui"}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</AuthenticatedLayout>
|
|
);
|
|
}
|