Files
edr-platform/apps/edr-freight-web/backoffice/src/user-management/web-Management/News/EditNews.tsx
natib21 22cf8eabd1 fix ui
2026-07-14 07:59:17 +00:00

420 lines
15 KiB
TypeScript

import { useForm } from "react-hook-form";
import { Button } from "@/shared/common/ui/button";
import { Label } from "@/shared/common/ui/label";
import { Textarea } from "@/shared/common/ui/textarea";
import { ImageIcon, Loader2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { FormFeild } from "../components/FormFeild";
import { CreateNewsPayload, NewsItem } from "../types/newsTypes";
import { useNewsCategory } from "../hooks/useNewsCategory";
import { useNavigate, useParams } from "react-router-dom";
import { useNews } from "../hooks/useNews";
import { toast } from "sonner";
import { ContactUsParams } from "../hooks/useContactUs";
interface FormData {
titleEn: string;
titleAm: string;
subTitleAm: string;
subTitleEn: string;
bodyAm: string;
bodyEn: string;
newsCategoryId: string;
image: FileList;
isActive: boolean;
isFeatured: boolean;
}
export const EditNews = () => {
const { id } = useParams();
const { singleItem: item, updateItem } = useNews(undefined, id);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const navigate = useNavigate();
const defaultValues = useMemo(
() => ({
titleEn: item?.title?.en || "",
titleAm: item?.title?.am || "",
subTitleAm: item?.subTitle?.am || "",
subTitleEn: item?.subTitle?.en || "",
bodyAm: item?.body?.am || "",
bodyEn: item?.body?.en || "",
newsCategoryId: item?.newsCategoryId || "",
isActive: item?.isActive || false,
isFeatured: item?.isFeatured || false,
image: undefined,
}),
[item] // 👈 recompute only when item changes
);
const {
register,
handleSubmit,
watch,
setValue,
reset,
formState: { errors },
} = useForm<FormData>({
defaultValues,
});
useEffect(() => {
if (item) {
reset(defaultValues);
}
}, [item, defaultValues, reset]);
const imageFile = watch("image")?.[0];
const [imagePreview, setImagePreview] = useState<string>(
item?.presigned || ""
);
// Fetch news categories for dropdown
const { items: categories } = useNewsCategory();
useEffect(() => {
if (imageFile) {
const url = URL.createObjectURL(imageFile);
setImagePreview(url);
return () => URL.revokeObjectURL(url);
}
}, [imageFile]);
const ImageUpload = ({
label,
preview,
existingName,
register,
error,
onView,
id,
isRequired = false,
}: {
label: string;
preview: string | null;
existingName?: string;
register: any;
error?: string;
onView: () => void;
id: string;
isRequired?: boolean;
}) => (
<div className="space-y-2">
<Label className="text-gray-700 block mb-2">
{label} {isRequired && "*"}
</Label>
{preview && (
<div className="mb-3 flex items-center justify-between bg-primary-50 p-3 rounded-lg">
<div className="flex items-center">
<ImageIcon className="h-5 w-5 text-primary-500 mr-2" />
<span className="text-sm font-medium truncate max-w-[200px]">
{existingName || "Selected Image"}
</span>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={onView}
className="text-primary-600 hover:text-primary-800"
>
Preview
</Button>
</div>
)}
<div>
<FormFeild
id={id}
type="file"
accept="image/*"
{...register}
error={error}
/>
</div>
{error && <p className="text-xs text-red-500 mt-1">{error}</p>}
</div>
);
const handleEditSubmit = async (formData: FormData) => {
if (!item) return;
setIsSubmitting(true);
try {
const imageFile = formData.image?.[0];
const payload: CreateNewsPayload = {
newsCategoryId: formData.newsCategoryId,
title: {
am: formData.titleAm || "",
en: formData.titleEn || "",
},
subTitle: {
am: formData.subTitleAm || "",
en: formData.subTitleEn || "",
},
body: {
am: formData.bodyAm || "",
en: formData.bodyEn || "",
},
// Only include fileInfo if there's a new image
...(imageFile && {
fileInfo: {
fileName: imageFile.name,
contentType: imageFile.type,
size: imageFile.size,
originalname: imageFile.name,
},
}),
isActive: formData.isActive,
isFeatured: formData.isFeatured,
};
await updateItem({
id: item.id,
data: payload,
...(imageFile && { imageFile }),
});
toast.success("News updated successfully");
navigate(-1);
} catch (error) {
toast.error("Failed to update news");
console.error("Update error:", error);
setIsSubmitting(false);
}
};
return (
<div className="max-h-[80vh] overflow-y-auto p-6 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm">
<form onSubmit={handleSubmit(handleEditSubmit)} className="space-y-8">
{/* Category Selection */}
<div className="bg-gray-50 dark:bg-gray-700 p-5 rounded-lg border border-gray-100 dark:border-gray-600">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-4 flex items-center">
<div className="h-1 w-4 bg-primary-500 mr-2 rounded-full"></div>
Category
</h3>
<div className="space-y-2">
<Label htmlFor="newsCategoryId" className="text-gray-700 dark:text-gray-300">
News Category*
</Label>
<select
id="newsCategoryId"
{...register("newsCategoryId", {
required: "Category is required",
})}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500 dark:bg-gray-600 dark:text-gray-200"
>
<option value="">Select a category</option>
{categories?.map((category) => (
<option key={category.id} value={category.id}>
{category.newsCategoryTitle?.en || "Unnamed Category"}
</option>
))}
</select>
{errors.newsCategoryId && (
<p className="text-xs text-red-500 mt-1">
{errors.newsCategoryId.message}
</p>
)}
</div>
</div>
{/* Title Section */}
<div className="bg-gray-50 dark:bg-gray-700 p-5 rounded-lg border border-gray-100 dark:border-gray-600">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
<div className="h-1 w-4 bg-primary-500 mr-2 rounded-full"></div>
Title
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="titleAm" className="text-gray-700 dark:text-gray-200">
Title (Amharic)*
</Label>
<FormFeild
id="titleAm"
{...register("titleAm", { required: "Required" })}
error={errors.titleAm?.message}
placeholder="News title in Amharic"
/>
</div>
<div className="space-y-2">
<Label htmlFor="titleEn" className="text-gray-700 dark:text-gray-200">
Title (English)*
</Label>
<FormFeild
id="titleEn"
{...register("titleEn", { required: "Required" })}
error={errors.titleEn?.message}
placeholder="News title in English"
/>
</div>
</div>
</div>
{/* Subtitle Section */}
<div className="bg-gray-50 dark:bg-gray-700 p-5 rounded-lg border border-gray-100 dark:border-gray-600">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
<div className="h-1 w-4 bg-primary-500 mr-2 rounded-full"></div>
Subtitle
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="subTitleAm" className="text-gray-700 dark:text-gray-200">
Subtitle (Amharic)*
</Label>
<FormFeild
id="subTitleAm"
{...register("subTitleAm", { required: "Required" })}
error={errors.subTitleAm?.message}
placeholder="News subtitle in Amharic"
/>
</div>
<div className="space-y-2">
<Label htmlFor="subTitleEn" className="text-gray-700 dark:text-gray-200">
Subtitle (English)*
</Label>
<FormFeild
id="subTitleEn"
{...register("subTitleEn", { required: "Required" })}
error={errors.subTitleEn?.message}
placeholder="News subtitle in English"
/>
</div>
</div>
</div>
{/* Body Section */}
<div className="bg-gray-50 dark:bg-gray-700 p-5 rounded-lg border border-gray-100 dark:border-gray-600">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
<div className="h-1 w-4 bg-purple-500 mr-2 rounded-full"></div>
Content
</h3>
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="bodyAm" className="text-gray-700 dark:text-gray-200">
Content (Amharic)*
</Label>
<Textarea
id="bodyAm"
{...register("bodyAm", { required: "Required" })}
rows={6}
placeholder="News content in Amharic"
className="min-h-[150px] dark:bg-gray-600 dark:text-gray-200"
/>
{errors.bodyAm && (
<p className="text-xs text-red-500 mt-1">
{errors.bodyAm.message}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="bodyEn" className="text-gray-700 dark:text-gray-200">
Content (English)*
</Label>
<Textarea
id="bodyEn"
{...register("bodyEn", { required: "Required" })}
rows={6}
placeholder="News content in English"
className="min-h-[150px] dark:bg-gray-600 dark:text-gray-200"
/>
{errors.bodyEn && (
<p className="text-xs text-red-500 mt-1">
{errors.bodyEn.message}
</p>
)}
</div>
</div>
</div>
{/* Status Section */}
<div className="bg-gray-50 dark:bg-gray-700 p-5 rounded-lg border border-gray-100 dark:border-gray-600">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
<div className="h-1 w-4 bg-orange-500 mr-2 rounded-full"></div>
Settings
</h3>
<div className="space-y-4">
<div className="flex items-center justify-between">
<Label htmlFor="isActive" className="text-gray-700 dark:text-gray-200 font-medium">
Is Active
</Label>
<button
type="button"
onClick={() => setValue("isActive", !watch("isActive"))}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
watch("isActive") ? "bg-primary-500" : "bg-gray-300"
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
watch("isActive") ? "translate-x-6" : "translate-x-1"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="isFeatured" className="text-gray-700 font-medium">
Is Featured
</Label>
<button
type="button"
onClick={() => setValue("isFeatured", !watch("isFeatured"))}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
watch("isFeatured") ? "bg-yellow-500" : "bg-gray-300"
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
watch("isFeatured") ? "translate-x-6" : "translate-x-1"
}`}
/>
</button>
</div>
</div>
</div>
{/* Image Upload */}
<div className="bg-gray-50 dark:bg-gray-700 p-5 rounded-lg border border-gray-100 dark:border-gray-600">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
<div className="h-1 w-4 bg-yellow-500 mr-2 rounded-full"></div>
Image
</h3>
<ImageUpload
label="News Image"
preview={imagePreview}
existingName={item?.fileInfo?.originalname}
register={register("image")}
error={errors.image?.message}
onView={() => window.open(imagePreview, "_blank")}
id="image"
isRequired={false}
/>
</div>
{/* Actions */}
<div className="flex justify-end gap-3 pt-6 border-t border-gray-200 dark:border-gray-700">
<Button
variant="outline"
onClick={() => navigate(-1)}
type="button"
disabled={isSubmitting}
className="px-6 py-2 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
>
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting}
className="px-6 py-2 bg-purple-600 hover:bg-purple-700 text-white"
>
{isSubmitting ? (
<span className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
Updating...
</span>
) : (
"Update News"
)}
</Button>
</div>
</form>
</div>
);
};