Facilities Manager
Facilities Manager
Last Updated: April 15, 2026 Status: Phase 1 (MVP)
Overview
Manage church facilities including room scheduling, interactive floor plans, and work order tracking. This tool provides a comprehensive solution for facility management with visual floor plan overlays, calendar-based room booking, and maintenance request workflows.
File: src/features/facilities/components/FacilitiesManager.tsx DB Module: src/lib/db/facilities.ts Route / navigation: Path /tools/facilities-manager, Zustand toolsTool = facilities-manager. See 00-TOOLS-HUB.md. Desktop only: The tool renders a DesktopOnlyWarning on viewports narrower than md — editing hotspots, dragging work orders, and the floor-plan canvas all require a larger screen.
Shell layout (Styling Guide compliant)
Breadcrumb→PageHeader(centered, defaulttext-foreground) → 4-up stat cards withstagger-fade-in-blur→ sharedPillTabs(Rooms / Floor Plan / Work Orders).- Stat tiles use
STAT_AMOUNTfrom@/lib/ui-constantsand theuseCountUphook, per §29 of the Styling Guide. - Internal tabs are local state only — they do not participate in
toolsToolURL sync for Phase 1.
Features
Room Scheduling (rooms tab)
- Space Catalog — CRUD for spaces with name, building, floor, capacity, amenities, and active flag (read-only users see details but no Save/Delete buttons).
- Calendar View —
ResponsiveCalendarwith status-colored events (pending=warning, approved=success, rejected=destructive, cancelled=muted). - Reservation Sheet — Create/edit reservations in a
Sheetdrawer with datetime inputs, notes, and a Director-gated status selector. - Status & Space Filters — Filter by reservation status and/or by space.
- Conflict Prevention — Enforced at the DB via a GIST exclusion constraint on overlapping pending/approved rows.
Floor Plan Viewer (floor-plan tab)
- Upload pipeline — Files are compressed to 2048px max width at 0.85 quality via
compressImageand stored in the public `facility-media` bucket under{organization_id}/floor-plans/{timestamp}_{rand}.jpg. See STORAGE bucket migration. - Interactive Maps — Pan/zoom via
react-zoom-pan-pinchwith reset, zoom-in, and zoom-out controls pinned top-right. - SVG Hotspots — Click-to-interact room polygons overlaid on the floor-plan image. Polygons use percentage (0-100) coordinates in a
viewBox="0 0 100 100"SVG, so hotspots scale with the image. - Space-status coloring — Active spaces render in
--successtones; inactive/unknown fall back to muted tokens. - Hotspot Editor — Directors + canWrite only. Clicking the plan appends points; ≥3 points are required before assigning the shape to a space. Polygons are saved in one batch via
saveFloorPlanHotspots.
Work Orders (work-orders tab)
- Kanban View (default) — Four columns (Open, In Progress, On Hold, Completed) with drag-and-drop via
@dnd-kit/core. Dropping a card onto another column mutatesstatusthroughupdateWorkOrder. - List View — Table with status/priority/due columns. Row actions (Edit, Delete) appear only for users with
canWrite. - Priority & Status Badges — Tailwind semantic tokens (
bg-destructive,bg-warning/10,bg-primary/10,bg-muted). - Category / Priority / Space / Status filters plus client-side search by title/description.
- Comments Thread — Each work order drawer includes an inline comments
ScrollAreawith an Add-Comment input. - Current limits (Phase 1):
- The list view fetches
pageSize: 500fromget_facility_work_orders_pageand does not yet render visible pagination controls. - There is no assignee picker UI yet — the Sheet has no member-directory selector, so
assigned_tois preserved from the existing record on edit but cannot be set/changed from the UI. Assignee assignment is a Phase 2 follow-up.
Database
facility_spaces table
| Column | Type | Notes |
|--------|------|-------|
| id | UUID | PK |
| organization_id | UUID | FK → organizations |
| name | TEXT | Required |
| building | TEXT | Optional |
| floor | TEXT | Optional |
| capacity | INTEGER | Optional |
| amenities | JSONB | Array of strings |
| hourly_rate | NUMERIC(10,2) | Default 0 |
| daily_rate | NUMERIC(10,2) | Default 0 |
| photos | TEXT[] | Array of URLs |
| is_active | BOOLEAN | Default true |
| created_at | TIMESTAMPTZ | Auto |
| updated_at | TIMESTAMPTZ | Auto |
facility_floor_plans table
| Column | Type | Notes |
|--------|------|-------|
| id | UUID | PK |
| organization_id | UUID | FK → organizations |
| name | TEXT | Required |
| image_url | TEXT | Public URL to uploaded image |
| building | TEXT | Optional |
| floor | TEXT | Optional |
| created_at | TIMESTAMPTZ | Auto |
facility_floor_plan_hotspots table
| Column | Type | Notes |
|--------|------|-------|
| id | UUID | PK |
| floor_plan_id | UUID | FK → facility_floor_plans (cascade) |
| space_id | UUID | FK → facility_spaces (cascade) |
| coordinates | JSONB | Array of {x, y} points (percentages 0-100) || created_at | TIMESTAMPTZ | Auto |
facility_reservations table
| Column | Type | Notes |
|--------|------|-------|
| id | UUID | PK |
| organization_id | UUID | FK → organizations |
| space_id | UUID | FK → facility_spaces |
| event_id | UUID | FK → events (optional) |
| title | TEXT | Required |
| requested_by | UUID | FK → auth.users |
| approved_by | UUID | FK → auth.users (optional) |
| status | TEXT | pending, approved, rejected, cancelled |
| start_at | TIMESTAMPTZ | Required |
| end_at | TIMESTAMPTZ | Required, must be after start_at |
| recurrence_rule | TEXT | Optional RRULE string |
| notes | TEXT | Optional |
| rental_fee | NUMERIC(10,2) | Default 0 |
| created_at | TIMESTAMPTZ | Auto |
| updated_at | TIMESTAMPTZ | Auto |
Constraint: Exclusion constraint prevents overlapping pending/approved reservations for the same space.
facility_work_orders table
| Column | Type | Notes |
|--------|------|-------|
| id | UUID | PK |
| organization_id | UUID | FK → organizations |
| space_id | UUID | FK → facility_spaces (optional) |
| title | TEXT | Required |
| description | TEXT | Optional |
| category | TEXT | plumbing, electrical, hvac, custodial, grounds, technology, other |
| priority | TEXT | urgent, high, medium, low |
| status | TEXT | open, in_progress, on_hold, completed |
| reported_by | UUID | FK → auth.users |
| assigned_to | UUID | FK → auth.users (optional) |
| due_date | DATE | Optional |
| completed_at | TIMESTAMPTZ | Auto-set when status becomes completed |
| photos | TEXT[] | Array of URLs |
| cost | NUMERIC(10,2) | Optional |
| fund_id | UUID | Optional FK for expense tracking |
| created_at | TIMESTAMPTZ | Auto |
| updated_at | TIMESTAMPTZ | Auto |
facility_work_order_comments table
| Column | Type | Notes |
|--------|------|-------|
| id | UUID | PK |
| work_order_id | UUID | FK → facility_work_orders (cascade) |
| user_id | UUID | FK → auth.users |
| content | TEXT | Required |
| created_at | TIMESTAMPTZ | Auto |
Permissions
The UI uses two gates:
canWrite(fromusePermissions()) controls whether users can access mutating UI actions.isDirectoradds an extra gate for reservation status changes and floor-plan editor/upload controls.
| Action | Director + canWrite | Non-director + canWrite | Read-only |
|--------|----------------------|--------------------------|-----------|
| View all tabs | ✓ | ✓ | ✓ |
| Create/edit/delete spaces | ✓ | ✓ | ✗ |
| Create/edit/delete reservations | ✓ | ✓ | ✗ |
| Change reservation `status` (approve/reject) | ✓ | ✗ | ✗ |
| Upload/delete floor plans | ✓ | ✗ | ✗ |
| Draw / save / delete hotspots | ✓ | ✗ | ✗ |
| Create/edit/delete work orders | ✓ | ✓ | ✗ |
| Drag work orders between columns | ✓ | ✓ | ✗ |
| Open/post work-order comments in the edit drawer | ✓ | ✓ | ✗ |
RLS on the underlying tables is the source of truth; the UI gates above prevent users from seeing dead-end controls but do not weaken the database rules.
Tier Requirements
- Minimum Tier: Plus
- Available for all org types (churches, nonprofits)
Related Files
src/features/facilities/components/rooms/RoomScheduler.tsx— Room scheduling calendarsrc/features/facilities/components/floor-plan/FloorPlanViewer.tsx— Interactive floor plansrc/features/facilities/components/work-orders/WorkOrderManager.tsx— Work order kanban/listsrc/lib/db/facilities.ts— Typed Supabase accessorssrc/i18n/locales/en/tools.json—facilities.*namespace. A top-levelcommonblock inside thetoolsnamespace supplies the shared Cancel/Save/Create/Edit/Delete/Upload labels (t('common.cancel')etc. resolves inside thetoolsnamespace).supabase/migrations/20260415120000_create_facilities_tables.sql— Database schema (tables, RLS, exclusion constraint)supabase/migrations/20260415130000_create_facility_media_bucket.sql—facility-mediastorage bucket + RLS (public read, org-scoped write, 10 MB limit,image/jpeg|png|webponly)
Dependencies
react-zoom-pan-pinch— Pan/zoom for floor plan images@dnd-kit/core— Drag-and-drop for work order kanban (already installed)ResponsiveCalendar— Shared calendar component@/lib/imageUtils—compressImage(file, maxWidth, quality)— used to downsize uploads before Supabase Storage
Future Enhancements (Phase 2+)
- Member-directory assignee picker for work orders
- Paginated list view with visible page controls
- Public booking page with Stripe payment
- Preventive maintenance auto-scheduling
- Asset tracking and key management
- Facility inspections checklist
- Heat map utilization overlays
- HVAC/smart lock integrations
Synced from IFMmvp-Frontend documentation: pages/tools/16-FACILITIES-MANAGER.md
Ready to Get Started?
See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.
Questions? Email us at steven@getalignmint.org