refactor: Perbarui penanganan gambar dan boolean di PostController

main
marszayn 2025-03-10 14:04:45 +07:00
parent 6bb5742623
commit f413f8e4e0
5 changed files with 134 additions and 155 deletions

View File

@ -112,36 +112,42 @@ class PostController extends Controller
}
public function update(PostRequest $request, Post $post)
{
{
try {
DB::beginTransaction();
$data = $request->validated();
// Only update image if new one is uploaded
// Update gambar hanya jika ada file baru yang diupload
if ($request->hasFile('ImagePost')) {
// Delete old image if exists
// Hapus gambar lama jika ada
if ($post->ImagePost && Storage::disk('public')->exists($post->ImagePost)) {
Storage::disk('public')->delete($post->ImagePost);
}
$data['ImagePost'] = $request->file('ImagePost')->store('images/posts', 'public');
} else {
// Keep existing image if no new one uploaded
// Jika tidak ada file baru, jangan update field ImagePost
unset($data['ImagePost']);
}
$data['IsPublish'] = $request->boolean('IsPublish');
// Pastikan nilai is_publish dikonversi ke boolean
$data['IsPublish'] = filter_var($request->input('IsPublish'), FILTER_VALIDATE_BOOLEAN);
$post->update($data);
DB::commit();
return redirect()->route('admin.post.index')->with('success', 'Post berhasil diperbarui.');
return redirect()->route('admin.post.index')->with('success', 'Post berhasil diperbarui.');
} catch (\Exception $e) {
DB::rollBack();
Log::error('Error updating Post: ' . $e->getMessage());
Log::info('Form data received:', [
'IsPublish' => $request->input('IsPublish'),
'IsPublishType' => gettype($request->input('IsPublish')),
'hasFile' => $request->hasFile('ImagePost')
]);
return back()->with('error', 'Terjadi kesalahan saat memperbarui post.');
}
}
}
public function destroy(Post $post)
{

View File

@ -22,7 +22,6 @@ class PostRequest extends FormRequest
*/
public function rules(): array
{
if ($this->isMethod('POST')) {
return [
'KategoriId' => 'required|integer|exists:Kategori,KategoriId',
'SubKategoriId' => 'required|integer|exists:SubKategori,SubKategoriId',
@ -33,42 +32,4 @@ class PostRequest extends FormRequest
'IsPublish' => 'boolean',
];
}
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
return [
'KategoriId' => 'nullable|integer|exists:Kategori,KategoriId',
'SubKategoriId' => 'nullable|integer|exists:SubKategori,SubKategoriId',
'JudulPost' => ['nullable', 'string', 'max:255'],
'SlugPost' => ['nullable', 'string', 'max:255'],
'DescPost' => ['nullable', 'string'],
'ImagePost' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
'IsPublish' => 'nullable|boolean',
];
}
return [];
}
// Add this method to handle validation before rules are applied
protected function prepareForValidation()
{
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
// Only set these if they're not provided in the request
if (!$this->has('KategoriId')) {
$this->merge(['KategoriId' => $this->route('post')->KategoriId]);
}
if (!$this->has('SubKategoriId')) {
$this->merge(['SubKategoriId' => $this->route('post')->SubKategoriId]);
}
if (!$this->has('JudulPost')) {
$this->merge(['JudulPost' => $this->route('post')->JudulPost]);
}
if (!$this->has('SlugPost')) {
$this->merge(['SlugPost' => $this->route('post')->SlugPost]);
}
if (!$this->has('DescPost')) {
$this->merge(['DescPost' => $this->route('post')->DescPost]);
}
}
}
}

View File

@ -282,13 +282,13 @@ export default function AddPost({
<div className="space-y-2">
<div className="relative min-h-[300px]">
<Editor
apiKey="h471mb13phsh2c8rlp5msca6h0h8y0oy37llvpvzhqjymqq3"
apiKey={import.meta.env.VITE_KEY_TINY_MCE}
onEditorChange={(content) => {
setData("DescPost", content);
}}
init={{
plugins: [
// Core editing features
// Free plugins only
"anchor",
"autolink",
"charmap",
@ -302,60 +302,21 @@ export default function AddPost({
"table",
"visualblocks",
"wordcount",
"checklist",
"mediaembed",
"casechange",
"export",
"formatpainter",
"pageembed",
"a11ychecker",
"tinymcespellchecker",
"permanentpen",
"powerpaste",
"advtable",
"advcode",
"editimage",
"advtemplate",
"ai",
"mentions",
"tinycomments",
"tableofcontents",
"footnotes",
"mergetags",
"autocorrect",
"typography",
"inlinecss",
"markdown",
"importword",
"exportword",
"exportpdf",
"code",
"fullscreen",
"preview",
],
toolbar:
"undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table mergetags | addcomment showcomments | spellcheckdialog a11ycheck typography | align lineheight | checklist numlist bullist indent outdent | emoticons charmap | removeformat",
tinycomments_mode: "embedded",
tinycomments_author: "Author name",
mergetags_list: [
{
value: "First.Name",
title: "First Name",
},
{ value: "Email", title: "Email" },
],
ai_request: (
_request: any,
respondWith: {
string: (
callback: () => Promise<string>
) => void;
}
) =>
respondWith.string(() =>
Promise.reject(
"See docs to implement AI Assistant"
)
),
"undo redo | blocks | bold italic underline strikethrough | link image media table | align | bullist numlist | emoticons charmap | fullscreen preview code | removeformat",
height: 500,
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="Welcome to TinyMCE!"
initialValue="Isi Artikel"
/>
</div>
{errors.DescPost && (
@ -436,10 +397,21 @@ export default function AddPost({
</SelectContent>
</Select>
</div>
<Button type="submit" className="bg-blue-600 text-white">
<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"
>
Simpan
</Button>
</div>
</form>
</div>
</AuthenticatedLayout>

View File

@ -26,7 +26,7 @@ interface SubKategori {
NamaSubKategori: string;
}
interface Post {
interface Posting {
PostId: number;
KategoriId: number;
SubKategoriId: number;
@ -38,7 +38,7 @@ interface Post {
}
interface EditPostProps {
post: Post;
posting: Posting;
kategori: Kategori[];
subkategori: SubKategori[];
}
@ -56,28 +56,29 @@ interface PostFormData {
const slugify = (text: string) => text.toLowerCase().replace(/\s+/g, "-");
export default function EditPost({
post,
posting,
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,
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: post.IsPublish,
IsPublish: posting.IsPublish,
});
useEffect(() => {
if (post.ImagePost) {
setImagePreview(`/storage/${post.ImagePost}`);
if (posting.ImagePost) {
const path = `${posting.ImagePost}`;
setImagePreview(path);
}
}, [post.ImagePost]);
}, [posting.ImagePost]);
useEffect(() => {
if (data.JudulPost && data.JudulPost.trim() !== "") {
@ -107,13 +108,13 @@ export default function EditPost({
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);
}
put(`/admin/post/${post.PostId}`, {
post(`/admin/post/${posting.PostId}`, {
data: formData,
headers: { "Content-Type": "multipart/form-data" },
onSuccess: () => {
@ -234,24 +235,60 @@ export default function EditPost({
<div>
<Editor
apiKey="h471mb13phsh2c8rlp5msca6h0h8y0oy37llvpvzhqjymqq3"
apiKey={import.meta.env.VITE_KEY_TINY_MCE}
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",
// Free plugins only
"anchor",
"autolink",
"charmap",
"codesample",
"emoticons",
"image",
"link",
"lists",
"media",
"searchreplace",
"table",
"visualblocks",
"wordcount",
"code",
"fullscreen",
"preview",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | " +
"bullist numlist outdent indent | removeformat | help",
"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}
@ -334,10 +371,10 @@ export default function EditPost({
<div>
<Select
value={data.IsPublish.toString()}
onValueChange={(value) =>
setData("IsPublish", value === "true")
}
value={data.IsPublish.toString()}
>
<SelectTrigger>
<SelectValue placeholder="Pilih Status Publikasi" />

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { Link, useForm } from "@inertiajs/react";
import { Link, useForm, usePage } from "@inertiajs/react";
import { PageProps } from "@/types";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@ -24,6 +24,7 @@ import {
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import { Head } from "@inertiajs/react";
import { Toaster } from "@/components/ui/toaster";
import hasAnyPermission from "@/utils/hasAnyPermission";
interface SubKategori {
SubKategoriId: number;
@ -49,6 +50,8 @@ const ITEMS_PER_PAGE = 5;
export default function PostIndex({
posts = [],
}: PageProps<{ posts: Posting[] }>) {
const { auth } = usePage().props;
const userPermissions = auth?.user?.permissions ?? [];
const { toast } = useToast();
const [currentPage, setCurrentPage] = useState(1);