Spaces:
Sleeping
Platform Admin Frontend Development Guide
Last Updated: November 17, 2025
Target Audience: Frontend Developers
Backend Version: SwiftOps v1.0
Table of Contents
- Overview
- Authentication & Authorization
- Dashboard & Analytics
- Organization Management
- User Management
- System Monitoring
- Audit Logs
- Complete API Reference
- UI/UX Recommendations
- Error Handling
Overview
What is a Platform Admin?
Platform Admin is the highest-level administrative role with full system access. They manage:
- Organizations (Clients & Contractors)
- Platform-wide settings
- System health monitoring
- Audit logs and compliance
- Billing and usage metrics (planned)
Key Responsibilities:
- Create and manage client organizations (telecom operators)
- Create and manage contractor organizations (field service providers)
- Monitor platform usage across all organizations
- View comprehensive audit trails
- Manage platform-wide configurations
- Access all system data for oversight
Authorization Level:
{
role: "platform_admin",
permissions: ["*"], // All permissions
access_scope: "global" // Access to all organizations and projects
}
Authentication & Authorization
1. Registration Flow
Platform admins register through a secure 2-step OTP verification process.
Step 1: Request OTP
POST /api/v1/auth/send-admin-otp
Content-Type: application/json
{
"email": "admin@swiftops.com",
"first_name": "John",
"last_name": "Doe",
"phone": "+254700123456"
}
Response:
{
"message": "β
Registration request received! An OTP code has been sent to admin@swiftops.com..."
}
Step 2: Complete Registration
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "admin@swiftops.com",
"first_name": "John",
"last_name": "Doe",
"phone": "+254700123456",
"password": "SecurePassword123!",
"otp_code": "613281"
}
Response:
{
"message": "β
Platform admin account created successfully!",
"user_id": "uuid-here"
}
2. Login
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "admin@swiftops.com",
"password": "SecurePassword123!"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"user": {
"id": "uuid",
"email": "admin@swiftops.com",
"first_name": "John",
"last_name": "Doe",
"role": "platform_admin",
"is_active": true,
"phone": "+254700123456"
}
}
3. Authentication Headers
All subsequent requests must include:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
4. Get Current User
GET /api/v1/auth/me
Authorization: Bearer {token}
Response:
{
"id": "uuid",
"email": "admin@swiftops.com",
"role": "platform_admin",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"created_at": "2025-11-17T10:00:00Z"
}
Dashboard & Analytics
Dashboard Overview Page
The platform admin dashboard should display high-level metrics and system health.
1. System-Wide Statistics
Endpoint: GET /api/v1/tickets/stats
GET /api/v1/tickets/stats
Authorization: Bearer {token}
Response:
{
"total_tickets": 1500,
"by_status": {
"open": 120,
"assigned": 350,
"in_progress": 280,
"completed": 700,
"cancelled": 50
},
"by_type": {
"installation": 800,
"support": 500,
"infrastructure": 200
},
"by_priority": {
"urgent": 45,
"high": 200,
"normal": 900,
"low": 355
},
"overdue_tickets": 25,
"sla_violations": 12,
"avg_completion_time_hours": 18.5,
"completion_rate": 0.87
}
UI Component Suggestion:
// Dashboard Overview Cards
<div className="grid grid-cols-4 gap-4">
<StatCard
title="Total Tickets"
value={stats.total_tickets}
icon="ticket"
/>
<StatCard
title="In Progress"
value={stats.by_status.in_progress}
icon="clock"
/>
<StatCard
title="Completed Today"
value={stats.by_status.completed}
icon="check"
trend="+12%"
/>
<StatCard
title="SLA Violations"
value={stats.sla_violations}
icon="alert"
variant="danger"
/>
</div>
2. Organization Metrics
Clients Overview:
GET /api/v1/clients?limit=100
Authorization: Bearer {token}
Response:
[
{
"id": "uuid",
"name": "Airtel Kenya",
"industry": "Telecommunications",
"is_active": true,
"default_sla_days": 3,
"created_at": "2025-01-01T00:00:00Z",
"main_email": "contact@airtel.co.ke",
"main_phone": "+254700000000"
}
]
Contractors Overview:
GET /api/v1/contractors?limit=100
Authorization: Bearer {token}
Response:
[
{
"id": "uuid",
"name": "TechInstall Ltd",
"competencies": ["FTTH", "Fixed Wireless", "Fiber Splicing"],
"is_active": true,
"onboarding_status": "completed",
"created_at": "2025-01-15T00:00:00Z",
"main_email": "info@techinstall.co.ke"
}
]
UI Component Suggestion:
// Organizations Summary
<div className="grid grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3>Clients</h3>
<Badge>{clients.length} Active</Badge>
</CardHeader>
<CardBody>
<List>
{clients.map(client => (
<ListItem key={client.id}>
<Avatar name={client.name} />
<div>
<p className="font-semibold">{client.name}</p>
<p className="text-sm text-gray-500">{client.industry}</p>
</div>
<StatusBadge active={client.is_active} />
</ListItem>
))}
</List>
</CardBody>
</Card>
<Card>
<CardHeader>
<h3>Contractors</h3>
<Badge>{contractors.length} Active</Badge>
</CardHeader>
<CardBody>
{/* Similar list for contractors */}
</CardBody>
</Card>
</div>
3. Active Projects
GET /api/v1/projects?is_active=true&limit=100
Authorization: Bearer {token}
Response:
[
{
"id": "uuid",
"title": "Nairobi FTTH Expansion",
"client_id": "uuid",
"contractor_id": "uuid",
"client_name": "Airtel Kenya",
"contractor_name": "TechInstall Ltd",
"status": "active",
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"total_tickets": 500,
"completed_tickets": 320
}
]
UI Component Suggestion:
// Active Projects Table
<DataTable
columns={[
{ key: "title", label: "Project Name" },
{ key: "client_name", label: "Client" },
{ key: "contractor_name", label: "Contractor" },
{ key: "progress", label: "Progress", render: (row) => (
<ProgressBar
value={row.completed_tickets}
max={row.total_tickets}
/>
)},
{ key: "status", label: "Status" }
]}
data={projects}
/>
4. Recent Activity Feed
GET /api/v1/audit-logs?limit=20
Authorization: Bearer {token}
Response:
[
{
"id": "uuid",
"action": "create",
"entity_type": "client",
"description": "Client organization created: Safaricom Ltd",
"user_email": "admin@swiftops.com",
"created_at": "2025-11-17T10:30:00Z",
"ip_address": "192.168.1.1"
}
]
Organization Management
1. Create Client Organization
Purpose: Onboard new telecom operators or clients to the platform.
Endpoint:
POST /api/v1/clients
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Safaricom Kenya",
"description": "Leading telecommunications provider in Kenya",
"industry": "Telecommunications",
"main_email": "contact@safaricom.co.ke",
"main_phone": "+254700000000",
"website": "https://safaricom.co.ke",
"default_sla_days": 3
}
Response:
{
"id": "uuid",
"name": "Safaricom Kenya",
"description": "Leading telecommunications provider in Kenya",
"industry": "Telecommunications",
"main_email": "contact@safaricom.co.ke",
"main_phone": "+254700000000",
"website": "https://safaricom.co.ke",
"is_active": true,
"default_sla_days": 3,
"created_at": "2025-11-17T10:00:00Z",
"updated_at": "2025-11-17T10:00:00Z"
}
Validation Rules:
name: Required, unique, 3-100 charactersindustry: Optional, suggested values: "Telecommunications", "Utilities", "ISP", "Construction"main_email: Required, unique, valid email formatmain_phone: Optional, valid phone formatdefault_sla_days: Optional, integer 1-30, defaults to 3
UI Form Fields:
<Form onSubmit={handleCreateClient}>
<TextField
name="name"
label="Client Name"
required
placeholder="e.g., Safaricom Kenya"
/>
<TextArea
name="description"
label="Description"
rows={3}
placeholder="Brief description of the organization"
/>
<Select
name="industry"
label="Industry"
options={[
{ value: "Telecommunications", label: "Telecommunications" },
{ value: "Utilities", label: "Utilities" },
{ value: "ISP", label: "Internet Service Provider" },
{ value: "Construction", label: "Construction" }
]}
/>
<TextField
name="main_email"
label="Primary Email"
type="email"
required
placeholder="contact@example.com"
/>
<TextField
name="main_phone"
label="Primary Phone"
type="tel"
placeholder="+254700000000"
/>
<TextField
name="website"
label="Website"
type="url"
placeholder="https://example.com"
/>
<NumberField
name="default_sla_days"
label="Default SLA (Days)"
min={1}
max={30}
defaultValue={3}
/>
<Button type="submit">Create Client</Button>
</Form>
2. List Clients
Endpoint:
GET /api/v1/clients?skip=0&limit=100&is_active=true
Authorization: Bearer {token}
Query Parameters:
skip: Number of records to skip (pagination), default: 0limit: Max records to return (1-100), default: 100is_active: Filter by active status (optional)
Response: Array of client objects (see Create Client response for structure)
UI Component:
// Clients List/Table View
<PageHeader title="Clients" action={<Button onClick={openCreateModal}>+ New Client</Button>} />
<DataTable
columns={[
{ key: "name", label: "Name", sortable: true },
{ key: "industry", label: "Industry" },
{ key: "main_email", label: "Email" },
{ key: "main_phone", label: "Phone" },
{ key: "is_active", label: "Status", render: (row) => (
<StatusBadge active={row.is_active} />
)},
{ key: "created_at", label: "Created", render: (row) => (
formatDate(row.created_at)
)},
{ key: "actions", label: "", render: (row) => (
<DropdownMenu>
<MenuItem onClick={() => viewClient(row.id)}>View Details</MenuItem>
<MenuItem onClick={() => editClient(row.id)}>Edit</MenuItem>
<MenuItem onClick={() => deactivateClient(row.id)} variant="danger">
Deactivate
</MenuItem>
</DropdownMenu>
)}
]}
data={clients}
pagination={{
page: currentPage,
pageSize: 100,
total: totalClients,
onPageChange: setCurrentPage
}}
/>
3. View Client Details
Endpoint:
GET /api/v1/clients/{client_id}
Authorization: Bearer {token}
Response: Single client object
UI Page Structure:
// Client Details Page
<PageLayout>
<PageHeader
title={client.name}
subtitle={client.industry}
actions={[
<Button onClick={editClient}>Edit</Button>,
<Button variant="secondary" onClick={viewProjects}>View Projects</Button>
]}
/>
<Tabs>
<Tab label="Overview">
<div className="grid grid-cols-2 gap-6">
<Card title="Contact Information">
<dl>
<dt>Email</dt>
<dd>{client.main_email}</dd>
<dt>Phone</dt>
<dd>{client.main_phone}</dd>
<dt>Website</dt>
<dd><a href={client.website}>{client.website}</a></dd>
</dl>
</Card>
<Card title="Settings">
<dl>
<dt>Default SLA</dt>
<dd>{client.default_sla_days} days</dd>
<dt>Status</dt>
<dd><StatusBadge active={client.is_active} /></dd>
<dt>Created</dt>
<dd>{formatDate(client.created_at)}</dd>
</dl>
</Card>
</div>
</Tab>
<Tab label="Projects">
{/* List projects associated with this client */}
</Tab>
<Tab label="Users">
{/* List users belonging to this client organization */}
</Tab>
<Tab label="Activity Log">
{/* Audit logs for this client */}
</Tab>
</Tabs>
</PageLayout>
4. Update Client
Endpoint:
PUT /api/v1/clients/{client_id}
Authorization: Bearer {token}
Content-Type: application/json
{
"description": "Updated description",
"is_active": true,
"default_sla_days": 5,
"main_phone": "+254711222333"
}
Response: Updated client object
Note: Only include fields you want to update. Name and email cannot be changed after creation.
5. Deactivate Client
Endpoint:
DELETE /api/v1/clients/{client_id}
Authorization: Bearer {token}
Response:
{
"message": "Client soft-deleted successfully"
}
Note: This performs a soft delete (sets deleted_at timestamp). The client is hidden from default queries but data is retained.
6. Create Contractor Organization
Purpose: Onboard field service contractors who will execute work for clients.
Endpoint:
POST /api/v1/contractors
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "FieldTech Solutions",
"description": "Professional FTTH installation services",
"website": "https://fieldtech.co.ke",
"main_email": "ops@fieldtech.co.ke",
"main_phone": "+254722333444",
"competencies": ["FTTH", "Fixed Wireless", "Fiber Splicing", "FTTB"]
}
Response:
{
"id": "uuid",
"name": "FieldTech Solutions",
"description": "Professional FTTH installation services",
"website": "https://fieldtech.co.ke",
"main_email": "ops@fieldtech.co.ke",
"main_phone": "+254722333444",
"is_active": true,
"competencies": ["FTTH", "Fixed Wireless", "Fiber Splicing", "FTTB"],
"onboarding_status": "started",
"onboarding_completed_at": null,
"created_at": "2025-11-17T10:00:00Z",
"updated_at": "2025-11-17T10:00:00Z"
}
Validation Rules:
name: Required, unique, 3-100 characterscompetencies: Required array, available options:- "FTTH" (Fiber to the Home)
- "FTTB" (Fiber to the Building)
- "Fixed Wireless"
- "Fiber Splicing"
- "Infrastructure"
- "Maintenance"
- "Support"
UI Form Fields:
<Form onSubmit={handleCreateContractor}>
<TextField
name="name"
label="Contractor Name"
required
placeholder="e.g., FieldTech Solutions"
/>
<TextArea
name="description"
label="Description"
rows={3}
placeholder="Brief description of services offered"
/>
<TextField
name="main_email"
label="Primary Email"
type="email"
required
placeholder="ops@example.com"
/>
<TextField
name="main_phone"
label="Primary Phone"
type="tel"
placeholder="+254700000000"
/>
<TextField
name="website"
label="Website"
type="url"
placeholder="https://example.com"
/>
<CheckboxGroup
name="competencies"
label="Competencies"
required
options={[
{ value: "FTTH", label: "Fiber to the Home (FTTH)" },
{ value: "FTTB", label: "Fiber to the Building (FTTB)" },
{ value: "Fixed Wireless", label: "Fixed Wireless" },
{ value: "Fiber Splicing", label: "Fiber Splicing" },
{ value: "Infrastructure", label: "Infrastructure" },
{ value: "Maintenance", label: "Maintenance" },
{ value: "Support", label: "Support" }
]}
/>
<Button type="submit">Create Contractor</Button>
</Form>
7. List Contractors
Endpoint:
GET /api/v1/contractors?skip=0&limit=100&is_active=true
Authorization: Bearer {token}
Query Parameters: Same as clients
Response: Array of contractor objects
8. View Contractor Details
Endpoint:
GET /api/v1/contractors/{contractor_id}
Authorization: Bearer {token}
Response: Single contractor object
9. Update Contractor
Endpoint:
PUT /api/v1/contractors/{contractor_id}
Authorization: Bearer {token}
Content-Type: application/json
{
"description": "Updated description",
"is_active": true,
"competencies": ["FTTH", "Fixed Wireless", "Fiber Splicing", "FTTB"],
"onboarding_status": "completed"
}
Response: Updated contractor object
Note: Setting onboarding_status to "completed" automatically sets onboarding_completed_at timestamp.
Onboarding Status Values:
"started"- Initial state"documents_pending"- Awaiting compliance documents"training"- Undergoing training"completed"- Fully onboarded, ready for work
User Management
1. List All Users
Endpoint:
GET /api/v1/users?skip=0&limit=100&role=field_agent
Authorization: Bearer {token}
Query Parameters:
skip: Pagination offsetlimit: Max records (1-100)role: Filter by role (optional)platform_adminclient_admincontractor_adminproject_managerdispatcherfield_agentsales_agentsales_manager
is_active: Filter by active status (optional)client_id: Filter by client organization (optional)contractor_id: Filter by contractor organization (optional)
Response:
[
{
"id": "uuid",
"email": "agent@example.com",
"first_name": "John",
"last_name": "Doe",
"role": "field_agent",
"is_active": true,
"phone": "+254700123456",
"client_id": null,
"contractor_id": "uuid",
"created_at": "2025-11-17T10:00:00Z"
}
]
2. View User Details
Endpoint:
GET /api/v1/users/{user_id}
Authorization: Bearer {token}
Response: Single user object
3. Invite New User
Endpoint:
POST /api/v1/invitations
Authorization: Bearer {token}
Content-Type: application/json
{
"email": "newuser@example.com",
"first_name": "Jane",
"last_name": "Smith",
"role": "project_manager",
"client_id": "uuid",
"notification_channel": "email"
}
Response:
{
"id": "uuid",
"email": "newuser@example.com",
"role": "project_manager",
"status": "pending",
"expires_at": "2025-11-24T10:00:00Z",
"created_at": "2025-11-17T10:00:00Z"
}
Notification Channels:
"email"- Send invitation via email"whatsapp"- Send invitation via WhatsApp (default, saves email credits)"both"- Send via both channels
4. Update User
Endpoint:
PUT /api/v1/users/{user_id}
Authorization: Bearer {token}
Content-Type: application/json
{
"first_name": "Jane",
"last_name": "Smith",
"phone": "+254711222333",
"is_active": true
}
Response: Updated user object
5. Change User Role
Endpoint:
PUT /api/v1/users/{user_id}/role
Authorization: Bearer {token}
Content-Type: application/json
{
"role": "dispatcher",
"reason": "Promoted due to excellent performance"
}
Response:
{
"message": "User role updated successfully",
"user": { /* updated user object */ }
}
Note: Role changes are logged in audit trail with reason.
6. Deactivate User
Endpoint:
POST /api/v1/users/{user_id}/deactivate
Authorization: Bearer {token}
Content-Type: application/json
{
"reason": "Employee left the company"
}
Response:
{
"message": "User deactivated successfully"
}
System Monitoring
1. Assignment Statistics
Endpoint:
GET /api/v1/assignments/stats?project_id={uuid}
Authorization: Bearer {token}
Response:
{
"total_assignments": 500,
"active_assignments": 120,
"completed_assignments": 350,
"dropped_assignments": 20,
"rejected_assignments": 10,
"avg_travel_time_minutes": 45.5,
"avg_work_time_minutes": 120.3,
"avg_journey_distance_km": 15.2
}
UI Component:
<Card title="Assignment Performance">
<div className="grid grid-cols-3 gap-4">
<Metric
label="Avg Travel Time"
value={`${stats.avg_travel_time_minutes} min`}
icon="car"
/>
<Metric
label="Avg Work Time"
value={`${stats.avg_work_time_minutes} min`}
icon="clock"
/>
<Metric
label="Avg Distance"
value={`${stats.avg_journey_distance_km} km`}
icon="map"
/>
</div>
<DonutChart
data={[
{ label: "Completed", value: stats.completed_assignments },
{ label: "Active", value: stats.active_assignments },
{ label: "Dropped", value: stats.dropped_assignments }
]}
/>
</Card>
2. Timesheet Statistics
Endpoint:
GET /api/v1/timesheets/stats?start_date=2025-11-01&end_date=2025-11-30
Authorization: Bearer {token}
Query Parameters:
start_date: Start date (required, YYYY-MM-DD)end_date: End date (required, YYYY-MM-DD)user_id: Filter by specific user (optional)project_id: Filter by specific project (optional)
Response:
{
"total_days": 30,
"present_days": 25,
"absent_days": 3,
"on_leave_days": 2,
"sick_leave_days": 0,
"attendance_rate": 83.33,
"absence_rate": 10.0,
"total_hours_worked": 200.5,
"average_hours_per_day": 8.02,
"total_tickets_completed": 150,
"average_tickets_per_day": 6.0
}
3. Notification Statistics
Endpoint:
GET /api/v1/notifications/stats
Authorization: Bearer {token}
Response:
{
"total": 500,
"unread": 45,
"read": 455,
"by_type": {
"ticket_assigned": 200,
"ticket_completed": 150,
"payment_received": 50,
"system_alert": 100
},
"by_channel": {
"in_app": 500,
"email": 300,
"whatsapp": 200
}
}
4. Export Data
Platform admins can export data for all organizations.
Tickets Export:
GET /api/v1/export/tickets?project_id={uuid}&format=csv
Authorization: Bearer {token}
Response: CSV file download
Available Export Endpoints:
/api/v1/export/tickets- All tickets/api/v1/export/customers- All customers/api/v1/export/sales-orders- All sales orders/api/v1/export/agents- All field agents/api/v1/export/subscriptions- All subscriptions
Query Parameters:
format:csvorexcel(default:csv)project_id: Filter by project (optional)start_date: Filter by date range (optional)end_date: Filter by date range (optional)
Audit Logs
1. View Audit Logs
Endpoint:
GET /api/v1/audit-logs?skip=0&limit=100&action=create
Authorization: Bearer {token}
Query Parameters:
skip: Pagination offsetlimit: Max records (1-100)action: Filter by action type (optional)create,update,deletelogin,logout,login_failedregister,password_changestatus_change,role_change
entity_type: Filter by entity (optional)user,client,contractor,project,ticket
user_id: Filter by user who performed action (optional)start_date: Filter by date range (optional)end_date: Filter by date range (optional)
Response:
[
{
"id": "uuid",
"user_id": "uuid",
"user_email": "admin@swiftops.com",
"user_role": "platform_admin",
"action": "create",
"entity_type": "client",
"entity_id": "uuid",
"description": "Client organization created: Safaricom Ltd by platform_admin",
"changes": {
"new": {
"name": "Safaricom Ltd",
"industry": "Telecommunications"
}
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2025-11-17T10:30:00Z"
}
]
2. View Audit Logs for Specific Entity
Endpoint:
GET /api/v1/audit-logs/entity/{entity_type}/{entity_id}
Authorization: Bearer {token}
Example:
GET /api/v1/audit-logs/entity/client/uuid-here
Response: Array of audit log objects filtered for that entity
3. View Audit Logs for Specific User
Endpoint:
GET /api/v1/audit-logs/user/{user_id}
Authorization: Bearer {token}
Response: Array of audit log objects for that user's actions
UI Component:
// Audit Log Table
<DataTable
columns={[
{ key: "created_at", label: "Timestamp", render: (row) => (
formatDateTime(row.created_at)
)},
{ key: "user_email", label: "User" },
{ key: "action", label: "Action", render: (row) => (
<Badge variant={getActionColor(row.action)}>{row.action}</Badge>
)},
{ key: "entity_type", label: "Entity" },
{ key: "description", label: "Description" },
{ key: "ip_address", label: "IP Address" },
{ key: "actions", label: "", render: (row) => (
<Button size="sm" onClick={() => viewDetails(row)}>Details</Button>
)}
]}
data={auditLogs}
pagination={true}
/>
// Audit Log Detail Modal
<Modal isOpen={detailsOpen} onClose={() => setDetailsOpen(false)}>
<ModalHeader>Audit Log Details</ModalHeader>
<ModalBody>
<dl>
<dt>Action</dt>
<dd>{selectedLog.action}</dd>
<dt>User</dt>
<dd>{selectedLog.user_email} ({selectedLog.user_role})</dd>
<dt>Entity</dt>
<dd>{selectedLog.entity_type} - {selectedLog.entity_id}</dd>
<dt>Timestamp</dt>
<dd>{formatDateTime(selectedLog.created_at)}</dd>
<dt>IP Address</dt>
<dd>{selectedLog.ip_address}</dd>
<dt>User Agent</dt>
<dd>{selectedLog.user_agent}</dd>
<dt>Description</dt>
<dd>{selectedLog.description}</dd>
{selectedLog.changes && (
<>
<dt>Changes</dt>
<dd>
<pre className="bg-gray-100 p-4 rounded">
{JSON.stringify(selectedLog.changes, null, 2)}
</pre>
</dd>
</>
)}
</dl>
</ModalBody>
</Modal>
4. Export Audit Logs
Endpoint:
POST /api/v1/audit-logs/export
Authorization: Bearer {token}
Content-Type: application/json
{
"start_date": "2025-11-01",
"end_date": "2025-11-30",
"format": "csv"
}
Response: CSV file download with all audit logs in date range
Complete API Reference
Authentication Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/send-admin-otp |
Request OTP for admin registration |
| POST | /api/v1/auth/register |
Complete admin registration with OTP |
| POST | /api/v1/auth/login |
Login and get access token |
| POST | /api/v1/auth/logout |
Logout (invalidate token) |
| GET | /api/v1/auth/me |
Get current user info |
| PUT | /api/v1/auth/me |
Update current user profile |
| POST | /api/v1/auth/change-password |
Change password |
| POST | /api/v1/auth/forgot-password |
Request password reset |
| POST | /api/v1/auth/reset-password |
Reset password with token |
Client Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/clients |
Create new client organization |
| GET | /api/v1/clients |
List all clients (paginated) |
| GET | /api/v1/clients/{client_id} |
Get client details |
| PUT | /api/v1/clients/{client_id} |
Update client |
| DELETE | /api/v1/clients/{client_id} |
Deactivate client (soft delete) |
Contractor Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/contractors |
Create new contractor organization |
| GET | /api/v1/contractors |
List all contractors (paginated) |
| GET | /api/v1/contractors/{contractor_id} |
Get contractor details |
| PUT | /api/v1/contractors/{contractor_id} |
Update contractor |
| DELETE | /api/v1/contractors/{contractor_id} |
Deactivate contractor (soft delete) |
User Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/users |
List all users (paginated) |
| GET | /api/v1/users/{user_id} |
Get user details |
| PUT | /api/v1/users/{user_id} |
Update user |
| PUT | /api/v1/users/{user_id}/role |
Change user role |
| POST | /api/v1/users/{user_id}/deactivate |
Deactivate user |
| POST | /api/v1/users/{user_id}/activate |
Activate user |
Invitation Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/invitations |
Create user invitation |
| GET | /api/v1/invitations |
List all invitations |
| GET | /api/v1/invitations/{invitation_id} |
Get invitation details |
| POST | /api/v1/invitations/{invitation_id}/resend |
Resend invitation |
| DELETE | /api/v1/invitations/{invitation_id} |
Cancel invitation |
Project Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/projects |
List all projects |
| GET | /api/v1/projects/{project_id} |
Get project details |
| POST | /api/v1/projects |
Create project (as client/contractor) |
| PUT | /api/v1/projects/{project_id} |
Update project |
| DELETE | /api/v1/projects/{project_id} |
Delete project |
Ticket Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/tickets |
List all tickets |
| GET | /api/v1/tickets/{ticket_id} |
Get ticket details |
| GET | /api/v1/tickets/stats |
Get ticket analytics |
| PUT | /api/v1/tickets/{ticket_id} |
Update ticket |
| DELETE | /api/v1/tickets/{ticket_id} |
Delete ticket |
Assignment Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/assignments/stats |
Get assignment statistics |
| GET | /api/v1/tickets/{ticket_id}/assignments |
Get ticket assignments |
Timesheet Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/timesheets |
List timesheets |
| GET | /api/v1/timesheets/stats |
Get timesheet statistics |
Finance Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/contractor-invoices |
List contractor invoices |
| GET | /api/v1/contractor-invoices/{invoice_id} |
Get invoice details |
| POST | /api/v1/contractor-invoices |
Create invoice |
| PUT | /api/v1/contractor-invoices/{invoice_id} |
Update invoice |
| POST | /api/v1/contractor-invoices/{invoice_id}/payments |
Record payment |
Audit Log Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/audit-logs |
List all audit logs (paginated) |
| GET | /api/v1/audit-logs/user/{user_id} |
Get logs for specific user |
| GET | /api/v1/audit-logs/entity/{entity_type}/{entity_id} |
Get logs for specific entity |
| POST | /api/v1/audit-logs/export |
Export audit logs to CSV |
Export Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/export/tickets |
Export tickets to CSV/Excel |
| GET | /api/v1/export/customers |
Export customers to CSV/Excel |
| GET | /api/v1/export/sales-orders |
Export sales orders to CSV/Excel |
| GET | /api/v1/export/agents |
Export field agents to CSV/Excel |
| GET | /api/v1/export/subscriptions |
Export subscriptions to CSV/Excel |
Notification Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/notifications |
List user notifications |
| GET | /api/v1/notifications/stats |
Get notification statistics |
| GET | /api/v1/notifications/unread-count |
Get unread count (for badge) |
| PUT | /api/v1/notifications/{notification_id}/read |
Mark notification as read |
| PUT | /api/v1/notifications/mark-all-read |
Mark all as read |
UI/UX Recommendations
Page Structure
1. Dashboard (Home Page)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Platform Admin Dashboard β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββ ββββββββ ββββββββ ββββββββ β
β β 1.5K β β 320 β β 87% β β 12 β β
β βTicketβ β Activeβ β SLA β βAlert β β
β ββββββββ ββββββββ ββββββββ ββββββββ β
β β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β
β β Clients β β Contractors β β
β β ==================== β β ==================== β β
β β β’ Airtel Kenya β β β’ TechInstall Ltd β β
β β β’ Safaricom β β β’ FieldTech Co β β
β β β’ Zuku β β β’ NetConnect Pro β β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Active Projects β β
β β ============================================= β β
β β Nairobi FTTH Expansion [ββββββββββ] 80% β β
β β Mombasa Fixed Wireless [ββββββββββ] 50% β β
β βββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Recent Activity β β
β β ============================================= β β
β β 10:30 - Client "Safaricom" created β β
β β 10:15 - User role changed: John β Dispatcher β β
β β 09:45 - Project "Nairobi FTTH" completed β β
β βββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Organizations Page
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Organizations [Clients] [Contractors] β
β [+ New Client] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β π Search: [________________] Filters: [v] β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Name β Industry β Status β Created ββ
β ββββββββββββββββΌββββββββββββΌβββββββββΌβββββββββββββ
β β Airtel Kenya β Telecom β Active β Jan 2025 ββ
β β Safaricom β Telecom β Active β Feb 2025 ββ
β β Zuku β ISP β Active β Mar 2025 ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β Showing 3 of 15 clients [1] [2] [3] > β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
3. Users Page
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Users [+ Invite] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Filters: Role: [All βΌ] Organization: [All βΌ] β
β Status: [Active βΌ] β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Name β Email β Role β Org ββ
β βββββββββββββΌβββββββββββββββΌββββββββββββΌβββββββββββ
β β John Doe β john@ex.com β Agent β Tech Ltdββ
β β Jane Smithβ jane@ex.com β Dispatcherβ Tech Ltdββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
4. Audit Logs Page
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Audit Logs [Export CSV] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Filters: Action: [All βΌ] Entity: [All βΌ] β
β Date Range: [2025-11-01] to [2025-11-30] β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Time β User β Action β Entity β IP ββ
β ββββββββββΌββββββββββββββΌβββββββββΌβββββββββΌβββββββββ
β β 10:30 β admin@... β CREATE β Client β 192...ββ
β β 10:15 β admin@... β UPDATE β User β 192...ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Navigation Structure
Sidebar Menu:
SwiftOps
βββββββββββββ
π Dashboard
π Analytics
Organizations
βββββββββββββ
π’ Clients
π§ Contractors
Users & Access
βββββββββββββ
π₯ Users
π¨ Invitations
Operations
βββββββββββββ
π Projects
π« Tickets
π¦ Inventory
Finance
βββββββββββββ
π° Invoices
π³ Payments
System
βββββββββββββ
π Audit Logs
βοΈ Settings
π€ Exports
Color Scheme
Status Colors:
- Active/Success:
#10b981(Green) - Pending/Warning:
#f59e0b(Amber) - Inactive/Error:
#ef4444(Red) - Info:
#3b82f6(Blue)
Role Colors:
- Platform Admin:
#8b5cf6(Purple) - Client Admin:
#3b82f6(Blue) - Contractor Admin:
#10b981(Green) - Manager:
#f59e0b(Amber) - Agent:
#6b7280(Gray)
Responsive Design
Breakpoints:
- Mobile: < 640px
- Tablet: 640px - 1024px
- Desktop: > 1024px
Mobile Considerations:
- Collapsible sidebar menu
- Stacked card layouts
- Horizontal scrolling for tables
- Bottom navigation bar for key actions
Error Handling
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Display data |
| 201 | Created | Show success message, redirect |
| 400 | Bad Request | Show validation errors |
| 401 | Unauthorized | Redirect to login |
| 403 | Forbidden | Show "Access denied" message |
| 404 | Not Found | Show "Resource not found" |
| 409 | Conflict | Show "Already exists" error |
| 500 | Server Error | Show generic error, report to support |
Error Response Format
{
"detail": "Email already registered"
}
Or for validation errors:
{
"detail": [
{
"loc": ["body", "email"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Error Handling Example
// API Service with Error Handling
async function createClient(clientData) {
try {
const response = await fetch('/api/v1/clients', {
method: 'POST',
headers: {
'Authorization': `Bearer ${getAccessToken()}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(clientData)
});
if (!response.ok) {
if (response.status === 401) {
// Token expired, redirect to login
redirectToLogin();
return;
}
if (response.status === 409) {
// Conflict - client already exists
const error = await response.json();
showError(error.detail);
return;
}
if (response.status === 400) {
// Validation errors
const errors = await response.json();
showValidationErrors(errors.detail);
return;
}
// Generic error
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const client = await response.json();
showSuccess('Client created successfully!');
return client;
} catch (error) {
console.error('Error creating client:', error);
showError('Failed to create client. Please try again.');
}
}
// Show error notification
function showError(message) {
// Use your notification system
toast.error(message);
}
// Show success notification
function showSuccess(message) {
toast.success(message);
}
// Show validation errors
function showValidationErrors(errors) {
if (Array.isArray(errors)) {
errors.forEach(error => {
const field = error.loc[error.loc.length - 1];
toast.error(`${field}: ${error.msg}`);
});
} else {
showError(errors);
}
}
Token Refresh Strategy
// Intercept 401 responses and refresh token
async function fetchWithAuth(url, options = {}) {
const token = getAccessToken();
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`
}
});
if (response.status === 401) {
// Try to refresh token
const refreshed = await refreshAccessToken();
if (refreshed) {
// Retry original request with new token
const newToken = getAccessToken();
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newToken}`
}
});
} else {
// Refresh failed, redirect to login
redirectToLogin();
throw new Error('Session expired');
}
}
return response;
}
async function refreshAccessToken() {
try {
const refreshToken = getRefreshToken();
const response = await fetch('/api/v1/auth/refresh-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken })
});
if (response.ok) {
const data = await response.json();
setAccessToken(data.access_token);
return true;
}
return false;
} catch (error) {
console.error('Token refresh failed:', error);
return false;
}
}
Implementation Checklist
Phase 1: Core Setup
- Setup authentication (login/register)
- Implement token storage and refresh
- Create API service layer
- Setup error handling
Phase 2: Dashboard
- Create dashboard layout
- Implement statistics cards
- Add organization summary widgets
- Add recent activity feed
Phase 3: Organization Management
- Create clients list page
- Implement create client form
- Create client details page
- Implement client update/delete
- Create contractors list page
- Implement create contractor form
- Create contractor details page
- Implement contractor update/delete
Phase 4: User Management
- Create users list page
- Implement user invitation form
- Create user details page
- Implement user update/deactivate
- Add role change functionality
Phase 5: Monitoring & Logs
- Create audit logs page
- Implement log filtering
- Add export functionality
- Create system monitoring dashboard
Phase 6: Polish
- Add loading states
- Implement pagination
- Add search functionality
- Optimize performance
- Add accessibility features
- Write unit tests
Best Practices
1. State Management
// Use React Context or Redux for global state
const AuthContext = React.createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check if user is logged in on mount
const checkAuth = async () => {
const token = getAccessToken();
if (token) {
const userData = await fetchCurrentUser();
setUser(userData);
}
setLoading(false);
};
checkAuth();
}, []);
return (
<AuthContext.Provider value={{ user, setUser, loading }}>
{children}
</AuthContext.Provider>
);
}
2. API Client
// Create a centralized API client
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const token = getAccessToken();
const config = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
...(token && { 'Authorization': `Bearer ${token}` })
}
};
const response = await fetch(url, config);
if (!response.ok) {
throw await this.handleError(response);
}
return response.json();
}
async handleError(response) {
const error = await response.json();
return new Error(error.detail || 'An error occurred');
}
// Convenience methods
get(endpoint, options) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, data, options) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data, options) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint, options) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
const api = new ApiClient('http://localhost:8000/api/v1');
export default api;
3. Custom Hooks
// useClients hook
function useClients(filters = {}) {
const [clients, setClients] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
const data = await api.get('/clients', { params: filters });
setClients(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchClients();
}, [JSON.stringify(filters)]);
return { clients, loading, error };
}
// Usage
function ClientsPage() {
const { clients, loading, error } = useClients({ is_active: true });
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ClientsList clients={clients} />;
}
4. Performance Optimization
// Debounce search input
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchableList() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);
const { data } = useSearch(debouncedSearch);
return (
<>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<List data={data} />
</>
);
}
Support & Resources
Backend Documentation:
- API Docs:
http://localhost:8000/api/docs(Swagger UI) - ReDoc:
http://localhost:8000/api/redoc - Health Check:
http://localhost:8000/health
Related Documentation:
- User Management:
/docs/USER_MANAGEMENT_IMPLEMENTATION.md - Organizations API:
/docs/dev/ORGANIZATIONS_API_GUIDE.md - Auth API:
/docs/dev/AUTH_API_GUIDE.md - Audit Logging:
/docs/agent/PASSWORD_RESET_COMPLETE.md
Contact:
- Backend Team: backend@swiftops.com
- Technical Support: support@swiftops.com
Document Version: 1.0
Last Updated: November 17, 2025
Maintained By: SwiftOps Backend Team