fix: cancel button on form, edit customer, show installed/total count

This commit is contained in:
2026-03-26 08:20:51 +00:00
parent 72c3087dd0
commit eea011d68a
2 changed files with 49 additions and 24 deletions
+39 -15
View File
@@ -119,7 +119,7 @@ function CustomersTab() {
<span>חדש</span> <span>חדש</span>
</button> </button>
</div> </div>
{showForm && <CustomerForm onDone={() => { setShowForm(false); reload() }} />} {showForm && <CustomerForm onDone={() => { setShowForm(false); reload() }} onCancel={() => setShowForm(false)} />}
<div className="space-y-2"> <div className="space-y-2">
{customers.map(c => ( {customers.map(c => (
<div key={c.id} onClick={() => setSelected(c.id)} <div key={c.id} onClick={() => setSelected(c.id)}
@@ -134,7 +134,7 @@ function CustomersTab() {
</div> </div>
</div> </div>
<div className="flex items-center gap-3 mt-3 text-xs text-slate-400"> <div className="flex items-center gap-3 mt-3 text-xs text-slate-400">
<span>{c.extension_count} הרחבות</span> <span>{c.installed_count || 0}/{c.extension_count} מותקנות</span>
<span></span> <span></span>
<span className={c.is_active ? 'text-emerald-600' : 'text-red-500'}>{c.is_active ? 'פעיל' : 'לא פעיל'}</span> <span className={c.is_active ? 'text-emerald-600' : 'text-red-500'}>{c.is_active ? 'פעיל' : 'לא פעיל'}</span>
</div> </div>
@@ -154,29 +154,43 @@ function CustomersTab() {
) )
} }
function CustomerForm({ onDone }) { function CustomerForm({ onDone, onCancel, customer }) {
const [form, setForm] = useState({ name: '', email: '', espocrm_url: '', espocrm_api_key: '' }) const isEdit = !!customer
const [form, setForm] = useState({
name: customer?.name || '', email: customer?.email || '',
espocrm_url: customer?.espocrm_url || '', espocrm_api_key: ''
})
const submit = async () => { const submit = async () => {
await fetch(`${API}/customers`, { const url = isEdit ? `${API}/customers/${customer.id}` : `${API}/customers`
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form) await fetch(url, {
method: isEdit ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form)
}) })
onDone() onDone()
} }
const inputClass = "w-full bg-slate-50 border-none rounded-xl px-4 py-2.5 text-sm focus:ring-2 focus:ring-blue-500"
return ( return (
<div className="bg-white rounded-xl p-5 mb-4 space-y-3"> <div className="bg-white rounded-xl p-5 mb-4 space-y-3">
<input placeholder="שם לקוח" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} <input placeholder="שם לקוח" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })}
className="w-full bg-slate-50 border-none rounded-xl px-4 py-2.5 text-sm focus:ring-2 focus:ring-blue-500 text-right" /> className={`${inputClass} text-right`} />
<input placeholder="אימייל" value={form.email} onChange={e => setForm({ ...form, email: e.target.value })} <input placeholder="אימייל" value={form.email} onChange={e => setForm({ ...form, email: e.target.value })}
className="w-full bg-slate-50 border-none rounded-xl px-4 py-2.5 text-sm focus:ring-2 focus:ring-blue-500" dir="ltr" /> className={inputClass} dir="ltr" />
<input placeholder="EspoCRM URL" value={form.espocrm_url} onChange={e => setForm({ ...form, espocrm_url: e.target.value })} <input placeholder="EspoCRM URL" value={form.espocrm_url} onChange={e => setForm({ ...form, espocrm_url: e.target.value })}
className="w-full bg-slate-50 border-none rounded-xl px-4 py-2.5 text-sm focus:ring-2 focus:ring-blue-500" dir="ltr" /> className={inputClass} dir="ltr" />
<input placeholder="Espo-Authorization (base64)" value={form.espocrm_api_key} <input placeholder={isEdit ? "Espo-Authorization (השאר ריק אם לא משתנה)" : "Espo-Authorization (base64)"}
onChange={e => setForm({ ...form, espocrm_api_key: e.target.value })} value={form.espocrm_api_key} onChange={e => setForm({ ...form, espocrm_api_key: e.target.value })}
className="w-full bg-slate-50 border-none rounded-xl px-4 py-2.5 text-sm font-mono focus:ring-2 focus:ring-blue-500" dir="ltr" /> className={`${inputClass} font-mono`} dir="ltr" />
<div className="flex gap-2">
<button onClick={submit} <button onClick={submit}
className="deep-action-gradient text-white px-4 py-2.5 rounded-xl font-bold w-full shadow-lg shadow-blue-500/20 active:scale-95 transition-all"> className="deep-action-gradient text-white px-4 py-2.5 rounded-xl font-bold flex-1 shadow-lg shadow-blue-500/20 active:scale-95 transition-all">
שמור {isEdit ? 'עדכון' : 'שמור'}
</button> </button>
<button onClick={onCancel}
className="px-4 py-2.5 rounded-xl font-bold text-slate-500 hover:bg-slate-100 transition-all">
ביטול
</button>
</div>
</div> </div>
) )
} }
@@ -186,6 +200,7 @@ function CustomerDetail({ customerId }) {
const { data: allExtensions } = useFetch(`${API}/extensions`) const { data: allExtensions } = useFetch(`${API}/extensions`)
const [deploying, setDeploying] = useState(null) const [deploying, setDeploying] = useState(null)
const [deployResult, setDeployResult] = useState(null) const [deployResult, setDeployResult] = useState(null)
const [editing, setEditing] = useState(false)
if (!customer) return <Loader /> if (!customer) return <Loader />
@@ -224,15 +239,24 @@ function CustomerDetail({ customerId }) {
return ( return (
<div> <div>
{editing ? (
<CustomerForm customer={customer} onDone={() => { setEditing(false); reload() }} onCancel={() => setEditing(false)} />
) : (
<div className="flex items-center gap-4 mb-6"> <div className="flex items-center gap-4 mb-6">
<div className="w-12 h-12 rounded-xl bg-blue-100 flex items-center justify-center text-blue-700"> <div className="w-12 h-12 rounded-xl bg-blue-100 flex items-center justify-center text-blue-700">
<Icon name="domain" className="text-2xl" /> <Icon name="domain" className="text-2xl" />
</div> </div>
<div className="text-right"> <div className="text-right flex-1">
<h3 className="text-xl font-black text-slate-900">{customer.name}</h3> <h3 className="text-xl font-black text-slate-900">{customer.name}</h3>
<p className="text-sm text-slate-500" dir="ltr">{customer.espocrm_url}</p> <p className="text-sm text-slate-500" dir="ltr">{customer.espocrm_url}</p>
</div> </div>
<button onClick={() => setEditing(true)}
className="p-2 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all"
title="עריכת לקוח">
<Icon name="edit" className="text-lg" />
</button>
</div> </div>
)}
{deployResult && ( {deployResult && (
<div className={`p-4 rounded-xl mb-6 text-sm font-medium flex items-center gap-3 ${deployResult.status === 'ok' ? 'bg-emerald-50 text-emerald-800' : 'bg-red-50 text-red-800'}`}> <div className={`p-4 rounded-xl mb-6 text-sm font-medium flex items-center gap-3 ${deployResult.status === 'ok' ? 'bg-emerald-50 text-emerald-800' : 'bg-red-50 text-red-800'}`}>
+1
View File
@@ -394,6 +394,7 @@ class APIHandler(BaseHTTPRequestHandler):
rows = query("""SELECT c.id, c.name, c.email, c.espocrm_url, c.is_active, c.notes, c.created_at, c.updated_at, rows = query("""SELECT c.id, c.name, c.email, c.espocrm_url, c.is_active, c.notes, c.created_at, c.updated_at,
c.coolify_url, c.coolify_app_uuid, c.coolify_url, c.coolify_app_uuid,
(SELECT COUNT(*) FROM customer_extensions ce WHERE ce.customer_id = c.id AND ce.license_status = 'active') as extension_count, (SELECT COUNT(*) FROM customer_extensions ce WHERE ce.customer_id = c.id AND ce.license_status = 'active') as extension_count,
(SELECT COUNT(*) FROM customer_extensions ce WHERE ce.customer_id = c.id AND ce.license_status = 'active' AND ce.installed_version IS NOT NULL) as installed_count,
(SELECT MAX(dl.started_at) FROM deployment_log dl WHERE dl.customer_id = c.id) as last_deploy (SELECT MAX(dl.started_at) FROM deployment_log dl WHERE dl.customer_id = c.id) as last_deploy
FROM customers c ORDER BY c.name""") FROM customers c ORDER BY c.name""")
self._respond(200, rows) self._respond(200, rows)