fix: cancel button on form, edit customer, show installed/total count
This commit is contained in:
+39
-15
@@ -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'}`}>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user