/** * Item Form Page (Item Subtype) * Add/Edit Item form */ import React, { useState, useEffect, useMemo } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, Save, Loader2, Edit2, X } from "lucide-react"; import { __ } from "../lib/i18n"; import { usePermissions } from "../hooks/usePermissions"; import { useToast } from "../components/ui/toast"; import { apiClient } from "../lib/api-client"; import { generateSlug } from "../lib/slug"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Select } from "../components/ui/select"; import { SearchableSelect } from "../components/ui/searchable-select"; import { PageHeader } from "../components/common/PageHeader"; import { Card, CardContent, CardHeader, CardTitle, } from "../components/ui/card"; import { ConditionalRender } from "../components/ui/conditional-render"; interface ItemFormData { name: string; slug: string; description: string; type_id: string; status: string; } const ItemForm: React.FC = () => { const queryClient = useQueryClient(); const { can } = usePermissions(); const { showToast } = useToast(); const [formData, setFormData] = useState({ name: "", slug: "", description: "", type_id: "", status: "publish", }); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const [isSlugEditable, setIsSlugEditable] = useState(false); const action = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("action") || "create"; }, []); const itemId = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("id") ? parseInt(params.get("id") || "0") : null; }, []); const isEditMode = action === "edit" && itemId !== null; // Fetch item types - only published ones are usable const { data: typesData } = useQuery({ queryKey: ["item-types-published"], queryFn: async () => { try { const response = await apiClient.get("/item-types", { params: { per_page: 100, status: "publish", // Only get published item types }, }); return response.data || []; } catch (error: any) { showToast( error?.message || __("Failed to load item types", "yatra"), "error", ); return []; } }, enabled: can("yatra_view_trips"), }); const { data: itemData, isLoading: isLoadingItem } = useQuery({ queryKey: ["item", itemId], queryFn: async () => { if (!itemId) return null; try { const response = await apiClient.get(`/items/${itemId}`); return response; } catch (error: any) { showToast( error?.message || __("Failed to load item", "yatra"), "error", ); throw error; } }, enabled: isEditMode && can("yatra_view_trips"), }); useEffect(() => { if (itemData && isEditMode) { setFormData({ name: itemData.name || "", slug: itemData.slug || "", description: itemData.description || "", type_id: itemData.type_id?.toString() || "", status: itemData.status || "publish", }); } }, [itemData, isEditMode]); const handleNameChange = (value: string) => { // Auto-generate slug from name only in ADD mode (not in EDIT mode) // In EDIT mode, slug only changes if user explicitly edits it if (!isEditMode && !isSlugEditable) { const newSlug = generateSlug(value); setFormData((prev) => ({ ...prev, name: value, slug: newSlug, })); } else { setFormData((prev) => ({ ...prev, name: value, })); } if (errors.name) { setErrors((prev) => ({ ...prev, name: "" })); } }; const handleSlugChange = (value: string) => { // Only allow manual slug editing if edit mode is enabled if (isSlugEditable) { setFormData((prev) => ({ ...prev, slug: value })); if (errors.slug) { setErrors((prev) => ({ ...prev, slug: "" })); } } }; const handleToggleSlugEdit = () => { if (isSlugEditable) { // If disabling edit, regenerate slug from name const newSlug = generateSlug(formData.name); setFormData((prev) => ({ ...prev, slug: newSlug })); } setIsSlugEditable(!isSlugEditable); }; const handleFieldChange = (field: keyof ItemFormData, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { setErrors((prev) => ({ ...prev, [field]: "" })); } }; const validateForm = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = __("Name is required", "yatra"); } if (!formData.slug.trim()) { newErrors.slug = __("Slug is required", "yatra"); } else if (!/^[\p{L}\p{N}-]+$/u.test(formData.slug)) { newErrors.slug = __( "Slug can only contain letters, numbers, and hyphens", "yatra", ); } if (!formData.type_id) { newErrors.type_id = __("Item type is required", "yatra"); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const saveMutation = useMutation({ mutationFn: async (data: ItemFormData) => { const payload: any = { name: data.name.trim(), slug: data.slug.trim(), description: data.description.trim(), type_id: parseInt(data.type_id), status: data.status, }; // If slug was manually edited, add flag to preserve it if (isEditMode && isSlugEditable) { payload.preserve_slug = true; } if (isEditMode && itemId) { return await apiClient.put(`/items/${itemId}`, payload); } else { return await apiClient.post("/items", payload); } }, onSuccess: (response) => { queryClient.invalidateQueries({ queryKey: ["items"] }); queryClient.invalidateQueries({ queryKey: ["item", itemId] }); showToast( isEditMode ? __("Item updated successfully", "yatra") : __("Item created successfully", "yatra"), "success", ); // Handle redirect based on backend response if (response?.redirect) { setTimeout(() => { window.location.href = response.redirect_to; }, 1000); } else if (response?.stay_on_page) { // Stay on current page for updates setIsSubmitting(false); } else { // Default fallback - redirect to items list setTimeout(() => { window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=itinerary&tab=items`; }, 1000); } }, onError: (error: any) => { const errorMessage = error?.message || __("An error occurred while saving the item", "yatra"); showToast(errorMessage, "error"); setIsSubmitting(false); }, }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { showToast(__("Please fix the form errors", "yatra"), "warning"); return; } setIsSubmitting(true); setErrors({}); saveMutation.mutate(formData); }; const handleCancel = () => { window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=itinerary&tab=items`; }; if (isEditMode && isLoadingItem) { return (
{__("Loading...", "yatra")}
); } const types = typesData || []; return (
{__("Back", "yatra")} } />
{__("Basic Information", "yatra")}
handleFieldChange("type_id", value)} options={types.map((type: any) => ({ value: type.id.toString(), label: type.name, icon: type.icon, }))} placeholder={__("Select a type...", "yatra")} searchPlaceholder={__("Search item types...", "yatra")} className={errors.type_id ? "border-red-500" : ""} error={!!errors.type_id} /> {errors.type_id && (

{errors.type_id}

)}
handleNameChange(e.target.value)} placeholder={__("e.g., Hiking", "yatra")} className={errors.name ? "border-red-500" : ""} required /> {errors.name && (

{errors.name}

)}
handleSlugChange(e.target.value)} placeholder={__("item-slug", "yatra")} className={`pr-10 ${errors.slug ? "border-red-500" : ""} ${!isSlugEditable ? "bg-gray-50 dark:bg-gray-800 cursor-not-allowed" : ""}`} disabled={!isSlugEditable} required />
{errors.slug && (

{errors.slug}

)}

{isSlugEditable ? __( "Manually editing slug. Click X to cancel and regenerate from name.", "yatra", ) : __( "Auto-generated from name. Click edit icon to customize.", "yatra", )}