Design: Organization Device Management Tab
Overview
Add a “Quản lý thiết bị” tab to the Organization Detail page. This tab lets Super Admins manage devices scoped to a specific organization. The tab reuses the existing DeviceList structure with org-filtered data and role-based permission gating.
Scope
Phase 1 only:
- CRUD devices that belong to the current organization (list, create, edit, delete)
- Devices not yet assigned to any organization are NOT included — only devices already belonging to this organization
- Permission matrix:
- Super Admin: full CRUD + assign/unassign
- Admin / Owner: read-only (view list only, no create/edit/delete buttons)
Out of scope for Phase 1:
- Bulk import/export
- Device firmware updates
- Device pairing/wiring to ponds (future)
Architecture
File Changes
| File | Change |
|---|---|
frontend/src/pages/organizations/OrganizationDetail.tsx |
Add new tab item to <Tabs> |
frontend/src/pages/organizations/components/OrganizationDevicesTab.tsx |
New — tab component |
frontend/src/pages/organizations/components/OrganizationDevicesTab.spec.tsx |
New — test |
Data Flow
OrganizationDetail (tab key: 'devices')
→ <OrganizationDevicesTab organization={organization} />
→ useDevices({ orgId: organization.id }) ← TanStack Query, filter by org
→ deviceService.createDevice({ ...data, organizationId: org.id }) ← auto-assign org
→ permission check via getOrganizationUserCapabilities
Key Decisions
- Filter on
orgId: BackendGET /devices?orgId={id}must return only devices belonging to this organization. This is validated at the service layer viaOrganizationFilterInterceptor. - Auto-assign on create: When Super Admin creates a device via this tab,
organizationIdis set toorganization.idbefore the API call — the form does not expose an org selector. - Permission gating: Button visibility (Create/Edit/Delete) is controlled by
tabCapabilitiesderived fromgetOrganizationUserCapabilities({ actorRole, selectedOrganizationId: organization.id }). - Reuse pattern: Follows the same pattern as
OrganizationUsersTab— same capability pattern, same form structure.
Components
OrganizationDevicesTab
Props:
interface OrganizationDevicesTabProps {
organization: Organization;
}
State:
page,limit,search— for list querydrawerVisible— toggle create/edit drawerselectedDevice— device being edited (or undefined for create)filters—{ type?, status? }for table filter
Behavior:
- Fetch:
useDevices({ orgId: organization.id, page, limit, search, type, status }) - Create:
deviceService.createDevice({ ...data, organizationId: organization.id }) - Edit:
deviceService.updateDevice(selectedDevice.id, data) - Delete:
deviceService.deleteDevice(id)
Permission-gated UI:
const tabCaps = getOrganizationUserCapabilities({ actorRole, actorOrganizationId, selectedOrganizationId: organization.id });
// Only Super Admin can see action buttons
{tabCaps.canEditUser && <Button icon={<EditOutlined />} onClick={() => handleEdit(device)} />}
{tabCaps.canDeleteUser && <Popconfirm onConfirm={() => handleDelete(device.id)}>...}
{tabCaps.canCreateUser && <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>Thêm thiết bị</Button>}
Note:
canEditUserandcanDeleteUserapply to device actions in this context. The naming follows the existinggetOrganizationUserCapabilitiespattern which is resource-agnostic. If needed, a dedicatedcanManageDevicesflag can be added to the capability logic — but for Phase 1, we reuse the existing permission flags.
Form (Drawer)
Fields:
name(required, string)serialNumber(required, string)type(required, enum: sensor / gateway / inverter / feeder)model(required, string)
Hidden fields (auto-set):
organizationId=organization.id
On submit:
- Validate with Zod schema
- Call
deviceService.createDeviceordeviceService.updateDevice - Close drawer, refetch list
UI Consistency
- Follows
OrganizationUsersTablayout pattern - Uses
PageHeaderpattern but stripped (noPageHeaderin tab — just header block) - Uses
DataTablefor table rendering - Status badge colors via
statusColorsfrom@/theme/colors - Ant Design
Tag,Button,Drawerconsistent with rest of app
Testing Strategy
Unit tests (OrganizationDevicesTab.spec.tsx):
- Super Admin sees Create button, Edit/Delete on rows
- Admin sees no action buttons
- Owner sees no action buttons
- Create form submits with correct
organizationId - Edit form pre-populates fields
- Delete shows confirmation and calls service
- Empty state shown when no devices
Integration tests (future):
- End-to-end flow via Super Admin creating device in org → device appears in list