refactor: Perbarui penanganan gambar dan boolean di PostController
parent
6bb5742623
commit
f413f8e4e0
|
@ -112,36 +112,42 @@ class PostController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(PostRequest $request, Post $post)
|
public function update(PostRequest $request, Post $post)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
|
|
||||||
// Only update image if new one is uploaded
|
// Update gambar hanya jika ada file baru yang diupload
|
||||||
if ($request->hasFile('ImagePost')) {
|
if ($request->hasFile('ImagePost')) {
|
||||||
// Delete old image if exists
|
// Hapus gambar lama jika ada
|
||||||
if ($post->ImagePost && Storage::disk('public')->exists($post->ImagePost)) {
|
if ($post->ImagePost && Storage::disk('public')->exists($post->ImagePost)) {
|
||||||
Storage::disk('public')->delete($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
|
|
||||||
unset($data['ImagePost']);
|
|
||||||
}
|
}
|
||||||
|
$data['ImagePost'] = $request->file('ImagePost')->store('images/posts', 'public');
|
||||||
$data['IsPublish'] = $request->boolean('IsPublish');
|
} else {
|
||||||
$post->update($data);
|
// Jika tidak ada file baru, jangan update field ImagePost
|
||||||
|
unset($data['ImagePost']);
|
||||||
DB::commit();
|
|
||||||
return redirect()->route('admin.post.index')->with('success', 'Post berhasil diperbarui.');
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
DB::rollBack();
|
|
||||||
Log::error('Error updating Post: ' . $e->getMessage());
|
|
||||||
return back()->with('error', 'Terjadi kesalahan saat memperbarui post.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.');
|
||||||
|
} 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)
|
public function destroy(Post $post)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,53 +22,14 @@ class PostRequest extends FormRequest
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
if ($this->isMethod('POST')) {
|
return [
|
||||||
return [
|
'KategoriId' => 'required|integer|exists:Kategori,KategoriId',
|
||||||
'KategoriId' => 'required|integer|exists:Kategori,KategoriId',
|
'SubKategoriId' => 'required|integer|exists:SubKategori,SubKategoriId',
|
||||||
'SubKategoriId' => 'required|integer|exists:SubKategori,SubKategoriId',
|
'JudulPost' => ['required', 'string', 'max:255'],
|
||||||
'JudulPost' => ['required', 'string', 'max:255'],
|
'SlugPost' => ['required', 'string', 'max:255'],
|
||||||
'SlugPost' => ['required', 'string', 'max:255'],
|
'DescPost' => ['required', 'string'],
|
||||||
'DescPost' => ['required', 'string'],
|
'ImagePost' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
|
||||||
'ImagePost' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
|
'IsPublish' => 'boolean',
|
||||||
'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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,13 +282,13 @@ export default function AddPost({
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="relative min-h-[300px]">
|
<div className="relative min-h-[300px]">
|
||||||
<Editor
|
<Editor
|
||||||
apiKey="h471mb13phsh2c8rlp5msca6h0h8y0oy37llvpvzhqjymqq3"
|
apiKey={import.meta.env.VITE_KEY_TINY_MCE}
|
||||||
onEditorChange={(content) => {
|
onEditorChange={(content) => {
|
||||||
setData("DescPost", content);
|
setData("DescPost", content);
|
||||||
}}
|
}}
|
||||||
init={{
|
init={{
|
||||||
plugins: [
|
plugins: [
|
||||||
// Core editing features
|
// Free plugins only
|
||||||
"anchor",
|
"anchor",
|
||||||
"autolink",
|
"autolink",
|
||||||
"charmap",
|
"charmap",
|
||||||
|
@ -302,60 +302,21 @@ export default function AddPost({
|
||||||
"table",
|
"table",
|
||||||
"visualblocks",
|
"visualblocks",
|
||||||
"wordcount",
|
"wordcount",
|
||||||
"checklist",
|
"code",
|
||||||
"mediaembed",
|
"fullscreen",
|
||||||
"casechange",
|
"preview",
|
||||||
"export",
|
|
||||||
"formatpainter",
|
|
||||||
"pageembed",
|
|
||||||
"a11ychecker",
|
|
||||||
"tinymcespellchecker",
|
|
||||||
"permanentpen",
|
|
||||||
"powerpaste",
|
|
||||||
"advtable",
|
|
||||||
"advcode",
|
|
||||||
"editimage",
|
|
||||||
"advtemplate",
|
|
||||||
"ai",
|
|
||||||
"mentions",
|
|
||||||
"tinycomments",
|
|
||||||
"tableofcontents",
|
|
||||||
"footnotes",
|
|
||||||
"mergetags",
|
|
||||||
"autocorrect",
|
|
||||||
"typography",
|
|
||||||
"inlinecss",
|
|
||||||
"markdown",
|
|
||||||
"importword",
|
|
||||||
"exportword",
|
|
||||||
"exportpdf",
|
|
||||||
],
|
],
|
||||||
toolbar:
|
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",
|
"undo redo | blocks | bold italic underline strikethrough | link image media table | align | bullist numlist | emoticons charmap | fullscreen preview code | removeformat",
|
||||||
tinycomments_mode: "embedded",
|
height: 500,
|
||||||
tinycomments_author: "Author name",
|
menubar:
|
||||||
mergetags_list: [
|
"file edit view insert format tools table help",
|
||||||
{
|
image_caption: true,
|
||||||
value: "First.Name",
|
quickbars_selection_toolbar:
|
||||||
title: "First Name",
|
"bold italic | quicklink h2 h3 blockquote",
|
||||||
},
|
contextmenu: "link image table",
|
||||||
{ value: "Email", title: "Email" },
|
|
||||||
],
|
|
||||||
ai_request: (
|
|
||||||
_request: any,
|
|
||||||
respondWith: {
|
|
||||||
string: (
|
|
||||||
callback: () => Promise<string>
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
) =>
|
|
||||||
respondWith.string(() =>
|
|
||||||
Promise.reject(
|
|
||||||
"See docs to implement AI Assistant"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
initialValue="Welcome to TinyMCE!"
|
initialValue="Isi Artikel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.DescPost && (
|
{errors.DescPost && (
|
||||||
|
@ -436,10 +397,21 @@ export default function AddPost({
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-start gap-4">
|
||||||
<Button type="submit" className="bg-blue-600 text-white">
|
<Button
|
||||||
Simpan
|
type="button"
|
||||||
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</AuthenticatedLayout>
|
</AuthenticatedLayout>
|
||||||
|
|
|
@ -26,7 +26,7 @@ interface SubKategori {
|
||||||
NamaSubKategori: string;
|
NamaSubKategori: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Post {
|
interface Posting {
|
||||||
PostId: number;
|
PostId: number;
|
||||||
KategoriId: number;
|
KategoriId: number;
|
||||||
SubKategoriId: number;
|
SubKategoriId: number;
|
||||||
|
@ -38,7 +38,7 @@ interface Post {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditPostProps {
|
interface EditPostProps {
|
||||||
post: Post;
|
posting: Posting;
|
||||||
kategori: Kategori[];
|
kategori: Kategori[];
|
||||||
subkategori: SubKategori[];
|
subkategori: SubKategori[];
|
||||||
}
|
}
|
||||||
|
@ -56,28 +56,29 @@ interface PostFormData {
|
||||||
const slugify = (text: string) => text.toLowerCase().replace(/\s+/g, "-");
|
const slugify = (text: string) => text.toLowerCase().replace(/\s+/g, "-");
|
||||||
|
|
||||||
export default function EditPost({
|
export default function EditPost({
|
||||||
post,
|
posting,
|
||||||
kategori,
|
kategori,
|
||||||
subkategori,
|
subkategori,
|
||||||
}: EditPostProps) {
|
}: EditPostProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||||
|
|
||||||
const { data, setData, put, processing, errors } = useForm<PostFormData>({
|
const { data, setData, post, processing, errors } = useForm<PostFormData>({
|
||||||
KategoriId: post.KategoriId.toString(),
|
KategoriId: posting.KategoriId.toString(),
|
||||||
SubKategoriId: post.SubKategoriId.toString(),
|
SubKategoriId: posting.SubKategoriId.toString(),
|
||||||
JudulPost: post.JudulPost,
|
JudulPost: posting.JudulPost,
|
||||||
SlugPost: post.SlugPost,
|
SlugPost: posting.SlugPost,
|
||||||
DescPost: post.DescPost,
|
DescPost: posting.DescPost,
|
||||||
ImagePost: null,
|
ImagePost: null,
|
||||||
IsPublish: post.IsPublish,
|
IsPublish: posting.IsPublish,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (post.ImagePost) {
|
if (posting.ImagePost) {
|
||||||
setImagePreview(`/storage/${post.ImagePost}`);
|
const path = `${posting.ImagePost}`;
|
||||||
|
setImagePreview(path);
|
||||||
}
|
}
|
||||||
}, [post.ImagePost]);
|
}, [posting.ImagePost]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.JudulPost && data.JudulPost.trim() !== "") {
|
if (data.JudulPost && data.JudulPost.trim() !== "") {
|
||||||
|
@ -107,13 +108,13 @@ export default function EditPost({
|
||||||
formData.append("JudulPost", data.JudulPost);
|
formData.append("JudulPost", data.JudulPost);
|
||||||
formData.append("SlugPost", data.SlugPost);
|
formData.append("SlugPost", data.SlugPost);
|
||||||
formData.append("DescPost", data.DescPost);
|
formData.append("DescPost", data.DescPost);
|
||||||
|
// Send the boolean value directly
|
||||||
formData.append("IsPublish", data.IsPublish.toString());
|
formData.append("IsPublish", data.IsPublish.toString());
|
||||||
|
|
||||||
if (data.ImagePost instanceof File) {
|
if (data.ImagePost instanceof File) {
|
||||||
formData.append("ImagePost", data.ImagePost);
|
formData.append("ImagePost", data.ImagePost);
|
||||||
}
|
}
|
||||||
|
|
||||||
put(`/admin/post/${post.PostId}`, {
|
post(`/admin/post/${posting.PostId}`, {
|
||||||
data: formData,
|
data: formData,
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -234,24 +235,60 @@ export default function EditPost({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Editor
|
<Editor
|
||||||
apiKey="h471mb13phsh2c8rlp5msca6h0h8y0oy37llvpvzhqjymqq3"
|
apiKey={import.meta.env.VITE_KEY_TINY_MCE}
|
||||||
value={data.DescPost}
|
value={data.DescPost}
|
||||||
onEditorChange={(content) =>
|
onEditorChange={(content) =>
|
||||||
setData("DescPost", content)
|
setData("DescPost", content)
|
||||||
}
|
}
|
||||||
init={{
|
init={{
|
||||||
height: 300,
|
|
||||||
menubar: false,
|
|
||||||
plugins: [
|
plugins: [
|
||||||
"advlist autolink lists link image charmap print preview anchor",
|
// Free plugins only
|
||||||
"searchreplace visualblocks code fullscreen",
|
"anchor",
|
||||||
"insertdatetime media table paste code help wordcount",
|
"autolink",
|
||||||
|
"charmap",
|
||||||
|
"codesample",
|
||||||
|
"emoticons",
|
||||||
|
"image",
|
||||||
|
"link",
|
||||||
|
"lists",
|
||||||
|
"media",
|
||||||
|
"searchreplace",
|
||||||
|
"table",
|
||||||
|
"visualblocks",
|
||||||
|
"wordcount",
|
||||||
|
"code",
|
||||||
|
"fullscreen",
|
||||||
|
"preview",
|
||||||
],
|
],
|
||||||
toolbar:
|
toolbar:
|
||||||
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | " +
|
"undo redo | blocks | bold italic underline strikethrough | link image media table | align | bullist numlist | emoticons charmap | fullscreen preview code | removeformat",
|
||||||
"bullist numlist outdent indent | removeformat | help",
|
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 && (
|
{errors.DescPost && (
|
||||||
<p className="text-red-500 text-sm">
|
<p className="text-red-500 text-sm">
|
||||||
{errors.DescPost}
|
{errors.DescPost}
|
||||||
|
@ -334,10 +371,10 @@ export default function EditPost({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
|
value={data.IsPublish.toString()}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setData("IsPublish", value === "true")
|
setData("IsPublish", value === "true")
|
||||||
}
|
}
|
||||||
value={data.IsPublish.toString()}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Pilih Status Publikasi" />
|
<SelectValue placeholder="Pilih Status Publikasi" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Link, useForm } from "@inertiajs/react";
|
import { Link, useForm, usePage } from "@inertiajs/react";
|
||||||
import { PageProps } from "@/types";
|
import { PageProps } from "@/types";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
@ -24,6 +24,7 @@ import {
|
||||||
import AuthenticatedLayout from "@/layouts/authenticated-layout";
|
import AuthenticatedLayout from "@/layouts/authenticated-layout";
|
||||||
import { Head } from "@inertiajs/react";
|
import { Head } from "@inertiajs/react";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
import hasAnyPermission from "@/utils/hasAnyPermission";
|
||||||
|
|
||||||
interface SubKategori {
|
interface SubKategori {
|
||||||
SubKategoriId: number;
|
SubKategoriId: number;
|
||||||
|
@ -49,6 +50,8 @@ const ITEMS_PER_PAGE = 5;
|
||||||
export default function PostIndex({
|
export default function PostIndex({
|
||||||
posts = [],
|
posts = [],
|
||||||
}: PageProps<{ posts: Posting[] }>) {
|
}: PageProps<{ posts: Posting[] }>) {
|
||||||
|
const { auth } = usePage().props;
|
||||||
|
const userPermissions = auth?.user?.permissions ?? [];
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue