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) setDeploying(null)
} }
const remove = async (extId) => {
await fetch(`${API}/customers/${customerId}/extensions/${extId}`, { method: 'DELETE' })
reload()
}
const assign = async (extId) => { const assign = async (extId) => {
await fetch(`${API}/customers/${customerId}/extensions`, { await fetch(`${API}/customers/${customerId}/extensions`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -180,27 +185,61 @@ function CustomerDetail({ customerId }) {
</div> </div>
)} )}
<div className="bg-white rounded-lg shadow divide-y"> <div className="bg-white rounded-lg shadow overflow-hidden">
{(customer.extensions || []).map(e => ( <table className="w-full text-right">
<div key={e.id} className="p-3 flex items-center justify-between"> <thead className="bg-gray-50">
<div> <tr>
<span className="font-medium">{e.name}</span> <th className="px-4 py-3 text-sm font-medium text-gray-500">שם ההרחבה</th>
<span className="text-sm text-gray-500 mr-2"> <th className="px-4 py-3 text-sm font-medium text-gray-500">מותקנת</th>
{e.installed_version ? `v${e.installed_version}` : 'לא מותקן'} <th className="px-4 py-3 text-sm font-medium text-gray-500">עדכון זמין</th>
</span> <th className="px-4 py-3 text-sm font-medium text-gray-500">פעולות</th>
{e.installed_version && e.latest_version && e.installed_version !== e.latest_version && ( </tr>
<span className="text-xs bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded mr-2"> </thead>
עדכון: v{e.latest_version} <tbody className="divide-y divide-gray-200">
</span> {(customer.extensions || []).map(e => {
)} const installed = e.installed_version
</div> const hasUpdate = installed && e.latest_version && installed !== e.latest_version
<button onClick={() => deploy(e.name, e.latest_version)} return (
disabled={deploying === e.name} <tr key={e.id} className="hover:bg-gray-50">
className="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600 disabled:opacity-50"> <td className="px-4 py-3 font-medium">{e.name}</td>
{deploying === e.name ? '...' : 'דחוף'} <td className="px-4 py-3">
</button> {installed
</div> ? <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> </div>
{available.length > 0 && ( {available.length > 0 && (