/** * Item Type Form Page * Add/Edit Item Type 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 { PageHeader } from "../components/common/PageHeader"; import { Card, CardContent, CardHeader, CardTitle, } from "../components/ui/card"; import { ConditionalRender } from "../components/ui/conditional-render"; import { IconPicker, IconPickerValue } from "../components/ui/icon-picker"; interface ItemTypeFormData { name: string; slug: string; description: string; icon: { type: "icon" | "image"; value: string; } | null; color: string; status: string; } const ItemTypeForm: React.FC = () => { const queryClient = useQueryClient(); const { can } = usePermissions(); const { showToast } = useToast(); const [formData, setFormData] = useState({ name: "", slug: "", description: "", icon: null, color: "blue", 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 typeId = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("id") ? parseInt(params.get("id") || "0") : null; }, []); const isEditMode = action === "edit" && typeId !== null; const { data: typeData, isLoading: isLoadingType } = useQuery({ queryKey: ["item-type", typeId], queryFn: async () => { if (!typeId) return null; try { const response = await apiClient.get(`/item-types/${typeId}`); return response; } catch (error: any) { showToast( error?.message || __("Failed to load item type", "yatra"), "error", ); throw error; } }, enabled: isEditMode && can("yatra_view_trips"), }); useEffect(() => { if (typeData && isEditMode) { // Handle icon - could be string (old format) or IconPickerValue (new format) let iconValue: IconPickerValue | null = null; if (typeData.icon) { if (typeof typeData.icon === "string") { // Old format: just icon name string iconValue = { type: "icon", value: typeData.icon }; } else if ( typeof typeData.icon === "object" && typeData.icon !== null ) { // New format: IconPickerValue object iconValue = typeData.icon as IconPickerValue; } } setFormData({ name: typeData.name || "", slug: typeData.slug || "", description: typeData.description || "", icon: iconValue, color: typeData.color || "blue", status: typeData.status || "publish", }); } }, [typeData, 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 ItemTypeFormData, value: string | IconPickerValue | null, ) => { 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", ); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const saveMutation = useMutation({ mutationFn: async (data: ItemTypeFormData) => { const payload: any = { name: data.name.trim(), slug: data.slug.trim(), description: data.description.trim(), icon: data.icon, color: data.color, status: data.status, }; // If slug was manually edited, add flag to preserve it if (isEditMode && isSlugEditable) { payload.preserve_slug = true; } if (isEditMode && typeId) { return await apiClient.put(`/item-types/${typeId}`, payload); } else { return await apiClient.post("/item-types", payload); } }, onSuccess: (response: any) => { queryClient.invalidateQueries({ queryKey: ["item-types"] }); queryClient.invalidateQueries({ queryKey: ["item-type", typeId] }); showToast( isEditMode ? __("Item type updated successfully", "yatra") : __("Item type created successfully", "yatra"), "success", ); if (isEditMode) { // Don't redirect on update - stay on edit page setIsSubmitting(false); } else { // Redirect to edit page after create setTimeout(() => { const newId = response?.id || typeId; window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=itinerary&tab=item-types&action=edit&id=${newId}`; }, 1000); } }, onError: (error: any) => { const errorMessage = error?.message || __("An error occurred while saving the item type", "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=item-types`; }; const colorOptions = [ { value: "blue", label: __("Blue", "yatra") }, { value: "green", label: __("Green", "yatra") }, { value: "purple", label: __("Purple", "yatra") }, { value: "orange", label: __("Orange", "yatra") }, { value: "red", label: __("Red", "yatra") }, { value: "yellow", label: __("Yellow", "yatra") }, { value: "gray", label: __("Gray", "yatra") }, ]; if (isEditMode && isLoadingType) { return (
{/* Header Skeleton */}
{/* Form Skeleton */}
{/* Main Fields */}
{/* Name field */}
{/* Slug field */}
{/* Description field */}
{/* Icon field */}
{/* Color field */}
{/* Status field */}
{/* Sidebar */}
); } return (
{__("Back", "yatra")} } />
{__("Basic Information", "yatra")}
handleNameChange(e.target.value)} placeholder={__("e.g., Activity", "yatra")} className={errors.name ? "border-red-500" : ""} required /> {errors.name && (

{errors.name}

)}
handleSlugChange(e.target.value)} placeholder={__("item-type-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", )}