Home Reference

Reference

Technical reference
By Ilija Ćurić
6 articles

API Endpoints

CHRIS uses Supabase's auto-generated REST API (PostgREST) for database operations. Base URL https://[PROJECT_REF].supabase.co/rest/v1 Authentication All requests require authentication via JWT token. Headers Authorization: Bearer <JWT_TOKEN> apikey: <SUPABASE_ANON_KEY> Content-Type: application/json Getting a Token const { data: { session } } = await supabase.auth.getSession(); const token = session?.access_token; Profiles List Profiles GET /profiles Query parameters: | Parameter | Description | Example | |-----------|-------------|---------| | select | Columns to return | *,teams(name) | | status | Filter by status | eq.active | | team_id | Filter by team | eq.<uuid> | | order | Sort order | full_name.asc | Example: GET /profiles?select=id,full_name,email,status&status=eq.active&order=full_name.asc Get Single Profile GET /profiles?id=eq.<uuid> Update Profile PATCH /profiles?id=eq.<uuid> Content-Type: application/json { "full_name": "John Doe", "team_id": "<team-uuid>" } Leave Requests List Leave Requests GET /leave_requests Query parameters: | Parameter | Description | Example | |-----------|-------------|---------| | user_id | Filter by employee | eq.<uuid> | | status | Filter by status | eq.pending | | start_date | Filter by start | gte.2025-01-01 | | end_date | Filter by end | lte.2025-12-31 | Example with joins: GET /leave_requests?select=*,profiles(full_name),leave_types(name_en)&status=eq.pending Create Leave Request POST /leave_requests Content-Type: application/json { "user_id": "<user-uuid>", "leave_type_id": "<type-uuid>", "start_date": "2025-02-01", "end_date": "2025-02-05", "working_days": 5, "reason": "Family vacation" } Update Leave Request (Approve/Reject) PATCH /leave_requests?id=eq.<uuid> Content-Type: application/json { "status": "approved", "approver_user_id": "<approver-uuid>", "approver_comment": "Approved. Enjoy your vacation!", "approved_at": "2025-01-15T10:00:00Z" } Teams List Teams GET /teams?select=*,profiles!teams_lead_user_id_fkey(full_name) Create Team POST /teams Content-Type: application/json { "name": "Engineering", "lead_user_id": "<leader-uuid>" } Get Team Members GET /profiles?team_id=eq.<team-uuid>&select=id,full_name,email Translations Get All Translations GET /translations?select=key,croatian,english,russian,hindi Get by Category GET /translations?category=eq.common Upsert Translation POST /translations Content-Type: application/json Prefer: resolution=merge-duplicates { "key": "common.save", "croatian": "Spremi", "english": "Save", "russian": "Сохранить", "hindi": "सहेजें", "category": "common" } Holiday Schemes List Schemes GET /holiday_schemes?is_active=eq.true&order=year.desc Get Scheme with Holidays GET /holiday_schemes?id=eq.<uuid>&select=*,holidays(*) RPC Functions Call database functions via RPC endpoint. Get User Role POST /rpc/get_user_role Content-Type: application/json { "_user_id": "<user-uuid>" } Response: "admin" Check If Team Leader POST /rpc/is_team_leader Content-Type: application/json { "_user_id": "<user-uuid>" } Response: true Get Team Leadership POST /rpc/get_team_leadership Content-Type: application/json { "_user_id": "<user-uuid>" } Response: [ { "team_id": "uuid-here", "team_name": "Engineering" } ] Filtering Reference PostgREST filtering operators: | Operator | Description | Example | |----------|-------------|---------| | eq | Equals | status=eq.active | | neq | Not equals | status=neq.cancelled | | gt | Greater than | working_days=gt.5 | | gte | Greater or equal | start_date=gte.2025-01-01 | | lt | Less than | working_days=lt.10 | | lte | Less or equal | end_date=lte.2025-12-31 | | like | Pattern match | full_name=like.*Smith* | | ilike | Case-insensitive like | email=ilike.*@company.com | | in | In list | status=in.(pending,approved) | | is | Is null/true/false | end_date=is.null | Pagination Use Range header for pagination: GET /profiles Range: 0-9 Returns items 0-9 (first 10). Response includes: Content-Range: 0-9/100 Error Responses 401 Unauthorized { "message": "JWT expired", "code": "PGRST301" } 403 Forbidden (RLS) { "message": "new row violates row-level security policy", "code": "42501" } 404 Not Found { "message": "The result contains 0 rows", "code": "PGRST116" } TypeScript Client Using the Supabase client is recommended over raw REST calls: import { supabase } from '@/integrations/supabase/client'; // List profiles const { data, error } = await supabase .from('profiles') .select('*, teams(name)') .eq('status', 'active') .order('full_name'); // Create leave request const { data, error } = await supabase .from('leave_requests') .insert({ user_id: userId, leave_type_id: typeId, start_date: '2025-02-01', end_date: '2025-02-05', working_days: 5 }) .select() .single(); // Call RPC function const { data: role } = await supabase .rpc('get_user_role', { _user_id: userId });

Last updated on Jun 12, 2026

Database Schema

Complete reference for all database tables in CHRIS. Entity Relationship Diagram erDiagram profiles ||--o{ leave_requests : submits profiles ||--o{ contracts : has profiles }o--|| teams : belongs_to profiles }o--|| holiday_schemes : uses teams ||--o{ profiles : contains leave_requests }o--|| leave_types : has_type leave_requests ||--o{ leave_request_attachments : has contracts }o--|| contract_types : has_type contracts }o--|| currencies : uses contracts }o--|| holiday_schemes : uses contracts ||--o{ contract_attachments : has holiday_schemes ||--o{ holidays : contains user_roles }o--|| profiles : assigns_to Core Tables profiles Employee profiles linked to Supabase Auth users. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key (matches auth.users.id) | | full_name | text | No | Employee's full name | | email | text | Yes | Email address | | status | text | Yes | active, inactive | | team_id | uuid | Yes | FK to teams | | holiday_scheme_id | uuid | Yes | FK to holiday_schemes | | manager_user_id | uuid | Yes | Direct manager | | annual_entitlement_days | integer | Yes | Leave days per year | | carryover_days | integer | Yes | Days carried from last year | | employment_start_date | date | Yes | Start of employment | | contract_type | text | Yes | Employment type | | avatar_url | text | Yes | Profile picture URL | | cv_file_path | text | Yes | Path to uploaded CV | | cv_file_name | text | Yes | Original CV filename | | cv_uploaded_at | timestamp | Yes | CV upload timestamp | | emergency_contact | jsonb | Yes | Emergency contact details | | hr_notes | text | Yes | Notes visible to HR only | | salary_band | text | Yes | Salary band classification | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | Relationships: - team_id → teams.id - holiday_scheme_id → holiday_schemes.id teams Team structure with leaders. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | name | text | No | Team name | | lead_user_id | uuid | Yes | FK to profiles (team leader) | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | leave_requests Employee leave requests. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | user_id | uuid | No | FK to profiles (requester) | | leave_type_id | uuid | No | FK to leave_types | | start_date | date | No | First day of leave | | end_date | date | No | Last day of leave | | working_days | integer | No | Calculated working days | | status | leave_status | Yes | pending, approved, rejected, cancelled | | reason | text | Yes | Employee's reason | | approver_user_id | uuid | Yes | Who approved/rejected | | approver_comment | text | Yes | Approver's comment | | approved_at | timestamp | Yes | Decision timestamp | | entitled_days | integer | Yes | Days entitled at request time | | adds_entitlement | boolean | Yes | If request adds to entitlement | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | Relationships: - user_id → profiles.id - leave_type_id → leave_types.id leave_types Types of leave available. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | code | text | No | Unique code (annual_leave, sick_short) | | name_hr | text | No | Croatian name | | name_en | text | No | English name | | is_active | boolean | Yes | If type is available | | max_days_per_year | integer | Yes | Maximum allowed per year | | requires_documentation | boolean | Yes | If documents needed | | allows_attachments | boolean | Yes | If attachments allowed | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | contracts Employment contracts. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | employee_id | uuid | No | FK to profiles | | contract_type_id | uuid | No | FK to contract_types | | currency_id | uuid | No | FK to currencies | | holiday_scheme_id | uuid | Yes | FK to holiday_schemes | | position | text | Yes | Job title | | start_date | date | No | Contract start | | end_date | date | Yes | Contract end (null = ongoing) | | rate_type | text | No | hourly, monthly, yearly | | rate_amount | numeric | No | Salary/rate value | | yearly_leave_days | integer | No | Annual leave entitlement | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | Relationships: - employee_id → profiles.id - contract_type_id → contract_types.id - currency_id → currencies.id - holiday_scheme_id → holiday_schemes.id Supporting Tables holiday_schemes Holiday scheme definitions. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | name | text | No | Scheme name | | country | text | No | Country code | | year | integer | No | Calendar year | | description | text | Yes | Optional description | | is_active | boolean | No | If scheme is active | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | holidays Individual public holidays. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | holiday_scheme_id | uuid | Yes | FK to holiday_schemes | | date | date | No | Holiday date | | name_hr | text | No | Croatian name | | name_en | text | No | English name | | country | text | Yes | Country code | | year | integer | No | Year | | is_public | boolean | Yes | If public holiday | | created_at | timestamp | No | Creation timestamp | translations Internationalization key-value pairs. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | key | text | No | Unique translation key | | croatian | text | No | Croatian translation | | english | text | No | English translation | | russian | text | No | Russian translation | | hindi | text | No | Hindi translation | | category | text | Yes | Category for organization | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | user_roles Role assignments for users. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | user_id | uuid | No | FK to auth.users | | role | app_role | No | admin, hr_manager, employee | | created_at | timestamp | No | Creation timestamp | audit_logs Change tracking for compliance. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | actor_user_id | uuid | No | Who made the change | | entity_type | text | No | Table name | | entity_id | uuid | No | Record ID | | action | text | No | create, update, delete | | old_values | jsonb | Yes | Previous state | | new_values | jsonb | Yes | New state | | created_at | timestamp | No | Change timestamp | company_settings Global application settings. | Column | Type | Nullable | Description | |--------|------|----------|-------------| | id | uuid | No | Primary key | | company_name | text | No | Organization name | | country | text | No | Country code | | time_zone | text | No | Timezone | | fiscal_year_start | text | No | Fiscal year start month | | working_days_per_week | integer | No | Work days (usually 5) | | working_hours_per_day | integer | No | Work hours (usually 8) | | leave_policy | text | Yes | Leave policy document | | smtp_enabled | boolean | Yes | Email enabled | | smtp_host | text | Yes | SMTP server | | smtp_port | integer | Yes | SMTP port | | smtp_username | text | Yes | SMTP user | | smtp_password | text | Yes | SMTP password (encrypted) | | smtp_from_email | text | Yes | Sender email | | smtp_from_name | text | Yes | Sender name | | smtp_use_tls | boolean | Yes | Use TLS | | email_reroute_enabled | boolean | Yes | Reroute all emails | | email_reroute_addresses | text | Yes | Reroute destination | | created_at | timestamp | No | Creation timestamp | | updated_at | timestamp | No | Last update timestamp | Enums app_role User roles in the system. | Value | Description | |-------|-------------| | admin | Full system access | | hr_manager | HR operations, all employees | | employee | Own profile and requests | leave_status Leave request workflow states. | Value | Description | |-------|-------------| | pending | Awaiting approval | | approved | Request approved | | rejected | Request denied | | cancelled | Cancelled by employee | attachment_visibility Who can view attachments. | Value | Description | |-------|-------------| | employee | Visible to employee | | hr_only | Only HR and admin | Database Functions get_user_role(_user_id uuid) Returns the role for a given user. SELECT get_user_role('user-uuid-here'); -- Returns: 'admin', 'hr_manager', or 'employee' has_role(_user_id uuid, _role app_role) Checks if user has a specific role. SELECT has_role('user-uuid', 'admin'); -- Returns: true or false is_team_leader(_user_id uuid) Checks if user leads any team. SELECT is_team_leader('user-uuid'); -- Returns: true or false get_team_leadership(_user_id uuid) Returns teams led by user. SELECT * FROM get_team_leadership('user-uuid'); -- Returns: team_id, team_name

Last updated on Jun 12, 2026

Edge Functions

Supabase Edge Functions handle server-side logic in CHRIS. Overview Edge Functions are located in supabase/functions/ and run on Deno Deploy. | Function | Purpose | |----------|---------| | create-employee | Create new employee with auth user | | send-notification-email | Send emails via SMTP | | reset-employee-password | Password reset functionality | create-employee Creates a new employee by setting up both the auth user and profile. Endpoint POST /functions/v1/create-employee Headers Authorization: Bearer <ADMIN_JWT_TOKEN> Content-Type: application/json Request Body { "email": "john.doe@company.com", "full_name": "John Doe", "role": "employee", "team_id": "optional-team-uuid", "employment_start_date": "2025-02-01", "annual_entitlement_days": 20 } Parameters | Field | Type | Required | Description | |-------|------|----------|-------------| | email | string | Yes | Unique email address | | full_name | string | Yes | Employee's full name | | role | string | Yes | admin, hr_manager, employee | | team_id | uuid | No | Team assignment | | employment_start_date | date | No | Start date | | annual_entitlement_days | integer | No | Leave days (default: 20) | Response Success (201): { "success": true, "user_id": "new-user-uuid", "message": "Employee created successfully" } Error (400): { "success": false, "error": "Email already exists" } Behavior 1. Validates admin JWT token 2. Creates user in Supabase Auth 3. Creates profile record 4. Assigns role in user_roles 5. Sends welcome email (if SMTP configured) send-notification-email Sends emails using SMTP settings from company_settings. Endpoint POST /functions/v1/send-notification-email Headers Authorization: Bearer <JWT_TOKEN> Content-Type: application/json Request Body { "to": ["recipient@company.com"], "subject": "Leave Request Approved", "html": "<h1>Your leave has been approved</h1>", "text": "Your leave has been approved" } Parameters | Field | Type | Required | Description | |-------|------|----------|-------------| | to | string[] | Yes | Recipient email addresses | | subject | string | Yes | Email subject line | | html | string | Yes | HTML email body | | text | string | No | Plain text fallback | Response Success (200): { "success": true, "message": "Email sent successfully" } Error (500): { "success": false, "error": "SMTP connection failed" } Behavior 1. Validates JWT token 2. Retrieves SMTP settings from company_settings 3. Checks if email rerouting is enabled 4. Sends email via configured SMTP server 5. Returns success/failure status Email Rerouting When email_reroute_enabled is true in company_settings: - All emails go to email_reroute_addresses instead - Original recipients are ignored - Useful for testing reset-employee-password Initiates password reset for an employee. Endpoint POST /functions/v1/reset-employee-password Headers Authorization: Bearer <ADMIN_JWT_TOKEN> Content-Type: application/json Request Body { "user_id": "employee-user-uuid" } Parameters | Field | Type | Required | Description | |-------|------|----------|-------------| | user_id | uuid | Yes | Employee's user ID | Response Success (200): { "success": true, "message": "Password reset email sent" } Error (403): { "success": false, "error": "Admin access required" } Behavior 1. Validates admin JWT token 2. Looks up employee email 3. Generates password reset link 4. Sends reset email to employee Calling from Frontend Use the Supabase client to invoke Edge Functions: import { supabase } from '@/integrations/supabase/client'; // Create employee const { data, error } = await supabase.functions.invoke('create-employee', { body: { email: 'new.employee@company.com', full_name: 'New Employee', role: 'employee' } }); // Send notification const { data, error } = await supabase.functions.invoke('send-notification-email', { body: { to: ['manager@company.com'], subject: 'New Leave Request', html: '<p>A new leave request requires your approval.</p>' } }); // Reset password const { data, error } = await supabase.functions.invoke('reset-employee-password', { body: { user_id: 'employee-uuid' } }); Development Local Development # Start Supabase locally npx supabase start # Serve functions locally npx supabase functions serve Deploy Functions # Deploy all functions npx supabase functions deploy # Deploy specific function npx supabase functions deploy send-notification-email View Logs In Supabase Dashboard: 1. Go to Edge Functions 2. Select the function 3. Click Logs tab Or via CLI: npx supabase functions logs send-notification-email Environment Variables Edge Functions can access these secrets: | Variable | Description | |----------|-------------| | SUPABASE_URL | Project URL | | SUPABASE_ANON_KEY | Anonymous key | | SUPABASE_SERVICE_ROLE_KEY | Service role key | Set secrets via dashboard or CLI: npx supabase secrets set MY_SECRET=value Error Handling All functions follow consistent error responses: { "success": false, "error": "Error message here", "details": "Optional additional details" } HTTP status codes: | Code | Meaning | |------|---------| | 200 | Success | | 201 | Created | | 400 | Bad request (invalid input) | | 401 | Unauthorized (invalid token) | | 403 | Forbidden (insufficient permissions) | | 500 | Server error |

Last updated on Jun 12, 2026

Environment Variables

Complete reference for all environment variables in CHRIS. Required Variables These variables must be set for the application to function. VITE_SUPABASE_URL The URL of your Supabase project. VITE_SUPABASE_URL=https://[PROJECT_REF].supabase.co Where to find: 1. Go to Supabase Dashboard 2. Select your project 3. Navigate to Settings > API 4. Copy the Project URL VITE_SUPABASE_ANON_KEY The anonymous/public API key for Supabase. VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Where to find: 1. Go to Supabase Dashboard 2. Select your project 3. Navigate to Settings > API 4. Copy the anon public key !!! warning "Security Note" The anon key is safe to expose in frontend code. It only allows operations permitted by Row Level Security (RLS) policies. Optional Variables NODE_ENV The environment mode. NODE_ENV=development # or 'production' Values: - development - Enables source maps, verbose logging - production - Optimized builds, minimal logging Edge Function Variables These are set in Supabase, not in .env. SUPABASE_URL Automatically available in Edge Functions. SUPABASE_ANON_KEY Automatically available in Edge Functions. SUPABASE_SERVICE_ROLE_KEY Automatically available for bypassing RLS. !!! danger "Never Expose Service Role Key" The service role key bypasses all security. Never expose it to the frontend. Setting Up Environment Local Development 1. Copy the example file: cp .env.example .env 2. Edit .env with your values: VITE_SUPABASE_URL=https://your-project.supabase.co VITE_SUPABASE_ANON_KEY=your-anon-key 3. Restart the development server: npm run dev Production Deployment Set environment variables in your hosting platform: Vercel: 1. Go to Project Settings > Environment Variables 2. Add each variable Netlify: 1. Go to Site Settings > Build & deploy > Environment 2. Add each variable Docker: ENV VITE_SUPABASE_URL=https://your-project.supabase.co ENV VITE_SUPABASE_ANON_KEY=your-anon-key Variable Naming Vite Prefix Variables prefixed with VITE_ are exposed to the frontend: VITE_PUBLIC_VAR=accessible # Accessible in code SECRET_VAR=not-accessible # Not accessible in frontend Access in code: const url = import.meta.env.VITE_SUPABASE_URL; TypeScript Types Vite provides type checking for environment variables: // src/vite-env.d.ts interface ImportMetaEnv { readonly VITE_SUPABASE_URL: string; readonly VITE_SUPABASE_ANON_KEY: string; } Database Settings Some configuration is stored in the database rather than environment variables: company_settings Table | Setting | Description | |---------|-------------| | smtp_host | SMTP server address | | smtp_port | SMTP port | | smtp_username | SMTP authentication | | smtp_password | SMTP password | | smtp_from_email | Sender email | | smtp_from_name | Sender display name | | smtp_use_tls | Enable TLS | | email_reroute_enabled | Reroute all emails | | email_reroute_addresses | Reroute destination | These are configured via the Settings UI rather than environment variables, allowing changes without redeployment. Troubleshooting Variables Not Loading 1. Ensure variable names start with VITE_ 2. Restart the development server after changes 3. Check for typos in .env Production Build Missing Variables Ensure variables are set in your hosting platform before building: # Variables must exist at build time VITE_SUPABASE_URL=https://... npm run build Environment-Specific Values Use different .env files for different environments: .env # Default (development) .env.local # Local overrides (gitignored) .env.production # Production values Vite loads them in order, with later files overriding earlier ones.

Last updated on Jun 12, 2026

Leave Statuses

Reference for leave request workflow states in CHRIS. Status Values | Status | Description | |--------|-------------| | pending | Request submitted, awaiting approval | | approved | Request approved by manager/HR | | rejected | Request denied by manager/HR | | cancelled | Request cancelled by employee | Workflow Diagram stateDiagram-v2 [*] --> pending: Submit Request pending --> approved: Approve pending --> rejected: Reject pending --> cancelled: Cancel approved --> cancelled: Revoke (HR only) rejected --> [*] cancelled --> [*] approved --> [*] State Transitions pending → approved Trigger: Manager or HR approves the request Who can do this: - Team leader (for team members) - HR Manager (for any employee) - Admin (for any employee) Required fields: - approver_user_id - Who approved - approved_at - Timestamp of approval - approver_comment - Optional comment Effects: - Working days deducted from balance - Employee notified via email - Request appears on team calendar pending → rejected Trigger: Manager or HR rejects the request Who can do this: - Team leader (for team members) - HR Manager (for any employee) - Admin (for any employee) Required fields: - approver_user_id - Who rejected - approved_at - Timestamp of rejection - approver_comment - Required (reason for rejection) Effects: - No balance change - Employee notified via email with reason pending → cancelled Trigger: Employee cancels their own request Who can do this: - The employee who submitted the request - HR Manager - Admin Effects: - Request removed from pending queue - No balance change (request was never approved) approved → cancelled (Revoke) Trigger: HR or Admin revokes an approved request Who can do this: - HR Manager - Admin Required fields: - approver_comment - Reason for revocation Effects: - Working days restored to balance - Employee notified of revocation - Request removed from calendar !!! warning "Use Sparingly" Revoking approved leave should be rare and well-justified. Status Colors | Status | Color | Hex | |--------|-------|-----| | pending | Yellow | #FFC107 | | approved | Green | #4CAF50 | | rejected | Red | #F44336 | | cancelled | Gray | #9E9E9E | Database Enum CREATE TYPE leave_status AS ENUM ( 'pending', 'approved', 'rejected', 'cancelled' ); Querying by Status Get Pending Requests const { data } = await supabase .from('leave_requests') .select('*') .eq('status', 'pending') .order('created_at', { ascending: true }); Get Approved Requests for Date Range const { data } = await supabase .from('leave_requests') .select('*') .eq('status', 'approved') .gte('start_date', '2025-01-01') .lte('end_date', '2025-12-31'); Count by Status const { count } = await supabase .from('leave_requests') .select('*', { count: 'exact', head: true }) .eq('status', 'pending') .eq('user_id', userId); Updating Status Approve Request const { error } = await supabase .from('leave_requests') .update({ status: 'approved', approver_user_id: approverId, approver_comment: 'Approved. Enjoy your time off!', approved_at: new Date().toISOString() }) .eq('id', requestId); Reject Request const { error } = await supabase .from('leave_requests') .update({ status: 'rejected', approver_user_id: approverId, approver_comment: 'Cannot approve due to project deadline.', approved_at: new Date().toISOString() }) .eq('id', requestId); Cancel Request const { error } = await supabase .from('leave_requests') .update({ status: 'cancelled' }) .eq('id', requestId) .eq('status', 'pending'); // Only cancel if still pending UI Display Status Badge Component function StatusBadge({ status }: { status: string }) { const colors = { pending: 'bg-yellow-100 text-yellow-800', approved: 'bg-green-100 text-green-800', rejected: 'bg-red-100 text-red-800', cancelled: 'bg-gray-100 text-gray-800', }; return ( <span className={`px-2 py-1 rounded ${colors[status]}`}> {t(`leaveRequests.status.${status}`)} </span> ); } Translated Status Names | Status | Croatian | English | Russian | Hindi | |--------|----------|---------|---------|-------| | pending | Na čekanju | Pending | Ожидает | लंबित | | approved | Odobreno | Approved | Одобрено | स्वीकृत | | rejected | Odbijeno | Rejected | Отклонено | अस्वीकृत | | cancelled | Otkazano | Cancelled | Отменено | रद्द | Business Rules Pending Requests - Can only be modified by the submitter - Auto-expire if not acted upon within X days (configurable) - Show warning if balance insufficient Approved Requests - Cannot be edited - Can only be revoked by HR/Admin - Contribute to leave calendar Rejected Requests - Employee can submit a new request - Keep historical record for auditing Cancelled Requests - Soft delete - data preserved - Can be filtered out from normal views - Available in audit logs

Last updated on Jun 12, 2026

Roles & Permissions

Complete reference for role-based access control (RBAC) in CHRIS. Role Hierarchy admin └── hr_manager └── employee Higher roles inherit all permissions from lower roles. Roles Overview admin Full system access. Can configure all settings and manage all data. Typical users: System administrators, IT staff hr_manager HR operations access. Can manage employees and approve leave. Typical users: HR personnel, department heads employee Basic access. Can manage own profile and submit requests. Typical users: All regular employees Permission Matrix Profiles (Employee Data) | Action | admin | hr_manager | employee | |--------|:-----:|:----------:|:--------:| | View own profile | ✓ | ✓ | ✓ | | Edit own profile | ✓ | ✓ | ✓ | | View all profiles | ✓ | ✓ | - | | Edit any profile | ✓ | ✓ | - | | Create employee | ✓ | ✓ | - | | Delete employee | ✓ | - | - | | View HR notes | ✓ | ✓ | - | | Edit HR notes | ✓ | ✓ | - | | Masquerade as user | ✓ | - | - | Leave Requests | Action | admin | hr_manager | employee | team_leader | |--------|:-----:|:----------:|:--------:|:-----------:| | View own requests | ✓ | ✓ | ✓ | ✓ | | Submit request | ✓ | ✓ | ✓ | ✓ | | Cancel own pending | ✓ | ✓ | ✓ | ✓ | | View all requests | ✓ | ✓ | - | - | | View team requests | ✓ | ✓ | - | ✓ | | Approve/reject any | ✓ | ✓ | - | - | | Approve/reject team | ✓ | ✓ | - | ✓ | | Revoke approved | ✓ | ✓ | - | - | Teams | Action | admin | hr_manager | employee | |--------|:-----:|:----------:|:--------:| | View all teams | ✓ | ✓ | ✓ | | Create team | ✓ | ✓ | - | | Edit team | ✓ | ✓ | - | | Delete team | ✓ | - | - | | Assign members | ✓ | ✓ | - | Settings | Action | admin | hr_manager | employee | |--------|:-----:|:----------:|:--------:| | View company settings | ✓ | - | - | | Edit company settings | ✓ | - | - | | Configure SMTP | ✓ | - | - | | Manage leave types | ✓ | - | - | | Manage holidays | ✓ | ✓ | - | | View audit logs | ✓ | ✓ | - | | Manage translations | ✓ | - | - | Contracts | Action | admin | hr_manager | employee | |--------|:-----:|:----------:|:--------:| | View own contract | ✓ | ✓ | ✓ | | View any contract | ✓ | ✓ | - | | Create contract | ✓ | ✓ | - | | Edit contract | ✓ | ✓ | - | | Delete contract | ✓ | - | - | Team Leader Role Team leaders are employees who lead one or more teams. This is not a separate role but additional permissions. Identifying Team Leaders // Check if user is a team leader const { data: isLeader } = await supabase .rpc('is_team_leader', { _user_id: userId }); Team Leader Permissions In addition to employee permissions: - View leave requests from team members - Approve/reject team member requests - View team calendar and availability Assignment Team leaders are assigned via the teams.lead_user_id field: UPDATE teams SET lead_user_id = 'user-uuid' WHERE id = 'team-uuid'; Row Level Security (RLS) Permissions are enforced at the database level via RLS policies. Example Policies Profiles - View own: CREATE POLICY "Users can view own profile" ON profiles FOR SELECT USING (auth.uid() = id); Profiles - HR can view all: CREATE POLICY "HR can view all profiles" ON profiles FOR SELECT USING ( EXISTS ( SELECT 1 FROM user_roles WHERE user_id = auth.uid() AND role IN ('admin', 'hr_manager') ) ); Leave requests - Team leader view: CREATE POLICY "Team leaders can view team requests" ON leave_requests FOR SELECT USING ( user_id IN ( SELECT id FROM profiles WHERE team_id IN ( SELECT id FROM teams WHERE lead_user_id = auth.uid() ) ) ); Checking Permissions in Code Using the Auth Hook import { useAuth } from '@/hooks/useAuth'; function MyComponent() { const { isAdmin, isHR, isEmployee, user } = useAuth(); if (isAdmin) { // Show admin UI } else if (isHR) { // Show HR UI } else { // Show employee UI } } Using RPC Functions // Get user's role const { data: role } = await supabase .rpc('get_user_role', { _user_id: userId }); // Check specific role const { data: isAdmin } = await supabase .rpc('has_role', { _user_id: userId, _role: 'admin' }); Role Assignment During Employee Creation Roles are assigned when creating an employee: await supabase.functions.invoke('create-employee', { body: { email: 'user@company.com', full_name: 'John Doe', role: 'hr_manager' // admin, hr_manager, or employee } }); Changing Roles Only admins can change roles: UPDATE user_roles SET role = 'hr_manager' WHERE user_id = 'user-uuid'; Best Practices Principle of Least Privilege Assign the minimum role needed: - Most users should be employee - Only HR staff need hr_manager - Limit admin to essential personnel Role Auditing Track role changes via audit logs: SELECT * FROM audit_logs WHERE entity_type = 'user_roles' ORDER BY created_at DESC; Testing Permissions Use masquerade to test as different roles: 1. Sign in as admin 2. Masquerade as an employee 3. Verify they see appropriate data 4. Exit masquerade

Last updated on Jun 12, 2026