Overview
The user management interface allows administrators to create, edit, and delete employee accounts. Each user has a role (employee or admin) and can be assigned to specific areas for incident routing.
Accessing User Management
Navigate to the employees page from the dashboard sidebar:
This route requires admin authentication.
User Roles
Incidents App supports three distinct roles:
Guest Temporary access via QR code scan. Can create incidents for their room.
Employee Staff members who respond to incidents. Limited to their assigned areas.
Admin Full access to dashboard, user management, and all incidents.
Role Hierarchy
Employee List
The main interface displays all employees in a table:
Columns
Column Description Editable Name Full name Yes Email Login email Yes Role employee or admin Yes Area Assigned department Yes Created Account creation date No Actions Edit/Delete menu -
Implementation
The employees page fetches users from the profiles table:
app/(dashboard)/dashboard/employees/page.tsx
'use client'
import * as React from "react" ;
import { supabase } from "@/lib/supabase" ;
import { Button } from "@/components/ui/button" ;
import { Table } from "@/components/ui/table" ;
export default function EmployeesPage () {
const [ employees , setEmployees ] = React . useState < any []>([]);
const [ loading , setLoading ] = React . useState ( true );
const fetchEmployees = React . useCallback ( async () => {
setLoading ( true );
const { data , error } = await supabase
. from ( "profiles" )
. select ( `
id,
full_name,
email,
role,
area,
created_at
` )
. in ( 'role' , [ 'empleado' , 'admin' ])
. order ( "created_at" , { ascending: false });
if ( data ) setEmployees ( data );
setLoading ( false );
}, []);
React . useEffect (() => {
fetchEmployees ();
}, [ fetchEmployees ]);
return (
< div className = "p-6" >
< div className = "mb-6 flex items-center justify-between" >
< h1 className = "text-3xl font-bold" > Employees </ h1 >
< Button onClick = {() => setCreateOpen ( true )} >
Add Employee
</ Button >
</ div >
{ /* Table component */ }
</ div >
);
}
Creating Users
Admins can create new employee accounts through a side sheet form:
Open Create Sheet
Click the “Add Employee” button in the top right
Fill in Details
Enter the employee’s information:
Full Name : Display name (e.g., “John Smith”)
Email : Login email (must be unique)
Password : Initial password (employee can change later)
Role : Select employee or admin
Area : Assign to a department (optional)
Submit
Click “Create” to register the user in Supabase Auth
Notify Employee
Share login credentials with the new employee
Implementation
async function createEmployee ( data : {
email : string ;
password : string ;
fullName : string ;
role : 'empleado' | 'admin' ;
area ?: string ;
}) {
// 1. Create auth user
const { data : authData , error : authError } = await supabase . auth . signUp ({
email: data . email ,
password: data . password ,
options: {
data: {
full_name: data . fullName ,
role: data . role ,
area: data . area ,
}
}
});
if ( authError ) {
console . error ( 'Error creating user:' , authError );
return false ;
}
// 2. Update profile (if using triggers, this happens automatically)
const { error : profileError } = await supabase
. from ( 'profiles' )
. update ({
full_name: data . fullName ,
role: data . role ,
area: data . area ,
})
. eq ( 'id' , authData . user ?. id );
if ( profileError ) {
console . error ( 'Error updating profile:' , profileError );
return false ;
}
return true ;
}
When a user is created in Supabase Auth, a corresponding row is automatically created in the profiles table via database trigger.
Editing Users
Update employee information:
Editable Fields
Full Name : Update display name
Role : Change between employee and admin
Area : Reassign to different department
Email : Update login email (requires Supabase Auth update)
Implementation
async function updateEmployee ( userId : string , updates : {
fullName ?: string ;
role ?: 'empleado' | 'admin' ;
area ?: string ;
email ?: string ;
}) {
// Update profile table
const { error : profileError } = await supabase
. from ( 'profiles' )
. update ({
full_name: updates . fullName ,
role: updates . role ,
area: updates . area ,
})
. eq ( 'id' , userId );
if ( profileError ) return false ;
// If email changed, update auth user (requires service_role key)
if ( updates . email ) {
// This requires admin API access
// Implement server-side API route for email updates
}
return true ;
}
Password Reset
Employees can reset their own passwords through the mobile app. Admins can trigger password reset emails:
async function sendPasswordReset ( email : string ) {
const { error } = await supabase . auth . resetPasswordForEmail ( email , {
redirectTo: 'https://yourapp.com/reset-password' ,
});
return ! error ;
}
Deleting Users
Deleting a user removes their account from Supabase Auth and cascades to the profiles table. Assigned incidents will lose their assignee reference.
Soft Delete (Recommended)
Mark users as inactive instead of deleting:
async function deactivateEmployee ( userId : string ) {
const { error } = await supabase
. from ( 'profiles' )
. update ({
active: false ,
deactivated_at: new Date (). toISOString ()
})
. eq ( 'id' , userId );
return ! error ;
}
Hard Delete
Permanently remove a user:
async function deleteEmployee ( userId : string ) {
// 1. Unassign all incidents
await supabase
. from ( 'incidents' )
. update ({ assigned_to: null })
. eq ( 'assigned_to' , userId );
// 2. Delete from auth (requires service_role key via API route)
const response = await fetch ( '/api/admin/delete-user' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ userId })
});
return response . ok ;
}
Area Assignment
Assigning employees to areas helps route incidents automatically:
How It Works
Employee is assigned an area string (e.g., “mantenimiento”)
When viewing their inbox, they see incidents matching their area
Incidents in their area appear in the “Buzon General” tab
Multiple Area Support
To support multiple areas per employee, store as comma-separated string:
// Store
area : "mantenimiento,climatizacion"
// Parse
const userAreas = employee . area ?. split ( ',' ) || [];
// Query
const { data } = await supabase
. from ( 'incidents' )
. select ( '*, area:areas(name)' )
. filter ( 'area.name' , 'in' , `( ${ userAreas . join ( ',' ) } )` );
Alternatively, create a junction table:
CREATE TABLE employee_areas (
employee_id UUID REFERENCES profiles(id) ON DELETE CASCADE ,
area_id UUID REFERENCES areas(id) ON DELETE CASCADE ,
PRIMARY KEY (employee_id, area_id)
);
Permissions Management
Role-based permissions are enforced at multiple levels:
Application Level
Middleware and route guards:
export function middleware ( request : NextRequest ) {
const { pathname } = request . nextUrl ;
if ( pathname . startsWith ( "/dashboard" )) {
const hasSession = Array . from ( request . cookies . getAll ()). some (
( c ) => c . name . startsWith ( "sb-" ) && c . name . endsWith ( "-auth-token" )
);
if ( ! hasSession ) {
return NextResponse . redirect ( new URL ( "/" , request . url ));
}
}
return NextResponse . next ();
}
Database Level
Row Level Security policies:
-- Employees can only view incidents in their assigned area
CREATE POLICY "Employees view own area incidents"
ON incidents FOR SELECT
USING (
auth . uid () IN (
SELECT id FROM profiles
WHERE role = 'empleado'
AND area = ( SELECT name FROM areas WHERE id = incidents . area_id )
)
);
-- Admins can view all incidents
CREATE POLICY "Admins view all incidents"
ON incidents FOR SELECT
USING (
EXISTS (
SELECT 1 FROM profiles
WHERE id = auth . uid ()
AND role = 'admin'
)
);
User Search and Filtering
Find employees quickly:
By Name
const { data } = await supabase
. from ( 'profiles' )
. select ( '*' )
. ilike ( 'full_name' , `% ${ searchTerm } %` )
. in ( 'role' , [ 'empleado' , 'admin' ]);
By Role
const { data } = await supabase
. from ( 'profiles' )
. select ( '*' )
. eq ( 'role' , 'empleado' );
By Area
const { data } = await supabase
. from ( 'profiles' )
. select ( '*' )
. eq ( 'area' , 'mantenimiento' );
Bulk Operations
Bulk Role Update
async function bulkUpdateRole ( userIds : string [], newRole : 'empleado' | 'admin' ) {
const { error } = await supabase
. from ( 'profiles' )
. update ({ role: newRole })
. in ( 'id' , userIds );
return ! error ;
}
Bulk Area Assignment
async function bulkAssignArea ( userIds : string [], area : string ) {
const { error } = await supabase
. from ( 'profiles' )
. update ({ area })
. in ( 'id' , userIds );
return ! error ;
}
Export User List
Export employee data for HR or reporting:
function exportUsersToCSV ( users : any []) {
const headers = [ 'Name' , 'Email' , 'Role' , 'Area' , 'Created' ];
const rows = users . map ( user => [
user . full_name ,
user . email ,
user . role ,
user . area || 'N/A' ,
new Date ( user . created_at ). toLocaleDateString ()
]);
const csv = [ headers , ... rows ]. map ( row => row . join ( ',' )). join ( ' \n ' );
const blob = new Blob ([ csv ], { type: 'text/csv' });
const url = URL . createObjectURL ( blob );
const link = document . createElement ( 'a' );
link . href = url ;
link . download = `employees- ${ new Date (). toISOString () } .csv` ;
link . click ();
}
Security Best Practices
Strong Passwords Enforce minimum password requirements through Supabase settings
Email Verification Require email confirmation before first login
MFA (Optional) Enable two-factor authentication for admin accounts
Audit Logs Track user creation, updates, and deletions
Next Steps
Incident Management Learn how employees interact with incidents
Analytics View user performance metrics
Database Schema Explore the profiles table structure
Mobile App See how employees use the mobile app