LabTrack is a full-stack web application that brings transparency and structure to campus lab operations. It connects asset visualization, complaint workflows, AI-assisted triage, and real-time admin operations into a single system.
- System Overview
- Architecture
- User Workflows
- Backend Routes & Endpoints
- Database Schema
- Services Layer
- Real-Time Communication
- Setup & Deployment
- API Reference
- Key Features
LabTrack solves the problem of scattered, untracked lab asset management by creating a single source of truth. Students and staff can report issues in context (visually on a lab map), admins get structured workflows to resolve them, and management gets data to plan maintenance.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, Framer Motion, Recharts |
| Backend | Node.js, Express.js |
| Database | Supabase (PostgreSQL) |
| Real-time | Socket.IO |
| Storage | Supabase Storage (complaint images) |
| AI | Google Gemini API (priority classification) |
| Export | ExcelJS, PDFKit, csv-parse |
- Deployment: Frontend on Vite dev server or static build, Backend on Node.js, Database/Storage on Supabase
- Auth: JWT-based, role-separated (student/admin)
- Real-time Sync: Socket.IO with role and user rooms
- Data Export: CSV, Excel, PDF with filters and summaries
┌─────────────────────────────────┐
│ Frontend (React) │
│ - LandingPage │
│ - LoginPage │
│ - StudentPage (labs + map) │
│ - AdminPage (dashboard, kanban)│
└──────────────┬──────────────────┘
│ HTTP/HTTPS (axios)
│ WebSocket (Socket.IO)
▼
┌─────────────────────────────────────────────────────────────┐
│ Backend (Express.js + Node) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ authRoutes│ │assetRoutes│ │complaints│ │adminRoutes│ │
│ │ │ │ │ │ routes │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ↓ ↓ ↓ ↓ │
│ ┌────────────────────────────────────────┐ │
│ │ Middleware: JWT Auth & Authorization │ │
│ └────────────────────────────────────────┘ │
│ ↓ ↓ ↓ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Services: Supabase, AI, Socket.IO │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │
HTTP │ │ WebSocket
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Supabase │ │ Socket.IO Server│
│ (PostgreSQL) │ │ (Real-time sync)│
│ (Storage) │ └──────────────────┘
└──────────────────┘
- User opens browser → Frontend loads from Vite dev server
- User logs in →
POST /api/auth/login→ JWT issued → stored in localStorage - User navigates to dashboard → Protected routes verify JWT
- Student views labs →
GET /api/assets/labs→ loaded from Supabase - Student selects a PC →
GET /api/assets/detail/:systemId→ detailed asset view - Student files complaint →
POST /api/complaints→ image uploaded → AI classifies → socket emitted - Admin receives update → Socket.IO emits to admin room → dashboard refreshes
- Admin moves complaint on kanban →
PATCH /api/admin/kanban/:id→ asset status updated → history logged → student notified via notification
1. Landing Page (public)
├─ View live snapshot (working/faulty/maintenance percentages)
├─ See labs and their status
└─ Read recent activity
2. Login
├─ Enter email and password
└─ Choose "Student" role tab
3. Student Portal (authenticated)
├─ "Current Arena" Tab
│ ├─ Select a lab from tabs
│ ├─ View lab visualization (grid of PCs)
│ │ ├─ Green = working
│ │ ├─ Red = faulty
│ │ └─ Amber = maintenance
│ ├─ Click a PC node
│ │ ├─ Right panel shows "PC Details"
│ │ │ └─ System ID, CPU, RAM, status, etc.
│ │ ├─ Switch to "Timeline" tab
│ │ │ ├─ View active complaints (pending/in_progress)
│ │ │ ├─ View past complaints (resolved)
│ │ │ ├─ Support existing complaint with +1 button
│ │ │ └─ See asset events (purchase, maintenance, complaints)
│ │ └─ Click "Report an Issue" button
│ │ ├─ Modal opens
│ │ ├─ Student writes description
│ │ ├─ Optionally attach image (JPG/PNG/WEBP)
│ │ ├─ Backend AI classifies priority
│ │ └─ Complaint created, asset marked faulty
│ └─ Search by system ID or original ID
│
└─ "My Complaints" Tab
├─ List of all student's submitted complaints
├─ Filter by status, priority, lab, section, date range
├─ Sort by newest, oldest, priority
└─ See complaint details and status history
4. Notifications
├─ Bell icon in header
├─ Receive when admin resolves complaint
├─ Real-time push via Socket.IO
└─ Mark as read (if implemented)
1. Login
├─ Enter email and password
└─ Choose "Admin" role tab
2. Admin Control Center (authenticated)
├─ "Dashboard" Tab
│ ├─ KPI cards (total assets, faulty count, open complaints)
│ ├─ Charts
│ │ ├─ Complaints by lab (bar chart)
│ │ ├─ Status breakdown (pending/in_progress/resolved)
│ │ └─ Update in real-time as complaints change
│ ├─ Export Section
│ │ ├─ Select data type (complaints, inventory, both)
│ │ ├─ Select format (CSV, Excel, PDF)
│ │ ├─ Apply filters (lab, section, category, date range, priority, status)
│ │ ├─ Click export button
│ │ └─ Download file (CSV, XLSX, or PDF)
│ └─ Import Section (if CSV uploaded)
│ ├─ Bulk import assets from CSV/Excel file
│ ├─ Validation and duplicate detection
│ └─ Feedback on imported/skipped rows
│
├─ "Complaints" Tab (Kanban Board)
│ ├─ Three columns: Pending | In Progress | Resolved
│ ├─ Urgent complaints section (top)
│ │ ├─ Shows complaints breaching or near SLA
│ │ ├─ Color-coded: breached (red) vs near breach (amber)
│ │ ├─ Shows age in hours/days
│ │ └─ Quick "Focus in Board" button
│ ├─ Kanban Filters
│ │ ├─ Search by system ID, issue description, student name
│ │ ├─ Filter by status, priority, affected students count
│ │ ├─ Filter by lab, section
│ │ ├─ Filter by date range
│ │ └─ Sort by newest, oldest, priority high-low, priority low-high
│ ├─ Card (each complaint)
│ │ ├─ System ID and location
│ │ ├─ Student name and email
│ │ ├─ Priority badge
│ │ ├─ Support count
│ │ ├─ Issue description
│ │ ├─ Drag to move between columns
│ │ └─ Click to see full details
│ └─ On Move (drag & drop)
│ ├─ Backend updates complaint status
│ ├─ Asset status changes accordingly:
│ │ ├─ pending or in_progress → asset faulty
│ │ ├─ in_progress → asset maintenance
│ │ └─ resolved → asset working/maintenance/faulty (auto-detect)
│ ├─ History event logged
│ ├─ Student notified (if resolved)
│ ├─ Real-time update broadcast via Socket.IO
│ └─ Dashboard refreshes
│
└─ Admin Notifications
├─ Bell icon in header
├─ Receive when new complaint filed
├─ Receive when complaint gets +1 support
├─ Real-time push via Socket.IO
└─ Linked to admin control center
3. Admin can perform:
├─ View all complaints across all students
├─ Move complaint status (state machine)
├─ Bulk export data for reporting
├─ See complaint-to-asset and asset-to-complaints relationships
└─ Monitor lab health and response times
- Public
- Body:
{ name, email, password, role } - Response:
{ id, name, email, role } - Flow:
- Validate required fields
- Check if email already exists
- Hash password with bcryptjs (salt 10)
- Insert into
userstable - Return user object (no token)
- Public
- Body:
{ email, password, role } - Response:
{ token: "JWT", user: { id, name, email, role } } - Flow:
- Fetch user by email
- Compare password hash with bcryptjs
- Verify role matches (if role provided)
- Generate JWT (expires 1 day)
- Return token and user object
- Frontend stores in localStorage
- Public (no auth required)
- Response:
{ "snapshot": { "totalAssets": 124, "working": 82, "maintenance": 12, "faulty": 6, "summary": "..." }, "labStatuses": [ { "id": "LAB 2", "state": "ok|maint|fault" } ], "recentActivity": ["event 1", "event 2", ...] } - Used by: LandingPage
- Flow:
- Count all assets
- Count by status
- Fetch first 12 history events
- Group complaints by lab
- Calculate percentages
- Return snapshot
- Authenticated (any role)
- Response:
[ { "lab": "LAB 2", "sections": { "2A": 10, "2B": 10 }, "total": 20, "faulty": 2 } ] - Used by: StudentPage (lab tabs)
- Flow:
- Query all assets
- Group by lab and section
- Count total and faulty per lab
- Return array of lab summaries
- Authenticated (any role)
- Query:
q(optional search term) - Response: Array of assets in that lab
[ { "id": "uuid", "system_id": "LAB-2-A-1-1", "lab": "LAB 2", "section": "2A", "row_num": 1, "position": 1, "status": "working", "cpu": "Intel i7", "ram": "16GB", ... } ] - Used by: LabVisualizer (map of PCs)
- Flow:
- Query assets by lab
- Order by section, row, position (for grid layout)
- Filter by search term (system_id or original_id) if provided
- Return asset list
- Authenticated (any role)
- Response:
{ "asset": { id, system_id, lab, cpu, ram, status, ... }, "history": [ { id, event_type, details, event_date, ... } ], "complaints": [ { id, description, priority, status, user_id, users: { name }, ... } ] } - Used by: StudentPage (right panel detail view)
- Flow:
- Query asset by system_id
- Query history events for that asset
- Query complaints for that asset
- Enrich complaints with support count
- Return combined object
- Authenticated
- Response: Array of complaints
- Students only see their own complaints
- Admins see all complaints
- Flow:
- Query all complaints with asset and user joins
- Filter by current user if role is student
- Return complaint list
- Authenticated
- Response: Array of notifications
- Filter by user role and user ID
- Used by: NotificationBell
- Flow:
- Query notifications
- Filter by role_target and user_id
- Limit to 25 most recent
- Return notification list
- Public (no auth required)
- Query:
days(default 3),limit(default 6) - Response: Array of old pending/in_progress complaints with age
- Used by: LandingPage (showing overdue issues to public)
- Flow:
- Calculate cutoff date (days ago)
- Query pending and in_progress complaints before cutoff
- Calculate age in days
- Limit to N results
- Return overdue complaints
- Authenticated (student only)
- Body:
{ assetId, description, priority (optional), image (optional multipart) } - Response: Created complaint object
- This is a critical endpoint. Full flow:
- Validate asset and description exist
- Image Upload (if provided):
- Upload to Supabase Storage (complaint-images bucket)
- Get public URL
- AI Classification:
- Call Gemini API with complaint description
- Extract priority (Low/Medium/High)
- Fallback to Medium if Gemini unavailable
- Create Complaint:
- Insert into complaints table with status='pending'
- Store both priority (user-provided or AI) and ai_priority
- Update Asset Status:
- Mark asset as 'faulty'
- Log History Event:
- Insert into history table with event_type='Complaint Logged'
- Create Notification:
- Insert into notifications table (role_target='admin')
- Emit Socket Update:
- Broadcast to admin room: complaint_created event
- Return created complaint object
- Authenticated (student only)
- Body:
{ }(empty) - Response: Updated complaint with support count
- This allows students to "+1" an existing complaint (indicate they have same issue):
- Validate:
- Complaint exists
- Complaint not resolved
- User is not the reporter
- User hasn't already supported this complaint
- Update:
- Add user_id to supporter_ids array
- Increment support_count
- Log History Event:
- Insert into history table with event_type='Complaint +1'
- Create Notification:
- Insert into notifications (role_target='admin')
- Emit Socket Update:
- Broadcast to admin room and reporter's user room
- Return updated complaint
- Validate:
- Authenticated (admin only)
- Response:
{ "totals": { "assets": 124, "faulty": 6, "complaints": 8 }, "complaintsPerLab": [ { "name": "LAB 2", "value": 3 } ], "byStatus": [ { "name": "pending", "value": 2 }, { "name": "in_progress", "value": 4 }, { "name": "resolved", "value": 2 } ] } - Used by: AdminPage (dashboard charts)
- Flow:
- Count total assets
- Count faulty assets
- Fetch all complaints (with joins to assets)
- Group by lab and status
- Return metrics
- Authenticated (admin only)
- Response: Array of all complaints with asset and user joins
- Used by: KanbanBoard
- Flow:
- Query complaints with nested asset and user data
- Order by created_at descending
- Return complaint list (frontend filters into columns)
- Authenticated (admin only)
- Body:
{ status }(pending | in_progress | resolved) - Response: Updated complaint object
- This is a critical endpoint. Full flow:
- Validate:
- Status is in allowed list
- Complaint exists
- Complaint not already resolved (state machine lock)
- Update Complaint:
- Set status and updated_at timestamp
- Update Asset Status (complex logic):
- If moving to 'resolved':
- Query remaining pending/in_progress complaints for this asset
- If others exist in in_progress → asset becomes 'maintenance'
- Else if others exist in pending → asset becomes 'faulty'
- Else → asset becomes 'working'
- If moving to 'pending' or 'in_progress':
- Asset becomes 'faulty' (or 'maintenance' if in_progress)
- If moving to 'resolved':
- Log History Event:
- Insert into history table with event_type and details
- Create Notification (if resolved):
- Insert into notifications targeting the student
- Message: "Your complaint has been resolved"
- Emit Socket Updates:
- Broadcast to admin room: complaint_updated
- Broadcast to student's user room: complaint_resolved (or complaint_updated)
- Return updated complaint
- Validate:
- Authenticated (admin only)
- Response: Array of notifications for admins
- Flow:
- Query notifications where role_target='admin' or role_target IS NULL
- Limit to 20 most recent
- Return notification list
- Authenticated (admin only)
- Body: Multipart form data with file (CSV or Excel)
- Response:
{ imported, skippedDuplicates, validationErrors } - Flow:
- Parse File:
- Detect if CSV or Excel
- Parse into rows with columns (system_id, original_id, lab, section, row_num, position, status, cpu, ram, purchase_date, last_maintenance, etc.)
- Validate:
- Check required fields (system_id, original_id, lab, section)
- Detect duplicates within file
- Log validation errors
- Deduplicate:
- Query existing assets by system_id
- Filter out rows that already exist in DB
- Insert:
- Bulk insert final rows into assets table
- Return summary
- Parse File:
- Authenticated (admin only)
- Query Params:
?format=csv|excel|pdf &dataType=inventory|complaints|both &lab=LAB%202 §ion=2A &category=... &status=pending|in_progress|resolved &assetStatus=working|faulty|maintenance &priority=Low|Medium|High &search=... &from=2024-01-01 &to=2024-01-31 - Response: File download (blob) or PDF stream
- Flow:
- Parse Filters:
- Extract and normalize all query params
- Convert dates to ISO format (start of day and end of day)
- Fetch Inventory (if dataType includes inventory):
- Query assets with filters applied
- Join with complaints to get counts
- Format dates and compute complaint stats
- Fetch Complaints (if dataType includes complaints):
- Query complaints with filters
- Join with assets and users
- Fetch history events for timeline
- Build status history timeline
- Calculate affected students count
- Format & Render:
- CSV: Build CSV string with proper escaping
- Excel: Use ExcelJS to create workbook with sheets, frozen header, auto-filter
- PDF: Use PDFKit to create formatted document with sections, summaries, detailed cards
- Stream Response:
- Set appropriate headers (Content-Type, Content-Disposition)
- Send file to client
- Trigger browser download
- Parse Filters:
All tables use uuid primary keys with auto-generation via gen_random_uuid().
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
email text UNIQUE NOT NULL,
password_hash text NOT NULL,
role user_role NOT NULL DEFAULT 'student', -- ENUM: 'student', 'admin'
created_at timestamptz DEFAULT now()
);- Purpose: Store user accounts and authentication
- Indexes: Email is unique
- Roles:
student: Can view assets, file complaints, support complaints, view notificationsadmin: Can manage complaints, view analytics, export data, import assets, send notifications
CREATE TABLE assets (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
system_id text UNIQUE NOT NULL, -- e.g., "LAB-2-A-1-1"
original_id text UNIQUE NOT NULL, -- e.g., "PC-00123"
lab text NOT NULL, -- e.g., "LAB 2"
section text NOT NULL, -- e.g., "2A"
row_num int NOT NULL,
position int NOT NULL,
status asset_status NOT NULL DEFAULT 'working', -- ENUM: 'working', 'faulty', 'maintenance'
cpu text NOT NULL, -- e.g., "Intel i7-10700K"
ram text NOT NULL, -- e.g., "16GB DDR4"
purchase_date date NOT NULL,
last_maintenance date NOT NULL,
created_at timestamptz DEFAULT now()
);
CREATE INDEX idx_assets_lab_section ON assets(lab, section);
CREATE INDEX idx_assets_system_original ON assets(system_id, original_id);- Purpose: Store inventory of lab computers
- Status Flow:
- Starts as
working - Becomes
faultywhen complaint filed or moved to pending/in_progress - Becomes
maintenancewhen complaint moved to in_progress - Returns to
workingormaintenancewhen all complaints resolved
- Starts as
- Location: Grid position by lab, section, row, position (for visualizer)
CREATE TABLE complaints (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
asset_id uuid NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
description text NOT NULL, -- Issue description from student
priority priority_level NOT NULL DEFAULT 'Medium', -- ENUM: 'Low', 'Medium', 'High'
ai_priority priority_level, -- Priority from Gemini classification
image_url text, -- URL to complaint image in Storage
status complaint_status NOT NULL DEFAULT 'pending', -- ENUM: 'pending', 'in_progress', 'resolved'
support_count int NOT NULL DEFAULT 0, -- How many other students +1'd this
supporter_ids uuid[] NOT NULL DEFAULT '{}', -- Array of user IDs who supported
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
CREATE INDEX idx_complaints_status_priority ON complaints(status, priority);
CREATE INDEX idx_complaints_created_at ON complaints(created_at);
CREATE INDEX idx_complaints_asset_status_created ON complaints(asset_id, status, created_at DESC);- Purpose: Store issue reports filed by students
- Workflow:
- Created as
pending→ Admin sees in Pending column - Moved to
in_progressby admin → Asset becomes maintenance - Moved to
resolvedby admin → Asset auto-status calculated → Student notified - Cannot move backward from
resolved(state machine lock)
- Created as
- Support Mechanism:
- Other students can "+1" a complaint to indicate they have the same issue
support_countincrements,supporter_idsarray grows- Increases visibility for admin
CREATE TABLE history (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
asset_id uuid NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
event_type text NOT NULL, -- e.g., 'Complaint Logged', 'Resolved'
details text NOT NULL, -- Descriptive message
event_date timestamptz NOT NULL DEFAULT now(),
created_at timestamptz DEFAULT now()
);
CREATE INDEX idx_history_asset_date ON history(asset_id, event_date DESC);- Purpose: Audit trail for each asset
- Event Types:
Complaint Logged: Student filed complaintComplaint +1: Student supported existing complaintComplaint Status Updated: Admin moved to different statusComplaint Resolved: Complaint marked resolvedPurchase,Maintenance: Seeded data events
CREATE TABLE notifications (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL,
message text NOT NULL,
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
role_target user_role, -- If NULL, targets everyone; if set, targets role
is_read boolean DEFAULT false,
created_at timestamptz DEFAULT now()
);
CREATE INDEX idx_notifications_user_role_created ON notifications(user_id, role_target, created_at DESC);- Purpose: Notifications for students and admins
- Targeting:
role_target='admin', user_id=null→ All adminsrole_target='student', user_id=<uuid>→ Specific studentrole_target=null→ All users
- Use Cases:
- New complaint filed → Notify all admins
- Complaint resolved → Notify specific student
- Complaint received +1 → Notify all admins
// Creates and exports Supabase client
// Uses environment variables:
// - SUPABASE_URL: Base URL for Supabase project
// - SUPABASE_SERVICE_ROLE_KEY: Service role key with full RLS bypass
//
// Auth mode disabled (persistSession: false, autoRefreshToken: false)
// because backend is stateless and uses service role for server-side operationsKey Points:
- Service role key has full database and storage access (bypass RLS)
- Frontend uses a separate publishable key (if used) for client-side ops
- All queries go through this singleton instance
- Errors are thrown up to Express error handler
Used by: Every route that needs database access
export const classifyComplaint = async (description) => {
// Calls Google Gemini API
// Prompt: "Analyze this lab complaint and return only: Low, Medium, or High. Complaint: {description}"
// Returns: { priority: 'Low'|'Medium'|'High', source: 'gemini'|'fallback' }
// Fallback: If GEMINI_API_KEY not set or API fails, return 'Medium'
}Key Points:
- Runs on each complaint creation (not retroactive)
- Classifies based on issue severity keywords
- Fallback ensures system works without AI (graceful degradation)
- Result stored separately in
ai_priorityfield - User can override with manual priority selection
Example:
- Input: "Screen is completely black, can't boot"
- Output:
{ priority: 'High', source: 'gemini' }
// Initializes Socket.IO server on HTTP server
// Handles real-time bidirectional communication
// Socket Connection Flow:
// 1. Client connects with JWT token in auth object
// 2. Server extracts token, verifies JWT
// 3. Socket user context set (id, role, email)
// 4. Socket joins rooms:
// - role:{role} → e.g., role:admin, role:student
// - user:{userId} → e.g., user:550e8400-e29b-41d4-a716-446655440000
// 5. Socket emits 'labtrack:connected' event to client
// Emission Functions:
export const emitRoleUpdate = (role, payload) => {
// Sends to all sockets in role:{role} room
// Used for: admin dashboard updates, kanban changes
}
export const emitUserUpdate = (userId, payload) => {
// Sends to all sockets in user:{userId} room
// Used for: student notifications, personal complaint updates
}Room Structure:
- Role Rooms:
role:admin,role:student- All users with that role automatically joined
- Used for broadcast updates (e.g., new complaint to all admins)
- User Rooms:
user:<uuid>- One-to-one targeting
- Used for personal notifications (e.g., complaint resolved just for that student)
Events:
- Client receives:
labtrack:updatewith payload{ type, complaintId, assetId, userId, status, ... } - Example Payload:
{ "type": "complaint_updated", "complaintId": "550e8400-e29b-41d4-a716-446655440000", "assetId": "550e8400-e29b-41d4-a716-446655440001", "status": "in_progress" }
Frontend Integration (frontend/src/lib/socket.js):
const socket = getSocket(token);
socket.on('labtrack:update', (payload) => {
// Refresh local state or re-fetch from API
// Triggers dashboard/board re-render
});// middleware/auth.js
export const authenticate = (req, res, next) => {
// Extracts JWT from "Authorization: Bearer <token>" header
// Verifies token against JWT_SECRET
// Sets req.user = decoded payload (id, email, role, name)
// Passes to next middleware or route handler
// Throws 401 if missing or invalid
}
export const authorize = (...roles) => (req, res, next) => {
// Checks if req.user.role is in allowed roles
// Used as: router.use(authenticate, authorize('admin'))
// Throws 403 if not authorized
}Example Usage:
// Only admin route
router.get('/admin/dashboard', authenticate, authorize('admin'), (req, res) => {
// req.user guaranteed to have role='admin'
});
// Student or admin
router.get('/assets', authenticate, (req, res) => {
// Any authenticated user
// Check role inside handler if needed
});-
Student Files Complaint:
POST /api/complaints ├─ Create complaint row ├─ Update asset to faulty ├─ Log history event ├─ Create notification └─ emitRoleUpdate('admin', { type: 'complaint_created', complaintId: '...', assetId: '...' }) Socket.IO Broadcast ├─ All admins receive 'labtrack:update' event └─ Frontend: Load new kanban card -
Admin Moves Complaint:
PATCH /api/admin/kanban/:id { status: 'in_progress' } ├─ Update complaint status ├─ Update asset status to maintenance ├─ Log history event ├─ emitRoleUpdate('admin', { type: 'complaint_updated' }) ├─ emitUserUpdate(student_id, { type: 'complaint_updated' }) └─ emitUserUpdate(student_id, { type: 'complaint_resolved' }) (if status === 'resolved') Socket.IO Broadcasts ├─ Admin room: Card moves between columns └─ Student room: Notification + real-time update -
Frontend Socket Listeners (StudentPage & AdminPage):
useEffect(() => { const socket = getSocket(session?.token); socket.on('labtrack:update', async (payload) => { // Refresh all relevant data await loadAssets(); await loadComplaints(); await refreshSelectedDetail(); }); return () => { socket.off('labtrack:update', handleUpdate); }; }, [session?.token]);
Backend (.env in backend/):
PORT=4000
FRONTEND_URL=http://localhost:5173
JWT_SECRET=your_very_secret_key_min_32_chars
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...
SUPABASE_BUCKET=complaint-images
GEMINI_API_KEY=AIzaSyD...
Frontend (.env in frontend/):
VITE_API_URL=http://localhost:4000/api
VITE_SOCKET_URL=http://localhost:4000
- Create Supabase project
- Run SQL files in order:
supabase/schema.sql supabase/seed.sql
- Create storage bucket
complaint-imageswith public read policy
# Root
npm install
# Run both frontend and backend with live reload
npm run dev
# Frontend only
npm run dev -w frontend
# Backend only
npm run dev -w backendFrontend: http://localhost:5173 Backend: http://localhost:4000 API: http://localhost:4000/api
Admin: [email protected] / Password@123
Student: [email protected] / Password@123
- LAB 2: Sections 2A, 2B, 2C | 2 rows × 10 PCs = 60 total
- LAB 3A: Sections L, R | 4 × 4 grid = 32 total
- LAB 3B: Sections L, R | 4 × 4 grid = 32 total
- Status: ~70% working, ~20% faulty, ~10% maintenance
| Method | Endpoint | Auth | Body | Response |
|---|---|---|---|---|
| POST | /api/auth/signup |
❌ | { name, email, password, role } |
{ id, name, email, role } |
| POST | /api/auth/login |
❌ | { email, password, role } |
{ token, user } |
| Method | Endpoint | Auth | Response |
|---|---|---|---|
| GET | /api/assets/landing-snapshot |
❌ | Snapshot data |
| GET | /api/assets/labs |
✅ | Lab list with sections and counts |
| GET | /api/assets/:lab |
✅ | Assets in lab (searchable by system_id) |
| GET | /api/assets/detail/:systemId |
✅ | Asset detail + history + complaints |
| Method | Endpoint | Auth | Body | Response |
|---|---|---|---|---|
| GET | /api/complaints |
✅ | - | Student's complaints or all (if admin) |
| GET | /api/complaints/public-overdue |
❌ | - | Overdue complaints |
| GET | /api/complaints/notifications |
✅ | - | User's notifications |
| POST | /api/complaints |
✅ | { assetId, description, priority?, image? } |
Created complaint |
| POST | /api/complaints/:id/plus |
✅ | - | Updated complaint with support |
| Method | Endpoint | Auth | Body | Response |
|---|---|---|---|---|
| GET | /api/admin/dashboard |
✅ Admin | - | KPI totals and charts data |
| GET | /api/admin/kanban |
✅ Admin | - | All complaints for kanban |
| PATCH | /api/admin/kanban/:id |
✅ Admin | { status } |
Updated complaint |
| GET | /api/admin/notifications |
✅ Admin | - | Admin notifications |
| POST | /api/admin/import |
✅ Admin | File (CSV/Excel) | Import summary |
| GET | /api/admin/export |
✅ Admin | Query filters | CSV/Excel/PDF file |
- Interactive grid of lab computers
- Real-time status indication (working/faulty/maintenance)
- Click-to-inspect detail panel
- Search by system ID or original ID
- Responsive to status changes via Socket.IO
- Student submits issue with optional image attachment
- AI auto-classifies priority (Gemini)
- Admin kanban: drag-and-drop status management
- State machine prevents resolved complaints from moving backward
- Support mechanism (+1) aggregates duplicate issues
- Auto-notification when resolved
- WebSocket broadcasts to admins when new complaint filed
- Instant board refresh when status moved
- Student notified immediately when complaint resolved
- No page refresh needed
- CSV: Full raw data for spreadsheet analysis
- Excel: Formatted workbook with frozen header and auto-filter
- PDF: Admin-ready report with summaries and detailed cards
- Filters: Lab, section, category, status, priority, date range, search
- Every asset change logged to history table
- Timeline visible to students and admins
- Timestamps and actor information captured
- Used for reporting and accountability
Frontend shows "Failed to load assets":
- Check
VITE_API_URLenvironment variable - Verify backend is running on port 4000
- Check CORS in backend (should allow
FRONTEND_URL)
JWT token invalid after time:
- Token expires after 1 day
- User must log in again
- Check
JWT_SECRETconsistency between backend instances
Complaints not appearing in real-time:
- Check Socket.IO connection in browser DevTools (Network → WS)
- Verify backend socket.js is properly initialized
- Check browser console for socket errors
Export file is empty or missing data:
- Verify filters are applied correctly (check query params)
- Check Supabase query permissions and data integrity
- Test CSV export first (simpler than PDF)
Gemini AI not classifying priorities:
- Check
GEMINI_API_KEYis set in backend .env - Check API key is valid and has quota
- System falls back to Medium priority if API fails (no error thrown)
- Real-time sync: Socket.IO single server (no cluster support yet)
- Database queries: Limited indexing on complaints and history
- Export: Limited to ~200 rows in PDF (by design)
- Image storage: No automatic cleanup (storage grows)
- Use Supabase connection pooling for high load
- Add Redis for session management if scaling
- Implement complaint archival after 30 days
- Add CDN for image storage distribution
- Consider Kafka or Redis Streams for event backlog
- Database Backups: Enable Supabase automated backups
- Image Cleanup: Implement cron job to delete old complaint images
- Log Rotation: Check backend logs for errors
- Dependency Updates: Keep npm packages current for security
- Monitoring: Set up Supabase metrics alerts
# Export via Supabase CLI
supabase db dump --db-url postgres://... > backup.sql
# Restore
psql < backup.sqlWhen extending LabTrack:
- New Routes: Add to appropriate router file, follow existing patterns
- Database Changes: Update schema.sql, manage migrations
- Socket Events: Use
emitRoleUpdate()oremitUserUpdate()with clear payload - Frontend Changes: Test real-time sync with Socket.IO
- Export Features: Add columns to INVENTORY_COLUMNS or COMPLAINT_COLUMNS arrays
LabTrack © 2024. Campus Lab Management System.
Created: April 2026
Last Updated: April 16, 2026
Status: Active Development