Skip to main content

Overview

The incident management interface provides administrators with comprehensive tools to view, filter, assign, and resolve incidents across the property. Built as a client-side interactive page, it offers real-time data management.

Accessing Incident Management

Navigate to the incidents page from the dashboard sidebar:
/dashboard/incidents
This route is protected by authentication middleware and requires admin privileges.

Features

View All Incidents

Comprehensive table with all incident details

Filter & Search

Find specific incidents quickly

Assign Staff

Assign incidents to appropriate employees

Update Status

Change incident status and priority

Incident Table

The main interface displays incidents in an interactive data table:

Columns

ColumnDescriptionActions
TitleIncident descriptionClick to view details
StatusCurrent workflow statusUpdate via edit sheet
PriorityUrgency level (baja/media/alta)Color-coded badge
AreaDepartment/categoryFilter by area
RoomRoom codeLink to room details
Assigned ToStaff memberReassign via dropdown
CreatedTimestampSort chronologically
ActionsMenuEdit, delete, resolve

Implementation

The incidents page is a client component that fetches data on mount:
app/(dashboard)/dashboard/incidents/page.tsx
'use client'

import * as React from "react";
import { supabase } from "@/lib/supabase";
import { DataTable } from "@/components/data-table";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent } from "@/components/ui/sheet";

export default function IncidentsCRUDPage() {
    const [incidents, setIncidents] = React.useState<any[]>([]);
    const [loading, setLoading] = React.useState(true);

    const fetchIncidents = React.useCallback(async () => {
        setLoading(true);
        const { data, error } = await supabase
            .from("incidents")
            .select(`
                *,
                area:areas(id, name),
                room:rooms(id, room_code),
                assignee:profiles!incidents_assigned_to_fkey(id, full_name, email)
            `)
            .order("created_at", { ascending: false });

        if (data) setIncidents(data);
        setLoading(false);
    }, []);

    React.useEffect(() => {
        fetchIncidents();
    }, [fetchIncidents]);

    return (
        <div className="p-6">
            <div className="mb-6 flex items-center justify-between">
                <h1 className="text-3xl font-bold">Incidents</h1>
                <Button onClick={() => setCreateOpen(true)}>
                    New Incident
                </Button>
            </div>
            <DataTable data={incidents} loading={loading} />
        </div>
    );
}

Status Management

Incidents follow a defined workflow:
1

Pendiente (Pending)

Initial state when guest or staff creates an incidentColor: Yellow badge
Actions: Assign to staff, update priority
2

Recibida (Received)

Staff member has viewed the incidentColor: Blue badge
Actions: Accept and move to in progress
3

En Progreso (In Progress)

Staff is actively working on the incidentColor: Orange badge
Actions: Add notes, update progress, resolve
4

Resuelta (Resolved)

Incident has been completedColor: Green badge
Actions: Reopen if needed

Updating Status

Admins can manually change incident status:
async function updateIncidentStatus(incidentId: string, newStatus: string) {
  const { error } = await supabase
    .from('incidents')
    .update({ 
      status: newStatus,
      updated_at: new Date().toISOString()
    })
    .eq('id', incidentId);

  if (error) {
    console.error('Error updating status:', error);
    return false;
  }

  // Refresh incident list
  fetchIncidents();
  return true;
}

Priority Levels

Three priority levels determine urgency:

Baja (Low)

  • Color: Green
  • Use Case: Minor issues, non-urgent requests
  • Examples: Extra towels, light bulb replacement
  • SLA: 24 hours

Media (Medium)

  • Color: Yellow/Orange
  • Use Case: Standard maintenance issues
  • Examples: TV not working, slow drain
  • SLA: 4-6 hours

Alta (High)

  • Color: Red
  • Use Case: Urgent issues affecting guest comfort
  • Examples: No AC, water leak, safety concerns
  • SLA: 1 hour

Updating Priority

async function updateIncidentPriority(incidentId: string, priority: 'baja' | 'media' | 'alta') {
  const { error } = await supabase
    .from('incidents')
    .update({ priority })
    .eq('id', incidentId);

  if (!error) {
    // Optionally notify assigned staff of priority change
    await sendPriorityChangeNotification(incidentId, priority);
  }
}

Assigning Incidents

Admins can assign incidents to specific employees:

Assignment Logic

  1. Area-Based: Incidents are typically assigned to employees in the matching area
  2. Workload Balancing: Consider employee current task count
  3. Skill-Based: Some incidents require specific expertise
  4. Location: Assign to staff closest to the room

Implementation

async function assignIncident(incidentId: string, employeeId: string) {
  const { error } = await supabase
    .from('incidents')
    .update({ 
      assigned_to: employeeId,
      status: 'recibida',
      updated_at: new Date().toISOString()
    })
    .eq('id', incidentId);

  if (!error) {
    // Send push notification to assigned employee
    await sendAssignmentNotification(employeeId, incidentId);
  }
}

Bulk Assignment

Assign multiple incidents to one employee:
async function bulkAssign(incidentIds: string[], employeeId: string) {
  const { error } = await supabase
    .from('incidents')
    .update({ 
      assigned_to: employeeId,
      status: 'recibida'
    })
    .in('id', incidentIds);

  return !error;
}
The dashboard provides multiple ways to filter incidents:

By Status

const { data } = await supabase
  .from('incidents')
  .select('*')
  .eq('status', 'pendiente');

By Priority

const { data } = await supabase
  .from('incidents')
  .select('*')
  .eq('priority', 'alta');

By Area

const { data } = await supabase
  .from('incidents')
  .select('*, area:areas(name)')
  .eq('area_id', areaId);

By Room

const { data } = await supabase
  .from('incidents')
  .select('*, room:rooms(room_code)')
  .eq('room_id', roomId);
const { data } = await supabase
  .from('incidents')
  .select('*')
  .or(`title.ilike.%${searchTerm}%,description.ilike.%${searchTerm}%`);

Edit Incident Sheet

The edit interface appears in a side sheet when clicking on an incident:

Editable Fields

  • Title: Incident summary
  • Description: Detailed description
  • Priority: Low, Medium, High
  • Status: Current workflow status
  • Area: Department category
  • Assigned To: Staff member
  • Room: Location (if applicable)

Sheet Component

import { Sheet, SheetContent, SheetHeader } from "@/components/ui/sheet";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Select } from "@/components/ui/select";

function EditIncidentSheet({ incident, open, onClose }) {
  const [formData, setFormData] = React.useState(incident);

  async function handleSubmit() {
    const { error } = await supabase
      .from('incidents')
      .update(formData)
      .eq('id', incident.id);

    if (!error) {
      onClose();
      fetchIncidents(); // Refresh list
    }
  }

  return (
    <Sheet open={open} onOpenChange={onClose}>
      <SheetContent>
        <SheetHeader>
          <h2>Edit Incident</h2>
        </SheetHeader>
        {/* Form fields */}
      </SheetContent>
    </Sheet>
  );
}

Deleting Incidents

Deleting incidents permanently removes them from the database. This action cannot be undone.
Instead of hard deletion, mark incidents as archived:
async function archiveIncident(incidentId: string) {
  const { error } = await supabase
    .from('incidents')
    .update({ 
      archived: true,
      archived_at: new Date().toISOString()
    })
    .eq('id', incidentId);

  return !error;
}

Hard Delete

Permanently remove an incident:
async function deleteIncident(incidentId: string) {
  const { error } = await supabase
    .from('incidents')
    .delete()
    .eq('id', incidentId);

  return !error;
}

Bulk Operations

Perform actions on multiple incidents:

Bulk Status Update

async function bulkUpdateStatus(incidentIds: string[], newStatus: string) {
  const { error } = await supabase
    .from('incidents')
    .update({ status: newStatus })
    .in('id', incidentIds);

  return !error;
}

Bulk Delete

async function bulkDelete(incidentIds: string[]) {
  const { error } = await supabase
    .from('incidents')
    .delete()
    .in('id', incidentIds);

  return !error;
}

Export Incidents

Export incident data for reporting:
function exportIncidentsToCSV(incidents: any[]) {
  const headers = ['Title', 'Status', 'Priority', 'Area', 'Room', 'Assigned To', 'Created'];
  const rows = incidents.map(inc => [
    inc.title,
    inc.status,
    inc.priority,
    inc.area?.name || '',
    inc.room?.room_code || '',
    inc.assignee?.full_name || '',
    new Date(inc.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 = `incidents-${new Date().toISOString()}.csv`;
  link.click();
}

Next Steps

User Management

Manage employee accounts and permissions

Analytics

View incident metrics and trends

Database Schema

Learn about the incidents table structure

Mobile App

See how staff interact with incidents on mobile

Build docs developers (and LLMs) love