feat: port inventory (انبارداری) page from tauri to admin dashboard
Add a per-tenant (doctor/clinic) Inventory domain and admin page, ported from clinic-pro-tauri /inventory (which was static/mock) into a real feature. Backend (src/Inventory/): - Entities InventoryItem, InventoryPackage, InventoryPackageItem, scoped via entity_type/entity_id like TenantTag. Item status is derived, package total and availability derived at read time. - InventoryService (stats, package assembly, availability), thin InventoryController with CRUD for items and packages + categories endpoint. - Migration + docs/api/inventory.md + functional tests (10 tests, 42 assertions). Frontend (assets/admin/): - InventoryPage with two tabs (کالاهای مصرفی / پکیج), stat cards, items table (desktop + mobile cards), packages accordion, add/edit item and package modals, search + category filter — pixel-matched to the tauri source. - useInventory hook (TanStack Query), route + sidebar link for doctor/clinic. - Vitest coverage (real data, empty state, modal, packages tab). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -852,3 +852,56 @@ html, body { max-width: 100%; overflow-x: hidden; }
|
||||
@media (max-width: 560px) {
|
||||
.settings-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* ── Inventory (انبارداری) ─────────────────────────────────────────────────
|
||||
Status pill colors mirror clinic-pro-tauri src/components/inventory/list/
|
||||
InventoryStatus.jsx exactly, in both light and dark. */
|
||||
.inv-badge {
|
||||
display: inline-block; width: 100px; text-align: center;
|
||||
padding: 4px 8px; border-radius: 4px; font-size: 14px; font-weight: 500;
|
||||
}
|
||||
.inv-badge.in-stock { background: #E8FADD; color: #3c9a4f; }
|
||||
.inv-badge.low-stock { background: #FFF3DD; color: #FDBA35; }
|
||||
.inv-badge.out-of-stock { background: #FFE3E2; color: #FF5450; }
|
||||
.dark .inv-badge.in-stock { background: #324A32; color: #75E22E; }
|
||||
.dark .inv-badge.low-stock { background: #4E4234; color: #FDBA35; }
|
||||
.dark .inv-badge.out-of-stock { background: rgba(255,84,80,0.20); color: #FF5450; }
|
||||
|
||||
/* Tab row — mirrors inventory/TabsRow + TabItem. */
|
||||
.inv-tabs {
|
||||
display: flex; align-items: flex-end; gap: 24px;
|
||||
border-bottom: 1px solid #EDEDED; margin: 4px 0 18px; width: 100%;
|
||||
overflow-x: auto; scrollbar-width: none;
|
||||
}
|
||||
.inv-tabs::-webkit-scrollbar { display: none; }
|
||||
.dark .inv-tabs { border-color: #35343D; }
|
||||
.inv-tab {
|
||||
display: flex; align-items: center; gap: 6px; padding: 0 8px 6px;
|
||||
font-size: 16px; cursor: pointer; white-space: nowrap;
|
||||
border-bottom: 2px solid transparent; color: #6B7280; background: none;
|
||||
}
|
||||
.dark .inv-tab { color: #A1A1A1; }
|
||||
.inv-tab.active { border-bottom: 3px solid #5559ce; color: #5559ce; }
|
||||
.dark .inv-tab.active { color: #5559ce; }
|
||||
|
||||
/* Inventory responsive table/card switch (md breakpoint = 768px, like tauri). */
|
||||
.inv-desktop { display: block; }
|
||||
.inv-mobile { display: none; }
|
||||
@media (max-width: 767px) {
|
||||
.inv-desktop { display: none; }
|
||||
.inv-mobile { display: grid; }
|
||||
}
|
||||
.inv-table { width: 100%; border-collapse: collapse; }
|
||||
.inv-table thead tr { background: #e1e1e1; }
|
||||
.inv-table th {
|
||||
color: #616161; font-size: 14px; font-weight: 400;
|
||||
padding: 10px 18px; text-align: start; white-space: nowrap;
|
||||
}
|
||||
.inv-table td {
|
||||
padding: 12px 18px; text-align: start; white-space: nowrap;
|
||||
font-size: 16px; font-weight: 500; color: var(--text-2);
|
||||
border-bottom: 1px solid #DBDBDB;
|
||||
}
|
||||
.dark .inv-table td { border-color: var(--border); }
|
||||
.inv-table tbody tr:hover { background: #f4f5fd; }
|
||||
.dark .inv-table tbody tr:hover { background: var(--surface-2); }
|
||||
|
||||
Reference in New Issue
Block a user