import { useState, useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; import api from "../../api/client"; import { useAuth } from "../../auth/AuthContext"; const CONTACT_TYPES = ["email", "phone", "whatsapp", "other"]; const LANGUAGES = [ { value: "el", label: "Greek" }, { value: "en", label: "English" }, { value: "de", label: "German" }, { value: "fr", label: "French" }, { value: "it", label: "Italian" }, ]; const TITLES = ["", "Fr.", "Rev.", "Archim.", "Bp.", "Abp.", "Met.", "Mr.", "Mrs.", "Ms.", "Dr.", "Prof."]; const PRESET_TAGS = ["church", "monastery", "municipality", "school", "repeat-customer", "vip", "pending", "inactive"]; const CONTACT_TYPE_ICONS = { email: "๐Ÿ“ง", phone: "๐Ÿ“ž", whatsapp: "๐Ÿ’ฌ", other: "๐Ÿ”—", }; const inputClass = "w-full px-3 py-2 text-sm rounded-md border"; const inputStyle = { backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)", }; const labelStyle = { display: "block", marginBottom: 4, fontSize: 12, color: "var(--text-secondary)", fontWeight: 500, }; function Field({ label, children, style }) { return (
{children}
); } function SectionCard({ title, children }) { return (

{title}

{children}
); } const emptyContact = () => ({ type: "email", label: "", value: "", primary: false }); export default function CustomerForm() { const { id } = useParams(); const isEdit = Boolean(id); const navigate = useNavigate(); const { user, hasPermission } = useAuth(); const canEdit = hasPermission("crm", "edit"); const [form, setForm] = useState({ title: "", name: "", surname: "", organization: "", language: "el", tags: [], folder_id: "", location: { city: "", country: "", region: "" }, contacts: [], notes: [], }); const [loading, setLoading] = useState(isEdit); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [tagInput, setTagInput] = useState(""); const [newNoteText, setNewNoteText] = useState(""); const [editingNoteIdx, setEditingNoteIdx] = useState(null); const [editingNoteText, setEditingNoteText] = useState(""); useEffect(() => { if (!isEdit) return; api.get(`/crm/customers/${id}`) .then((data) => { setForm({ title: data.title || "", name: data.name || "", surname: data.surname || "", organization: data.organization || "", language: data.language || "el", tags: data.tags || [], folder_id: data.folder_id || "", location: { city: data.location?.city || "", country: data.location?.country || "", region: data.location?.region || "", }, contacts: data.contacts || [], notes: data.notes || [], }); }) .catch((err) => setError(err.message)) .finally(() => setLoading(false)); }, [id, isEdit]); const set = (field, value) => setForm((f) => ({ ...f, [field]: value })); const setLoc = (field, value) => setForm((f) => ({ ...f, location: { ...f.location, [field]: value } })); // Tags const addTag = (raw) => { const tag = raw.trim(); if (tag && !form.tags.includes(tag)) { set("tags", [...form.tags, tag]); } setTagInput(""); }; const removeTag = (tag) => set("tags", form.tags.filter((t) => t !== tag)); // Contacts const addContact = () => set("contacts", [...form.contacts, emptyContact()]); const removeContact = (i) => set("contacts", form.contacts.filter((_, idx) => idx !== i)); const setContact = (i, field, value) => { const updated = form.contacts.map((c, idx) => idx === i ? { ...c, [field]: value } : c); set("contacts", updated); }; const setPrimaryContact = (i) => { const type = form.contacts[i].type; const updated = form.contacts.map((c, idx) => ({ ...c, primary: c.type === type ? idx === i : c.primary, })); set("contacts", updated); }; // Notes const addNote = () => { if (!newNoteText.trim()) return; const note = { text: newNoteText.trim(), by: user?.name || "unknown", at: new Date().toISOString(), }; set("notes", [...form.notes, note]); setNewNoteText(""); }; const removeNote = (i) => { set("notes", form.notes.filter((_, idx) => idx !== i)); if (editingNoteIdx === i) setEditingNoteIdx(null); }; const startEditNote = (i) => { setEditingNoteIdx(i); setEditingNoteText(form.notes[i].text); }; const saveEditNote = (i) => { if (!editingNoteText.trim()) return; const updated = form.notes.map((n, idx) => idx === i ? { ...n, text: editingNoteText.trim(), at: new Date().toISOString() } : n ); set("notes", updated); setEditingNoteIdx(null); }; const buildPayload = () => ({ title: form.title.trim() || null, name: form.name.trim(), surname: form.surname.trim() || null, organization: form.organization.trim() || null, language: form.language, tags: form.tags, ...(!isEdit && { folder_id: form.folder_id.trim().toLowerCase() }), location: { city: form.location.city.trim(), country: form.location.country.trim(), region: form.location.region.trim(), }, contacts: form.contacts.filter((c) => c.value.trim()), notes: form.notes, }); const handleSave = async () => { if (!form.name.trim()) { setError("Customer name is required."); return; } if (!isEdit && !form.folder_id.trim()) { setError("Internal Folder ID is required."); return; } if (!isEdit && !/^[a-z0-9][a-z0-9\-]*[a-z0-9]$/.test(form.folder_id.trim().toLowerCase())) { setError("Internal Folder ID must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen."); return; } setSaving(true); setError(""); try { if (isEdit) { await api.put(`/crm/customers/${id}`, buildPayload()); navigate(`/crm/customers/${id}`); } else { const res = await api.post("/crm/customers", buildPayload()); navigate(`/crm/customers/${res.id}`); } } catch (err) { setError(err.message); } finally { setSaving(false); } }; const handleDelete = async () => { setSaving(true); try { await api.delete(`/crm/customers/${id}`); navigate("/crm/customers"); } catch (err) { setError(err.message); setSaving(false); } }; if (loading) { return
Loading...
; } return (

{isEdit ? "Edit Customer" : "New Customer"}

{canEdit && ( )}
{error && (
{error}
)} {/* Basic Info */} {/* Row 1: Title, Name, Surname */}
set("name", e.target.value)} placeholder="First name" /> set("surname", e.target.value)} placeholder="Last name" />
{/* Row 2: Organization, Language, Folder ID */}
set("organization", e.target.value)} placeholder="Church, organization, etc." /> {!isEdit ? ( set("folder_id", e.target.value.toLowerCase().replace(/[^a-z0-9\-]/g, ""))} placeholder="e.g. saint-john-corfu" /> ) : (
Folder ID
{form.folder_id || "โ€”"}
)}
{!isEdit && (

Lowercase letters, numbers and hyphens only. This becomes the Nextcloud folder name and cannot be changed later.

)} {/* Row 3: Tags */}
{form.tags.map((tag) => ( removeTag(tag)} title="Click to remove" > {tag} ร— ))}
{/* Preset quick-add tags */}
{PRESET_TAGS.filter((t) => !form.tags.includes(t)).map((t) => ( ))}
setTagInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === ",") { e.preventDefault(); addTag(tagInput); } }} onBlur={() => tagInput.trim() && addTag(tagInput)} placeholder="Type a custom tag and press Enter or comma..." />
{/* Location */}
setLoc("city", e.target.value)} placeholder="City" /> setLoc("country", e.target.value)} placeholder="Country" /> setLoc("region", e.target.value)} placeholder="Region" />
{/* Contacts */} {form.contacts.map((c, i) => (
{CONTACT_TYPE_ICONS[c.type] || "๐Ÿ”—"} setContact(i, "label", e.target.value)} placeholder="label (e.g. work)" /> setContact(i, "value", e.target.value)} placeholder="value" />
))}
{/* Notes */} {form.notes.length > 0 && (
{form.notes.map((note, i) => (
{editingNoteIdx === i ? (