import { useEffect, useId, useRef, useState } from 'react' import { ChevronDown, Search, X } from 'lucide-react' import { formatCellNumber, formatUserName, type User, } from '../data/users' import { ApiError } from '../lib/api' import { dataUrlToBlob, isLocalImageSrc, uploadMedia, } from '../lib/mediaApi' import { specialOrderCategories, specialOrderCategoryLabel, type SpecialOrder, type SpecialOrderCategory, } from '../lib/specialOrdersApi' import { listUsers } from '../lib/usersApi' import { ImageGallery } from './ImageGallery' import styles from './CategoryModal.module.css' import localStyles from './SpecialOrderModal.module.css' export type SpecialOrderFormValues = { userId: string category: SpecialOrderCategory name: string description: string images: Array<{ url: string; storageKey: string }> } type SpecialOrderModalProps = { open: boolean editing?: SpecialOrder | null onClose: () => void onSubmit: (values: SpecialOrderFormValues) => void | Promise } export function SpecialOrderModal({ open, editing = null, onClose, onSubmit, }: SpecialOrderModalProps) { const titleId = useId() const isEditing = Boolean(editing) const userRef = useRef(null) const [user, setUser] = useState(null) const [userQuery, setUserQuery] = useState('') const [userOpen, setUserOpen] = useState(false) const [userResults, setUserResults] = useState([]) const [userLoading, setUserLoading] = useState(false) const [category, setCategory] = useState('specialCake') const [name, setName] = useState('') const [description, setDescription] = useState('') const [gallery, setGallery] = useState([]) const [galleryKeys, setGalleryKeys] = useState>({}) const [error, setError] = useState('') const [isSubmitting, setIsSubmitting] = useState(false) useEffect(() => { if (!open) return if (editing) { setUser(editing.user) setUserQuery(formatUserName(editing.user) || editing.user.name) setCategory(editing.category) setName(editing.name) setDescription(editing.description) const urls = editing.images.map((item) => item.url) setGallery(urls) setGalleryKeys( Object.fromEntries( editing.images.map((item) => [item.url, item.storageKey]), ), ) } else { setUser(null) setUserQuery('') setCategory('specialCake') setName('') setDescription('') setGallery([]) setGalleryKeys({}) } setUserOpen(false) setUserResults([]) setError('') setIsSubmitting(false) }, [open, editing]) useEffect(() => { if (!open) return function handleKeyDown(event: KeyboardEvent) { if (event.key === 'Escape' && !isSubmitting) onClose() } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, [open, onClose, isSubmitting]) useEffect(() => { function handlePointerDown(event: MouseEvent) { if (!userRef.current?.contains(event.target as Node)) { setUserOpen(false) } } document.addEventListener('mousedown', handlePointerDown) return () => document.removeEventListener('mousedown', handlePointerDown) }, []) useEffect(() => { if (!open || !userOpen) return const q = userQuery.trim() if (q.length < 2) { setUserResults([]) setUserLoading(false) return } let cancelled = false setUserLoading(true) const timer = window.setTimeout(() => { void (async () => { try { const result = await listUsers({ q, role: 'customer', page: 1, pageSize: 12, }) if (!cancelled) setUserResults(result.items) } catch { if (!cancelled) setUserResults([]) } finally { if (!cancelled) setUserLoading(false) } })() }, 250) return () => { cancelled = true window.clearTimeout(timer) } }, [open, userOpen, userQuery]) if (!open) return null async function resolveGallery() { const items: Array<{ url: string; storageKey: string }> = [] for (const src of gallery) { if (!isLocalImageSrc(src)) { const key = galleryKeys[src] if (!key) throw new Error('کلید ذخیره‌سازی تصویر یافت نشد') items.push({ url: src, storageKey: key }) continue } const blob = await dataUrlToBlob(src) const uploaded = await uploadMedia('specialOrder', blob) items.push({ url: uploaded.url, storageKey: uploaded.storageKey }) } return items } async function handleSubmit(event: React.FormEvent) { event.preventDefault() if (isSubmitting) return if (!user?.id) { setError('انتخاب کاربر الزامی است') return } if (!name.trim()) { setError('نام الزامی است') return } setError('') setIsSubmitting(true) try { const images = await resolveGallery() await onSubmit({ userId: user.id, category, name: name.trim(), description: description.trim(), images, }) } catch (err) { setError( err instanceof ApiError ? err.message : err instanceof Error ? err.message : 'ذخیره سفارش ویژه ناموفق بود.', ) } finally { setIsSubmitting(false) } } return (
{ if (!isSubmitting) onClose() }} role="presentation" >
event.stopPropagation()} >

Special Orders

{isEditing ? 'ویرایش سفارش ویژه' : 'ثبت سفارش ویژه'}

void handleSubmit(e)}> {error && (
{error}
)}
{ setUserQuery(e.target.value) setUser(null) setUserOpen(true) }} onFocus={() => setUserOpen(true)} placeholder="جستجو با نام یا موبایل" disabled={isSubmitting} autoComplete="off" autoFocus={!isEditing} />
{userOpen && userQuery.trim().length >= 2 && (
    {userLoading ? (
  • در حال جستجو...
  • ) : userResults.length === 0 ? (
  • کاربری یافت نشد
  • ) : ( userResults.map((item) => (
  • )) )}
)}
{user ? (

{formatCellNumber(user.cellNumber)}

) : (

{userQuery.trim().length > 0 && userQuery.trim().length < 2 ? 'حداقل ۲ حرف وارد کنید' : 'کاربر را از نتایج جستجو انتخاب کنید'}

)}
دسته‌بندی
{specialOrderCategories.map((item) => ( ))}
setName(e.target.value)} placeholder="مثلاً کیک تولد سفارشی" disabled={isSubmitting} />