feat: structured extensions table in customer detail view

4 columns: name, installed version, available update, actions
Actions: install (if not installed), update + remove (if installed)
This commit is contained in:
2026-03-26 06:50:35 +00:00
parent 188c5fb878
commit 4638763ee1
+60 -21
View File
@@ -161,6 +161,11 @@ function CustomerDetail({ customerId }) {
setDeploying(null)
}
const remove = async (extId) => {
await fetch(`${API}/customers/${customerId}/extensions/${extId}`, { method: 'DELETE' })
reload()
}
const assign = async (extId) => {
await fetch(`${API}/customers/${customerId}/extensions`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -180,27 +185,61 @@ function CustomerDetail({ customerId }) {
</div>
)}
<div className="bg-white rounded-lg shadow divide-y">
{(customer.extensions || []).map(e => (
<div key={e.id} className="p-3 flex items-center justify-between">
<div>
<span className="font-medium">{e.name}</span>
<span className="text-sm text-gray-500 mr-2">
{e.installed_version ? `v${e.installed_version}` : 'לא מותקן'}
</span>
{e.installed_version && e.latest_version && e.installed_version !== e.latest_version && (
<span className="text-xs bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded mr-2">
עדכון: v{e.latest_version}
</span>
)}
</div>
<button onClick={() => deploy(e.name, e.latest_version)}
disabled={deploying === e.name}
className="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600 disabled:opacity-50">
{deploying === e.name ? '...' : 'דחוף'}
</button>
</div>
))}
<div className="bg-white rounded-lg shadow overflow-hidden">
<table className="w-full text-right">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-3 text-sm font-medium text-gray-500">שם ההרחבה</th>
<th className="px-4 py-3 text-sm font-medium text-gray-500">מותקנת</th>
<th className="px-4 py-3 text-sm font-medium text-gray-500">עדכון זמין</th>
<th className="px-4 py-3 text-sm font-medium text-gray-500">פעולות</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{(customer.extensions || []).map(e => {
const installed = e.installed_version
const hasUpdate = installed && e.latest_version && installed !== e.latest_version
return (
<tr key={e.id} className="hover:bg-gray-50">
<td className="px-4 py-3 font-medium">{e.name}</td>
<td className="px-4 py-3">
{installed
? <Badge>v{installed}</Badge>
: <span className="text-sm text-red-500">לא מותקן</span>}
</td>
<td className="px-4 py-3">
{hasUpdate
? <span className="text-sm font-medium text-amber-600">v{e.latest_version}</span>
: <span className="text-sm text-gray-300"></span>}
</td>
<td className="px-4 py-3">
<div className="flex gap-2 justify-end">
{!installed ? (
<button onClick={() => deploy(e.name, e.latest_version)}
disabled={deploying === e.name}
className="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600 disabled:opacity-50">
{deploying === e.name ? '...' : 'התקנה'}
</button>
) : (
<>
<button onClick={() => deploy(e.name, e.latest_version)}
disabled={deploying === e.name}
className="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600 disabled:opacity-50">
{deploying === e.name ? '...' : 'עדכון'}
</button>
<button onClick={() => remove(e.id)}
className="bg-red-500 text-white px-3 py-1 rounded text-sm hover:bg-red-600">
הסרה
</button>
</>
)}
</div>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
{available.length > 0 && (