skl/resources/js/pages/admin/post/edit_post.tsx

378 lines
15 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 Post {
PostId: number;
KategoriId: number;
SubKategoriId: number;
JudulPost: string;
ImagePost: string;
SlugPost: string;
DescPost: string;
IsPublish: boolean;
}
interface EditPostProps {
post: Post;
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({
post,
kategori,
subkategori,
}: EditPostProps) {
const { toast } = useToast();
const [imagePreview, setImagePreview] = useState<string | null>(null);
const { data, setData, put, processing, errors } = useForm<PostFormData>({
KategoriId: post.KategoriId.toString(),
SubKategoriId: post.SubKategoriId.toString(),
JudulPost: post.JudulPost,
SlugPost: post.SlugPost,
DescPost: post.DescPost,
ImagePost: null,
IsPublish: post.IsPublish,
});
useEffect(() => {
if (post.ImagePost) {
setImagePreview(`/storage/${post.ImagePost}`);
}
}, [post.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);
formData.append("IsPublish", data.IsPublish.toString());
if (data.ImagePost instanceof File) {
formData.append("ImagePost", data.ImagePost);
}
put(`/admin/post/${post.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="h471mb13phsh2c8rlp5msca6h0h8y0oy37llvpvzhqjymqq3"
value={data.DescPost}
onEditorChange={(content) =>
setData("DescPost", content)
}
init={{
height: 300,
menubar: false,
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table paste code help wordcount",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | " +
"bullist numlist outdent indent | removeformat | help",
}}
/>
{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
onValueChange={(value) =>
setData("IsPublish", value === "true")
}
value={data.IsPublish.toString()}
>
<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>
);
}