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 */}
{!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 ? (
) : (
<>
{note.text}
{note.by} ยท {note.at ? new Date(note.at).toLocaleDateString() : ""}
>
)}
))}
)}
{/* Delete */}
{isEdit && canEdit && (
Danger Zone
{!showDeleteConfirm ? (
) : (
Are you sure? This cannot be undone.
)}
)}
);
}