/** * Label manager — create labels with colors, assign to mails. */ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Card } from '@/components/ui/Card'; import { EmptyState } from '@/components/ui/EmptyState'; import { useToast } from '@/components/ui/Toast'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; import { Loader2, X } from 'lucide-react'; import { fetchLabels, createLabel, deleteLabel, type MailLabel, } from '@/api/mail'; const PRESET_COLORS = [ '#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899', '#6366f1', '#64748b', ]; export function LabelManager() { const { t } = useTranslation(); const toast = useToast(); const [labels, setLabels] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showForm, setShowForm] = useState(false); const [saving, setSaving] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); // ── Label form (RHF + Zod) ── const labelSchema = z.object({ name: z.string().min(1, 'required'), color: z.string().optional().default(PRESET_COLORS[0]), }); type LabelFormData = z.infer; const { register: registerLabel, handleSubmit: handleSubmitLabel, reset: resetLabel, watch: watchLabel, setValue: setLabelValue, formState: { errors: labelErrors } } = useForm({ resolver: zodResolver(labelSchema), defaultValues: { name: '', color: PRESET_COLORS[0] }, }); const colorValue = watchLabel('color'); const load = useCallback(() => { setLoading(true); fetchLabels() .then((data) => { setLabels(data); setError(null); }) .catch((err) => { setError(err instanceof Error ? err.message : String(err)); }) .finally(() => setLoading(false)); }, []); useEffect(() => { load(); }, [load]); const handleSave = useCallback(async (data: LabelFormData) => { setSaving(true); try { const label = await createLabel({ name: data.name, color: data.color }); setLabels((prev) => [...prev, label]); toast.success(t('mail.labelCreated')); setShowForm(false); resetLabel({ name: '', color: PRESET_COLORS[0] }); } catch (err) { toast.error(err instanceof Error ? err.message : String(err)); } finally { setSaving(false); } }, [toast, t, resetLabel]); const handleDelete = useCallback(async () => { if (!deleteTarget) return; try { await deleteLabel(deleteTarget.id); setLabels((prev) => prev.filter((l) => l.id !== deleteTarget.id)); toast.success(t('mail.labelDeleted')); setDeleteTarget(null); } catch (err) { toast.error(err instanceof Error ? err.message : String(err)); } }, [deleteTarget, toast, t]); if (loading) { return (
); } if (error) { return (

{error}

); } return (

{t('mail.labels')}

{showForm && (
{PRESET_COLORS.map((c) => (
)} {labels.length === 0 && !showForm ? ( ) : (
{labels.map((label) => (
{label.name}
))}
)} setDeleteTarget(null)} />
); }