Healthcare/2022

Lighthouse: Transforming Organizational Health Management

A comprehensive health management platform designed to help organizations protect their populations, maintain compliance with health protocols, and coordinate health events efficiently for the film and entertainment industry.

Year
2022
Status
Completed
Typology
Web & Mobile App
Client
Daybreak Health

The challenge

Daybreak Health partnered with major entertainment companies across the U.S. film industry. Our research revealed that production companies were allocating significant portions of their budgets to COVID-19 compliance and testing operations. While a California Film Commission study found that major productions spent between 5-6.5% of their budgets on COVID protocols, real-world costs frequently reached 10-30% of total budgets, especially for productions involving travel and quarantine requirements.

The financial stakes were enormous. Films with budgets over $60 million allocated an average of $5.6 million to COVID compliance alone. For smaller productions, the burden was equally significant—a six-week shoot with 23 crew members could spend 12% of the entire budget just on testing.

When outbreaks occurred, the costs multiplied. Production shutdowns meant paying entire crews while idle, then rehiring once filming could resume—costs that could exceed 10% of total production budgets or hundreds of thousands of dollars per incident. For some productions, added precautions and protocols added approximately $120,000 per week to operating costs.

During a conversation with a line producer, we identified a critical gap: there was no centralized system to track testing status, view results for cast and crew, or manage isolation protocols. Without visibility into their population's health status, productions were operating blind—unable to prevent outbreaks or respond quickly when they occurred. This insight became the catalyst for Lighthouse.

Financial impact

Films with budgets over $60 million allocated an average of $5.6 million to COVID compliance alone. For smaller productions, a six-week shoot with 23 crew members could spend 12% of the entire budget just on testing. When outbreaks occurred, production shutdowns meant paying entire crews while idle, then rehiring once filming could resume—costs that could exceed 10% of total production budgets or hundreds of thousands of dollars per incident.

The solution

Lighthouse provided three core capabilities: Clinical Guidance & Support for access to expert-reviewed guidelines and educational materials, Compliance Dashboard for monitoring organization-wide health protocol compliance through intuitive dashboards, and Event Coordination for scheduling and managing health events to provide employees with timely access to necessary care.

User groups

The platform serves four distinct user classes:

Employers & people managers

- Line producers and PA coordinators (TV/Film) - School principals (Education) - COVID compliance officers - HR managers - C-suite executives

Patients/participants

- Union and non-union film/TV personnel (Zone A, B, C classifications) - Students and teachers - Hotel staff and guests - Event staff (concerts, live events, theater, sporting events)

Clinical staff (Daybreak Health)

- Nurses - Primary care physicians - EMTs - Health coaches

Operations team

- Daybreak Health operations staff who approve and coordinate events

Key features

Feature map

High-level product backlog for Lighthouse V1, organized by functional area: sign-up and account creation, account management, home and patient list (testing), messaging, education, billing, and risk modeling.

Lighthouse Feature Map

Interactive board (same structure as the map above):

Live Preview

Sign up / Account Creation

12 features

Onboarding - tutorial
Create a user account (Invite only Daybreak - if exists request approval)
Create organization (Established Organizations show as autocomplete)
Create sub-organizations (Established sub-orgs show as autocomplete)
Define your testing scenario needs (single day event, duration of shoot, multiple locations, etc.) - request sent to Daybreak to build testing events, staffing, etc.
Invite other admins (Reg Code - Viewers or editors) - generates link to Lighthouse home for specific organization and sub-organization
Sign Consent
Approval state (Edit request)
Upload CSV file of patients?

Account

9 features

Log-in
Log-out
Profile
Account Settings
Terms of Service
Privacy Policy
FAQ
Add new admins
View past Organizations, sub-orgs, and events

Home / Patient List (Testing...)

21 features

View Organization
View Sub-Organization
View Events
View list of patients
User management admins - Add (invite/resend invite) and delete users - Permissions access rules (All users equal - easily configurable). Compliance and consent
View results: Covid, STI testing, Mental Health for all patients and departments
View locations and capacity
Compliance status? - main flag - First simplified data viz. Split list (default: negative). Positive, up-to-date, not-up-to-date
Filter by zone, time of day, etc.? (Tag users - High level tags for events)

Messaging

7 features

Send large scale message (email, text)? - Report for test (location and download patient app). Listen to customer preference
Send group messages / individual messages to users (once on app)
Show specific types of messages (compliance, emergency, e.g. "You are next" after check-in)
Push notifications (Schema - should only be done by Daybreak)
Canned messages
Create communication preferences

Education

6 features

See Robust FAQ (Expandable buckets of questions)
See blog (Contentful CMS - general educational blog posts) (Static)
Send PDFs, links, and videos on clinical info. to patients to reduce call volume
Send news and most recent protocols (From other organizations) to patients. (Static)
Education to explain results or get more tests done. (Real-time)

Billing

6 features

View invoice and billing history - Static reports
Pay invoice via link to Stripe (Unique Stripe payment link per event?)
Get reminder for bill

Risk Modeling holistic data

4 features

tsx
'use client'

import React from 'react'

const FEATURE_COLUMNS = [
  {
    title: 'Sign up / Account Creation',
    count: 12,
    items: [
      'Onboarding - tutorial',
      'Create a user account (Invite only Daybreak - if exists request approval)',
      'Create organization (Established Organizations show as autocomplete)',
      'Create sub-organizations (Established sub-orgs show as autocomplete)',
      'Define your testing scenario needs (single day event, duration of shoot, multiple locations, etc.) - request sent to Daybreak to build testing events, staffing, etc.',
      'Invite other admins (Reg Code - Viewers or editors) - generates link to Lighthouse home for specific organization and sub-organization',
      'Sign Consent',
      'Approval state (Edit request)',
      'Upload CSV file of patients?',
    ],
  },
  {
    title: 'Account',
    count: 9,
    items: [
      'Log-in',
      'Log-out',
      'Profile',
      'Account Settings',
      'Terms of Service',
      'Privacy Policy',
      'FAQ',
      'Add new admins',
      'View past Organizations, sub-orgs, and events',
    ],
  },
  {
    title: 'Home / Patient List (Testing...)',
    count: 21,
    items: [
      'View Organization',
      'View Sub-Organization',
      'View Events',
      'View list of patients',
      'User management admins - Add (invite/resend invite) and delete users - Permissions access rules (All users equal - easily configurable). Compliance and consent',
      'View results: Covid, STI testing, Mental Health for all patients and departments',
      'View locations and capacity',
      'Compliance status? - main flag - First simplified data viz. Split list (default: negative). Positive, up-to-date, not-up-to-date',
      'Filter by zone, time of day, etc.? (Tag users - High level tags for events)',
    ],
  },
  {
    title: 'Messaging',
    count: 7,
    items: [
      'Send large scale message (email, text)? - Report for test (location and download patient app). Listen to customer preference',
      'Send group messages / individual messages to users (once on app)',
      'Show specific types of messages (compliance, emergency, e.g. "You are next" after check-in)',
      'Push notifications (Schema - should only be done by Daybreak)',
      'Canned messages',
      'Create communication preferences',
    ],
  },
  {
    title: 'Education',
    count: 6,
    items: [
      'See Robust FAQ (Expandable buckets of questions)',
      'See blog (Contentful CMS - general educational blog posts) (Static)',
      'Send PDFs, links, and videos on clinical info. to patients to reduce call volume',
      'Send news and most recent protocols (From other organizations) to patients. (Static)',
      'Education to explain results or get more tests done. (Real-time)',
    ],
  },
  {
    title: 'Billing',
    count: 6,
    items: [
      'View invoice and billing history - Static reports',
      'Pay invoice via link to Stripe (Unique Stripe payment link per event?)',
      'Get reminder for bill',
    ],
  },
  {
    title: 'Risk Modeling holistic data',
    count: 4,
    items: [],
  },
]

export default function LighthouseFeatureMap() {
  return (
    <div className="w-full overflow-x-auto pb-4">
      <div className="inline-flex gap-4 min-w-max pr-4" style={{ minHeight: '320px' }}>
        {FEATURE_COLUMNS.map((col) => (
          <div
            key={col.title}
            className="flex-shrink-0 w-[280px] rounded-xl border border-[var(--border)] bg-[var(--bg-surface)] shadow-sm overflow-hidden flex flex-col"
          >
            <div className="px-4 py-3 border-b border-[var(--border)] bg-[var(--bg-primary)]/50">
              <h3 className="text-sm font-semibold text-[var(--text-primary)] leading-tight">
                {col.title}
              </h3>
              <p className="text-xs text-[var(--text-secondary)] mt-0.5">
                {col.count} {col.count === 1 ? 'feature' : 'features'}
              </p>
            </div>
            <div className="flex-1 overflow-y-auto p-3 space-y-2">
              {col.items.length > 0 ? (
                col.items.map((item, i) => (
                  <div
                    key={i}
                    className="text-xs text-[var(--text-primary)] bg-[var(--bg-primary)]/60 border border-[var(--border)] rounded-lg px-3 py-2 leading-snug"
                  >
                    {item}
                  </div>
                ))
              ) : (
                <p className="text-xs text-[var(--text-secondary)] italic py-2">—</p>
              )}
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

Onboarding & setup

Admin login with role-based access, comprehensive profile creation with testing location preferences (on-site, at-home PCR, or both), organization creation and volume estimation, and testing event configuration with patient assignment.

Dashboard & monitoring (web)

Multi-organization view for admins managing multiple entities, real-time testing event overview, high-level compliance dashboard highlighting at-risk patients, and quick navigation to organization-specific details.

Live Preview

Compliance dashboard

Monitor organization-wide health protocol compliance

Total tests
145
At risk
3
Compliant
142
Compliance rate
98%

At risk individuals

3
NameRoleIssueLast testStatusActions
Sarah JohnsonCinematographerTest expired 3 days ago
2024-02-12
Overdue
Michael ChenSound EngineerPending test result
2024-02-15
Pending
Emma RodriguezProduction AssistantTest expired 1 day ago
2024-02-14
Overdue

Recent testing events

Zone A Testing
2024-02-1545 participants
Completed
Weekly PCR Screening
2024-02-1432 participants
In Progress
Pre-Production Testing
2024-02-1368 participants
Completed
tsx
'use client'

import React, { useState } from 'react';
import { TrendingUp, Users, AlertCircle, CheckCircle2, Clock, Mail } from 'lucide-react';

export default function LighthouseDashboard() {
  const [selectedOrg, setSelectedOrg] = useState('Avengers Production');

  const organizations = [
    { name: 'Avengers Production', tests: 145, atRisk: 3, compliant: 142 },
    { name: 'WandaVision', tests: 89, atRisk: 1, compliant: 88 },
    { name: 'Restaurant Chain A', tests: 234, atRisk: 5, compliant: 229 },
  ];

  const atRiskIndividuals = {
    'Avengers Production': [
      { name: 'Sarah Johnson', role: 'Cinematographer', reason: 'Test expired 3 days ago', lastTest: '2024-02-12', status: 'overdue' },
      { name: 'Michael Chen', role: 'Sound Engineer', reason: 'Pending test result', lastTest: '2024-02-15', status: 'pending' },
      { name: 'Emma Rodriguez', role: 'Production Assistant', reason: 'Test expired 1 day ago', lastTest: '2024-02-14', status: 'overdue' },
    ],
    'WandaVision': [
      { name: 'David Park', role: 'Actor', reason: 'Test expired 2 days ago', lastTest: '2024-02-13', status: 'overdue' },
    ],
    'Restaurant Chain A': [
      { name: 'James Wilson', role: 'Head Chef', reason: 'Test expired 5 days ago', lastTest: '2024-02-10', status: 'overdue' },
      { name: 'Lisa Thompson', role: 'Server', reason: 'Pending test result', lastTest: '2024-02-15', status: 'pending' },
      { name: 'Robert Martinez', role: 'Manager', reason: 'Test expired 2 days ago', lastTest: '2024-02-13', status: 'overdue' },
      { name: 'Jennifer Lee', role: 'Server', reason: 'Pending test result', lastTest: '2024-02-15', status: 'pending' },
      { name: 'Thomas Brown', role: 'Cook', reason: 'Test expired 1 day ago', lastTest: '2024-02-14', status: 'overdue' },
    ],
  };

  const selectedOrgData = organizations.find(org => org.name === selectedOrg) || organizations[0];
  const selectedAtRisk = atRiskIndividuals[selectedOrg as keyof typeof atRiskIndividuals] || [];

  return (
    <div style={{
      padding: '24px',
      backgroundColor: '#FFFFFF',
      borderRadius: '8px',
      fontFamily: '"DM Sans", -apple-system, BlinkMacSystemFont, sans-serif',
      maxWidth: '1200px',
    }}>
      {/* Header */}
      <div style={{ marginBottom: '24px' }}>
        <h2 style={{
          fontSize: '24px',
          fontWeight: '600',
          color: '#1A1A1A',
          marginBottom: '8px',
        }}>
          Compliance dashboard
        </h2>
        <p style={{
          fontSize: '14px',
          color: '#6B6B6B',
        }}>
          Monitor organization-wide health protocol compliance
        </p>
      </div>

      {/* Organization Selector */}
      <div style={{
        display: 'flex',
        gap: '12px',
        marginBottom: '24px',
        flexWrap: 'wrap',
      }}>
        {organizations.map((org) => (
          <button
            key={org.name}
            onClick={() => setSelectedOrg(org.name)}
            style={{
              padding: '10px 16px',
              borderRadius: '8px',
              border: selectedOrg === org.name ? '2px solid #132A53' : '1px solid #E5E7EB',
              backgroundColor: selectedOrg === org.name ? '#F0F4F8' : '#FFFFFF',
              color: selectedOrg === org.name ? '#132A53' : '#6B6B6B',
              fontSize: '14px',
              fontWeight: selectedOrg === org.name ? '600' : '400',
              cursor: 'pointer',
              transition: 'all 0.2s ease',
            }}
          >
            {org.name}
          </button>
        ))}
      </div>

      {/* Stats Grid */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
        gap: '16px',
        marginBottom: '24px',
      }}>
        <div style={{
          padding: '20px',
          backgroundColor: '#F9FAFB',
          borderRadius: '8px',
          border: '1px solid #E5E7EB',
        }}>
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            marginBottom: '8px',
          }}>
            <Users size={18} color="#6B6B6B" />
            <span style={{
              fontSize: '12px',
              color: '#6B6B6B',
              fontWeight: '500',
            }}>
              Total tests
            </span>
          </div>
          <div style={{
            fontSize: '32px',
            fontWeight: '600',
            color: '#1A1A1A',
          }}>
            {selectedOrgData.tests}
          </div>
        </div>

        <div style={{
          padding: '20px',
          backgroundColor: '#FEF2F2',
          borderRadius: '8px',
          border: '1px solid #FECACA',
        }}>
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            marginBottom: '8px',
          }}>
            <AlertCircle size={18} color="#DC2626" />
            <span style={{
              fontSize: '12px',
              color: '#DC2626',
              fontWeight: '500',
            }}>
              At risk
            </span>
          </div>
          <div style={{
            fontSize: '32px',
            fontWeight: '600',
            color: '#DC2626',
          }}>
            {selectedOrgData.atRisk}
          </div>
        </div>

        <div style={{
          padding: '20px',
          backgroundColor: '#F0FDF4',
          borderRadius: '8px',
          border: '1px solid #BBF7D0',
        }}>
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            marginBottom: '8px',
          }}>
            <CheckCircle2 size={18} color="#059669" />
            <span style={{
              fontSize: '12px',
              color: '#059669',
              fontWeight: '500',
            }}>
              Compliant
            </span>
          </div>
          <div style={{
            fontSize: '32px',
            fontWeight: '600',
            color: '#059669',
          }}>
            {selectedOrgData.compliant}
          </div>
        </div>

        <div style={{
          padding: '20px',
          backgroundColor: '#F9FAFB',
          borderRadius: '8px',
          border: '1px solid #E5E7EB',
        }}>
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            marginBottom: '8px',
          }}>
            <TrendingUp size={18} color="#6B6B6B" />
            <span style={{
              fontSize: '12px',
              color: '#6B6B6B',
              fontWeight: '500',
            }}>
              Compliance rate
            </span>
          </div>
          <div style={{
            fontSize: '32px',
            fontWeight: '600',
            color: '#1A1A1A',
          }}>
            {Math.round((selectedOrgData.compliant / selectedOrgData.tests) * 100)}%
          </div>
        </div>
      </div>

      {/* At Risk Individuals Table */}
      {selectedAtRisk.length > 0 && (
        <div style={{
          padding: '20px',
          backgroundColor: '#FFFFFF',
          borderRadius: '8px',
          border: '1px solid #E5E7EB',
          marginBottom: '24px',
        }}>
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            marginBottom: '20px',
          }}>
            <AlertCircle size={20} color="#DC2626" />
            <h3 style={{
              fontSize: '18px',
              fontWeight: '600',
              color: '#1A1A1A',
            }}>
              At risk individuals
            </h3>
            <span style={{
              padding: '2px 8px',
              borderRadius: '12px',
              fontSize: '12px',
              fontWeight: '600',
              backgroundColor: '#FEF2F2',
              color: '#DC2626',
            }}>
              {selectedAtRisk.length}
            </span>
          </div>

          {/* Table */}
          <div style={{
            overflowX: 'auto',
          }}>
            <table style={{
              width: '100%',
              borderCollapse: 'collapse',
            }}>
              <thead>
                <tr style={{
                  borderBottom: '1px solid #E5E7EB',
                }}>
                  <th style={{
                    textAlign: 'left',
                    padding: '12px 16px',
                    fontSize: '12px',
                    fontWeight: '600',
                    color: '#6B6B6B',
                    textTransform: 'uppercase',
                    letterSpacing: '0.5px',
                  }}>Name</th>
                  <th style={{
                    textAlign: 'left',
                    padding: '12px 16px',
                    fontSize: '12px',
                    fontWeight: '600',
                    color: '#6B6B6B',
                    textTransform: 'uppercase',
                    letterSpacing: '0.5px',
                  }}>Role</th>
                  <th style={{
                    textAlign: 'left',
                    padding: '12px 16px',
                    fontSize: '12px',
                    fontWeight: '600',
                    color: '#6B6B6B',
                    textTransform: 'uppercase',
                    letterSpacing: '0.5px',
                  }}>Issue</th>
                  <th style={{
                    textAlign: 'left',
                    padding: '12px 16px',
                    fontSize: '12px',
                    fontWeight: '600',
                    color: '#6B6B6B',
                    textTransform: 'uppercase',
                    letterSpacing: '0.5px',
                  }}>Last test</th>
                  <th style={{
                    textAlign: 'left',
                    padding: '12px 16px',
                    fontSize: '12px',
                    fontWeight: '600',
                    color: '#6B6B6B',
                    textTransform: 'uppercase',
                    letterSpacing: '0.5px',
                  }}>Status</th>
                  <th style={{
                    textAlign: 'right',
                    padding: '12px 16px',
                    fontSize: '12px',
                    fontWeight: '600',
                    color: '#6B6B6B',
                    textTransform: 'uppercase',
                    letterSpacing: '0.5px',
                  }}>Actions</th>
                </tr>
              </thead>
              <tbody>
                {selectedAtRisk.map((person, idx) => (
                  <tr
                    key={idx}
                    style={{
                      borderBottom: idx < selectedAtRisk.length - 1 ? '1px solid #F3F4F6' : 'none',
                      transition: 'background-color 0.2s',
                    }}
                    onMouseEnter={(e) => {
                      e.currentTarget.style.backgroundColor = '#F9FAFB';
                    }}
                    onMouseLeave={(e) => {
                      e.currentTarget.style.backgroundColor = 'transparent';
                    }}
                  >
                    <td style={{
                      padding: '16px',
                      fontSize: '14px',
                      fontWeight: '600',
                      color: '#1A1A1A',
                    }}>
                      {person.name}
                    </td>
                    <td style={{
                      padding: '16px',
                      fontSize: '14px',
                      color: '#6B6B6B',
                    }}>
                      {person.role}
                    </td>
                    <td style={{
                      padding: '16px',
                      fontSize: '14px',
                      color: '#1A1A1A',
                    }}>
                      {person.reason}
                    </td>
                    <td style={{
                      padding: '16px',
                      fontSize: '14px',
                      color: '#6B6B6B',
                    }}>
                      <div style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: '6px',
                      }}>
                        <Clock size={14} color="#6B6B6B" />
                        {person.lastTest}
                      </div>
                    </td>
                    <td style={{
                      padding: '16px',
                    }}>
                      <span style={{
                        padding: '4px 12px',
                        borderRadius: '12px',
                        fontSize: '12px',
                        fontWeight: '600',
                        backgroundColor: person.status === 'overdue' ? '#FEF2F2' : '#FEF3C7',
                        color: person.status === 'overdue' ? '#DC2626' : '#D97706',
                      }}>
                        {person.status === 'overdue' ? 'Overdue' : 'Pending'}
                      </span>
                    </td>
                    <td style={{
                      padding: '16px',
                      textAlign: 'right',
                    }}>
                      <div style={{
                        display: 'flex',
                        gap: '8px',
                        justifyContent: 'flex-end',
                      }}>
                        <button
                          style={{
                            padding: '6px 12px',
                            borderRadius: '6px',
                            border: '1px solid #E5E7EB',
                            backgroundColor: '#FFFFFF',
                            color: '#132A53',
                            fontSize: '12px',
                            fontWeight: '500',
                            cursor: 'pointer',
                            display: 'flex',
                            alignItems: 'center',
                            gap: '6px',
                            transition: 'all 0.2s',
                          }}
                          onMouseEnter={(e) => {
                            e.currentTarget.style.backgroundColor = '#F3F4F6';
                            e.currentTarget.style.borderColor = '#132A53';
                          }}
                          onMouseLeave={(e) => {
                            e.currentTarget.style.backgroundColor = '#FFFFFF';
                            e.currentTarget.style.borderColor = '#E5E7EB';
                          }}
                        >
                          <Mail size={14} />
                          Contact
                        </button>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {/* Recent Events */}
      <div style={{
        padding: '20px',
        backgroundColor: '#F9FAFB',
        borderRadius: '8px',
        border: '1px solid #E5E7EB',
      }}>
        <h3 style={{
          fontSize: '16px',
          fontWeight: '600',
          color: '#1A1A1A',
          marginBottom: '16px',
        }}>
          Recent testing events
        </h3>
        <div style={{
          display: 'flex',
          flexDirection: 'column',
          gap: '12px',
        }}>
          {[
            { name: 'Zone A Testing', date: '2024-02-15', participants: 45, status: 'Completed' },
            { name: 'Weekly PCR Screening', date: '2024-02-14', participants: 32, status: 'In Progress' },
            { name: 'Pre-Production Testing', date: '2024-02-13', participants: 68, status: 'Completed' },
          ].map((event, idx) => (
            <div
              key={idx}
              style={{
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
                padding: '12px',
                backgroundColor: '#FFFFFF',
                borderRadius: '6px',
                border: '1px solid #E5E7EB',
              }}
            >
              <div>
                <div style={{
                  fontSize: '14px',
                  fontWeight: '600',
                  color: '#1A1A1A',
                  marginBottom: '4px',
                }}>
                  {event.name}
                </div>
                <div style={{
                  fontSize: '12px',
                  color: '#6B6B6B',
                }}>
                  {event.date} • {event.participants} participants
                </div>
              </div>
              <span style={{
                padding: '4px 12px',
                borderRadius: '12px',
                fontSize: '12px',
                fontWeight: '600',
                backgroundColor: event.status === 'Completed' ? '#ECFDF5' : '#FEF3C7',
                color: event.status === 'Completed' ? '#059669' : '#D97706',
              }}>
                {event.status}
              </span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}


Results management

Patient list view organized within testing events, advanced filtering (name, test type, results status, timestamp), individual patient result details (positive, negative, pending), direct communication options (text/email to patient or Daybreak Health), and COVID-19 result tracking (extensible for additional test types).

Lighthouse home and live event testing results

Event coordination

Event request submission with 24-48 hour approval workflow and operations team review and approval process.

Live Preview
1
2
3
4

Request new event

Fill in the basic details for your testing event

tsx
'use client'

import React, { useState, useRef, useEffect } from 'react';
import { Calendar, MapPin, Users, Clock, CheckCircle2, ChevronDown } from 'lucide-react';

export default function LighthouseEventRequest() {
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({
    eventName: '',
    organization: '',
    customOrganization: '',
    organizationType: '',
    street: '',
    streetNumber: '',
    city: '',
    state: '',
    zipCode: '',
    date: '',
    participants: '',
    testType: 'PCR',
  });
  const [isCustomOrgMode, setIsCustomOrgMode] = useState(false);
  const [isOrgDropdownOpen, setIsOrgDropdownOpen] = useState(false);
  const [isOrgTypeDropdownOpen, setIsOrgTypeDropdownOpen] = useState(false);
  const [isTestTypeDropdownOpen, setIsTestTypeDropdownOpen] = useState(false);
  const orgDropdownRef = useRef<HTMLDivElement>(null);
  const orgTypeDropdownRef = useRef<HTMLDivElement>(null);
  const testTypeDropdownRef = useRef<HTMLDivElement>(null);

  const organizations = [
    { value: 'avengers', label: 'Avengers Production' },
    { value: 'wandavision', label: 'WandaVision' },
    { value: 'restaurant', label: 'Restaurant Chain A' },
  ];

  const organizationTypes = [
    { value: 'film', label: 'Film' },
    { value: 'broadway', label: 'Broadway' },
    { value: 'television', label: 'Television' },
    { value: 'event', label: 'Event' },
    { value: 'other', label: 'Other' },
  ];

  const testTypes = [
    { value: 'PCR', label: 'PCR' },
    { value: 'Rapid', label: 'Rapid Antigen' },
    { value: 'Both', label: 'Both' },
  ];

  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (orgDropdownRef.current && !orgDropdownRef.current.contains(e.target as Node)) {
        setIsOrgDropdownOpen(false);
      }
      if (orgTypeDropdownRef.current && !orgTypeDropdownRef.current.contains(e.target as Node)) {
        setIsOrgTypeDropdownOpen(false);
      }
      if (testTypeDropdownRef.current && !testTypeDropdownRef.current.contains(e.target as Node)) {
        setIsTestTypeDropdownOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const handleInputChange = (field: string, value: string) => {
    setFormData(prev => ({ ...prev, [field]: value }));
  };

  const handleNext = () => {
    if (step < 4) {
      setStep(step + 1);
    }
  };

  const handleSubmit = () => {
    setStep(5);
  };

  if (step === 5) {
    return (
      <div style={{
        padding: '40px',
        backgroundColor: '#FFFFFF',
        borderRadius: '8px',
        fontFamily: '"DM Sans", -apple-system, BlinkMacSystemFont, sans-serif',
        width: '100%',
        textAlign: 'center',
      }}>
        <div style={{
          width: '64px',
          height: '64px',
          borderRadius: '50%',
          backgroundColor: '#ECFDF5',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          margin: '0 auto 24px',
        }}>
          <CheckCircle2 size={32} color="#059669" />
        </div>
        <h2 style={{
          fontSize: '24px',
          fontWeight: '600',
          color: '#1A1A1A',
          marginBottom: '12px',
        }}>
          Event request submitted
        </h2>
        <p style={{
          fontSize: '14px',
          color: '#6B6B6B',
          marginBottom: '24px',
          lineHeight: '1.6',
        }}>
          Your event request has been submitted successfully. The operations team will review and approve your request within 24-48 hours. You'll receive a notification once it's been processed.
        </p>
        <button
          onClick={() => {
            setStep(1);
            setFormData({
              eventName: '',
              organization: '',
              customOrganization: '',
              organizationType: '',
              street: '',
              streetNumber: '',
              city: '',
              state: '',
              zipCode: '',
              date: '',
              participants: '',
              testType: 'PCR',
            });
            setIsCustomOrgMode(false);
          }}
          style={{
            padding: '12px 24px',
            backgroundColor: '#132A53',
            color: '#FFFFFF',
            border: 'none',
            borderRadius: '8px',
            fontSize: '14px',
            fontWeight: '600',
            cursor: 'pointer',
          }}
        >
          Create another event
        </button>
      </div>
    );
  }

  const selectedOrg = organizations.find(org => org.value === formData.organization);
  const selectedTestType = testTypes.find(type => type.value === formData.testType);

  // Validation functions for each step
  const isStep1Valid = () => {
    return formData.eventName.trim() !== '' && (formData.organization !== '' || formData.customOrganization.trim() !== '');
  };

  const isStep2Valid = () => {
    return formData.organizationType !== '' &&
           formData.streetNumber.trim() !== '' &&
           formData.street.trim() !== '' &&
           formData.city.trim() !== '' &&
           formData.state.trim() !== '' &&
           formData.zipCode.trim() !== '' &&
           formData.testType !== '';
  };

  const isStep3Valid = () => {
    return formData.date !== '' && formData.participants.trim() !== '';
  };

  return (
    <div style={{
      padding: '32px',
      backgroundColor: '#FFFFFF',
      borderRadius: '8px',
      fontFamily: '"DM Sans", -apple-system, BlinkMacSystemFont, sans-serif',
      width: '100%',
      minHeight: '600px',
      position: 'relative',
    }}>
      {/* Progress Indicator */}
      <div style={{
        display: 'flex',
        alignItems: 'center',
        marginBottom: '32px',
        gap: '8px',
        width: '100%',
      }}>
        {[1, 2, 3, 4].map((s) => (
          <React.Fragment key={s}>
            <div style={{
              width: '32px',
              height: '32px',
              borderRadius: '50%',
              backgroundColor: step >= s ? '#132A53' : '#E5E7EB',
              color: step >= s ? '#FFFFFF' : '#6B6B6B',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '14px',
              fontWeight: '600',
              flexShrink: 0,
            }}>
              {step > s ? '✓' : s}
            </div>
            {s < 4 && (
              <div style={{
                flex: 1,
                height: '2px',
                backgroundColor: step > s ? '#132A53' : '#E5E7EB',
                transition: 'background-color 0.3s ease',
              }} />
            )}
          </React.Fragment>
        ))}
      </div>

      <h2 style={{
        fontSize: '24px',
        fontWeight: '600',
        color: '#1A1A1A',
        marginBottom: '8px',
      }}>
        {step === 1 ? 'Request new event' : step === 2 ? 'Event details' : step === 3 ? 'Schedule & participants' : 'Review & submit'}
      </h2>
      <p style={{
        fontSize: '14px',
        color: '#6B6B6B',
        marginBottom: '24px',
      }}>
        {step === 1 ? 'Fill in the basic details for your testing event' : step === 2 ? 'Add location and test type information' : step === 3 ? 'Set schedule and participant count' : 'Review your event details before submitting'}
      </p>

      {step === 1 ? (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
          <div>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Event name
            </label>
            <input
              type="text"
              value={formData.eventName}
              onChange={(e) => handleInputChange('eventName', e.target.value)}
              placeholder="e.g., Zone A Weekly Testing"
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                boxSizing: 'border-box',
              }}
            />
          </div>

          <div ref={orgDropdownRef} style={{ position: 'relative' }}>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Select organization
            </label>
            {!isCustomOrgMode ? (
              <>
                <button
                  type="button"
                  onClick={() => setIsOrgDropdownOpen(!isOrgDropdownOpen)}
                  style={{
                    width: '100%',
                    padding: '12px 16px',
                    border: '1px solid #E5E7EB',
                    borderRadius: '8px',
                    fontSize: '14px',
                    backgroundColor: '#FFFFFF',
                    color: (selectedOrg || formData.customOrganization) ? '#1A1A1A' : '#9CA3AF',
                    textAlign: 'left',
                    cursor: 'pointer',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'space-between',
                    boxSizing: 'border-box',
                  }}
                >
                  <span>{selectedOrg ? selectedOrg.label : formData.customOrganization || 'Select organization'}</span>
                  <ChevronDown size={16} color="#6B6B6B" style={{ transform: isOrgDropdownOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }} />
                </button>
                {isOrgDropdownOpen && (
                  <div style={{
                    position: 'absolute',
                    top: '100%',
                    left: 0,
                    right: 0,
                    marginTop: '4px',
                    backgroundColor: '#FFFFFF',
                    border: '1px solid #E5E7EB',
                    borderRadius: '8px',
                    boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
                    zIndex: 10,
                    overflow: 'hidden',
                  }}>
                    {organizations.map((org) => (
                      <button
                        key={org.value}
                        type="button"
                        onClick={() => {
                          handleInputChange('organization', org.value);
                          handleInputChange('customOrganization', '');
                          setIsCustomOrgMode(false);
                          setIsOrgDropdownOpen(false);
                        }}
                        style={{
                          width: '100%',
                          padding: '12px 16px',
                          textAlign: 'left',
                          fontSize: '14px',
                          color: '#1A1A1A',
                          backgroundColor: formData.organization === org.value ? '#F3F4F6' : '#FFFFFF',
                          border: 'none',
                          cursor: 'pointer',
                          transition: 'background-color 0.2s',
                        }}
                        onMouseEnter={(e) => {
                          if (formData.organization !== org.value) {
                            e.currentTarget.style.backgroundColor = '#F9FAFB';
                          }
                        }}
                        onMouseLeave={(e) => {
                          if (formData.organization !== org.value) {
                            e.currentTarget.style.backgroundColor = '#FFFFFF';
                          }
                        }}
                      >
                        {org.label}
                      </button>
                    ))}
                    <button
                      type="button"
                      onClick={() => {
                        setIsCustomOrgMode(true);
                        handleInputChange('organization', '');
                        setIsOrgDropdownOpen(false);
                      }}
                      style={{
                        width: '100%',
                        padding: '12px 16px',
                        textAlign: 'left',
                        fontSize: '14px',
                        color: '#132A53',
                        backgroundColor: '#FFFFFF',
                        border: 'none',
                        borderTop: '1px solid #E5E7EB',
                        cursor: 'pointer',
                        transition: 'background-color 0.2s',
                      }}
                      onMouseEnter={(e) => {
                        e.currentTarget.style.backgroundColor = '#F9FAFB';
                      }}
                      onMouseLeave={(e) => {
                        e.currentTarget.style.backgroundColor = '#FFFFFF';
                      }}
                    >
                      + Add new organization
                    </button>
                  </div>
                )}
              </>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
                <input
                  type="text"
                  value={formData.customOrganization}
                  onChange={(e) => handleInputChange('customOrganization', e.target.value)}
                  placeholder="Enter organization name"
                  style={{
                    width: '100%',
                    padding: '12px 16px',
                    border: '1px solid #E5E7EB',
                    borderRadius: '8px',
                    fontSize: '14px',
                    boxSizing: 'border-box',
                  }}
                />
                <button
                  type="button"
                  onClick={() => {
                    setIsCustomOrgMode(false);
                    handleInputChange('customOrganization', '');
                  }}
                  style={{
                    padding: '6px 12px',
                    backgroundColor: 'transparent',
                    color: '#6B6B6B',
                    border: 'none',
                    borderRadius: '6px',
                    fontSize: '12px',
                    fontWeight: '500',
                    cursor: 'pointer',
                    alignSelf: 'flex-start',
                  }}
                  onMouseEnter={(e) => {
                    e.currentTarget.style.backgroundColor = '#F3F4F6';
                    e.currentTarget.style.color = '#1A1A1A';
                  }}
                  onMouseLeave={(e) => {
                    e.currentTarget.style.backgroundColor = 'transparent';
                    e.currentTarget.style.color = '#6B6B6B';
                  }}
                >
                  ← Back to list
                </button>
              </div>
            )}
          </div>

          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '24px' }}>
            <div style={{ width: '80px' }} /> {/* Spacer for alignment */}
            <button
              onClick={handleNext}
              disabled={!isStep1Valid()}
              style={{
                padding: '8px 16px',
                backgroundColor: isStep1Valid() ? '#132A53' : '#E5E7EB',
                color: isStep1Valid() ? '#FFFFFF' : '#9CA3AF',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: isStep1Valid() ? 'pointer' : 'not-allowed',
                transition: 'all 0.2s ease',
              }}
            >
              Continue
            </button>
          </div>
        </div>
      ) : step === 2 ? (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
          <div ref={orgTypeDropdownRef} style={{ position: 'relative' }}>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Organization type
            </label>
            <button
              type="button"
              onClick={() => setIsOrgTypeDropdownOpen(!isOrgTypeDropdownOpen)}
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                backgroundColor: '#FFFFFF',
                color: formData.organizationType ? '#1A1A1A' : '#9CA3AF',
                textAlign: 'left',
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'space-between',
                boxSizing: 'border-box',
              }}
            >
              <span>{formData.organizationType ? organizationTypes.find(t => t.value === formData.organizationType)?.label : 'Select organization type'}</span>
              <ChevronDown size={16} color="#6B6B6B" style={{ transform: isOrgTypeDropdownOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }} />
            </button>
            {isOrgTypeDropdownOpen && (
              <div style={{
                position: 'absolute',
                top: '100%',
                left: 0,
                right: 0,
                marginTop: '4px',
                backgroundColor: '#FFFFFF',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
                zIndex: 10,
                overflow: 'hidden',
              }}>
                {organizationTypes.map((type) => (
                  <button
                    key={type.value}
                    type="button"
                    onClick={() => {
                      handleInputChange('organizationType', type.value);
                      setIsOrgTypeDropdownOpen(false);
                    }}
                    style={{
                      width: '100%',
                      padding: '12px 16px',
                      textAlign: 'left',
                      fontSize: '14px',
                      color: '#1A1A1A',
                      backgroundColor: formData.organizationType === type.value ? '#F3F4F6' : '#FFFFFF',
                      border: 'none',
                      cursor: 'pointer',
                      transition: 'background-color 0.2s',
                    }}
                    onMouseEnter={(e) => {
                      if (formData.organizationType !== type.value) {
                        e.currentTarget.style.backgroundColor = '#F9FAFB';
                      }
                    }}
                    onMouseLeave={(e) => {
                      if (formData.organizationType !== type.value) {
                        e.currentTarget.style.backgroundColor = '#FFFFFF';
                      }
                    }}
                  >
                    {type.label}
                  </button>
                ))}
              </div>
            )}
          </div>

          <div>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Street number
            </label>
            <input
              type="text"
              value={formData.streetNumber}
              onChange={(e) => handleInputChange('streetNumber', e.target.value)}
              placeholder="Enter street number"
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                boxSizing: 'border-box',
              }}
            />
          </div>

          <div>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Street
            </label>
            <input
              type="text"
              value={formData.street}
              onChange={(e) => handleInputChange('street', e.target.value)}
              placeholder="Enter street name"
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                boxSizing: 'border-box',
              }}
            />
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
            <div>
              <label style={{
                display: 'block',
                fontSize: '14px',
                fontWeight: '500',
                color: '#1A1A1A',
                marginBottom: '8px',
              }}>
                City
              </label>
              <input
                type="text"
                value={formData.city}
                onChange={(e) => handleInputChange('city', e.target.value)}
                placeholder="Enter city"
                style={{
                  width: '100%',
                  padding: '12px 16px',
                  border: '1px solid #E5E7EB',
                  borderRadius: '8px',
                  fontSize: '14px',
                  boxSizing: 'border-box',
                }}
              />
            </div>

            <div>
              <label style={{
                display: 'block',
                fontSize: '14px',
                fontWeight: '500',
                color: '#1A1A1A',
                marginBottom: '8px',
              }}>
                State
              </label>
              <input
                type="text"
                value={formData.state}
                onChange={(e) => handleInputChange('state', e.target.value)}
                placeholder="Enter state"
                style={{
                  width: '100%',
                  padding: '12px 16px',
                  border: '1px solid #E5E7EB',
                  borderRadius: '8px',
                  fontSize: '14px',
                  boxSizing: 'border-box',
                }}
              />
            </div>
          </div>

          <div>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Zip code
            </label>
            <input
              type="text"
              value={formData.zipCode}
              onChange={(e) => handleInputChange('zipCode', e.target.value)}
              placeholder="Enter zip code"
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                boxSizing: 'border-box',
              }}
            />
          </div>

          <div ref={testTypeDropdownRef} style={{ position: 'relative' }}>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Test type
            </label>
            <button
              type="button"
              onClick={() => setIsTestTypeDropdownOpen(!isTestTypeDropdownOpen)}
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                backgroundColor: '#FFFFFF',
                color: selectedTestType ? '#1A1A1A' : '#9CA3AF',
                textAlign: 'left',
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'space-between',
                boxSizing: 'border-box',
              }}
            >
              <span>{selectedTestType ? selectedTestType.label : 'Select test type'}</span>
              <ChevronDown size={16} color="#6B6B6B" style={{ transform: isTestTypeDropdownOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }} />
            </button>
            {isTestTypeDropdownOpen && (
              <div style={{
                position: 'absolute',
                top: '100%',
                left: 0,
                right: 0,
                marginTop: '4px',
                backgroundColor: '#FFFFFF',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
                zIndex: 10,
                overflow: 'hidden',
              }}>
                {testTypes.map((type) => (
                  <button
                    key={type.value}
                    type="button"
                    onClick={() => {
                      handleInputChange('testType', type.value);
                      setIsTestTypeDropdownOpen(false);
                    }}
                    style={{
                      width: '100%',
                      padding: '12px 16px',
                      textAlign: 'left',
                      fontSize: '14px',
                      color: '#1A1A1A',
                      backgroundColor: formData.testType === type.value ? '#F3F4F6' : '#FFFFFF',
                      border: 'none',
                      cursor: 'pointer',
                      transition: 'background-color 0.2s',
                    }}
                    onMouseEnter={(e) => {
                      if (formData.testType !== type.value) {
                        e.currentTarget.style.backgroundColor = '#F9FAFB';
                      }
                    }}
                    onMouseLeave={(e) => {
                      if (formData.testType !== type.value) {
                        e.currentTarget.style.backgroundColor = '#FFFFFF';
                      }
                    }}
                  >
                    {type.label}
                  </button>
                ))}
              </div>
            )}
          </div>

          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '24px' }}>
            <button
              onClick={() => setStep(1)}
              style={{
                padding: '6px 12px',
                backgroundColor: 'transparent',
                color: '#6B6B6B',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: 'pointer',
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.backgroundColor = '#F3F4F6';
                e.currentTarget.style.color = '#1A1A1A';
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.backgroundColor = 'transparent';
                e.currentTarget.style.color = '#6B6B6B';
              }}
            >
              ← Back
            </button>
            <button
              onClick={handleNext}
              disabled={!isStep2Valid()}
              style={{
                padding: '8px 16px',
                backgroundColor: isStep2Valid() ? '#132A53' : '#E5E7EB',
                color: isStep2Valid() ? '#FFFFFF' : '#9CA3AF',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: isStep2Valid() ? 'pointer' : 'not-allowed',
                transition: 'all 0.2s ease',
              }}
            >
              Continue
            </button>
          </div>
        </div>
      ) : step === 3 ? (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
          <div>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Event date
            </label>
            <input
              type="date"
              value={formData.date}
              onChange={(e) => handleInputChange('date', e.target.value)}
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                boxSizing: 'border-box',
              }}
            />
          </div>

          <div>
            <label style={{
              display: 'block',
              fontSize: '14px',
              fontWeight: '500',
              color: '#1A1A1A',
              marginBottom: '8px',
            }}>
              Expected participants
            </label>
            <input
              type="number"
              value={formData.participants}
              onChange={(e) => handleInputChange('participants', e.target.value)}
              placeholder="Enter number of participants"
              style={{
                width: '100%',
                padding: '12px 16px',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                fontSize: '14px',
                boxSizing: 'border-box',
              }}
            />
          </div>

          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '24px' }}>
            <button
              onClick={() => setStep(2)}
              style={{
                padding: '6px 12px',
                backgroundColor: 'transparent',
                color: '#6B6B6B',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: 'pointer',
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.backgroundColor = '#F3F4F6';
                e.currentTarget.style.color = '#1A1A1A';
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.backgroundColor = 'transparent';
                e.currentTarget.style.color = '#6B6B6B';
              }}
            >
              ← Back
            </button>
            <button
              onClick={handleNext}
              disabled={!isStep3Valid()}
              style={{
                padding: '8px 16px',
                backgroundColor: isStep3Valid() ? '#132A53' : '#E5E7EB',
                color: isStep3Valid() ? '#FFFFFF' : '#9CA3AF',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: isStep3Valid() ? 'pointer' : 'not-allowed',
                transition: 'all 0.2s ease',
              }}
            >
              Continue
            </button>
          </div>
        </div>
      ) : (
        <div>
          <div style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '16px',
            marginBottom: '24px',
            padding: '20px',
            backgroundColor: '#F9FAFB',
            borderRadius: '8px',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
              <Calendar size={18} color="#6B6B6B" />
              <div>
                <div style={{ fontSize: '12px', color: '#6B6B6B' }}>Event name</div>
                <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A' }}>
                  {formData.eventName || 'Zone A Weekly Testing'}
                </div>
              </div>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
              <Users size={18} color="#6B6B6B" />
              <div>
                <div style={{ fontSize: '12px', color: '#6B6B6B' }}>Organization</div>
                <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A' }}>
                  {selectedOrg ? selectedOrg.label : formData.customOrganization || 'Avengers Production'}
                </div>
              </div>
            </div>
            {formData.organizationType && (
              <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
                <Users size={18} color="#6B6B6B" />
                <div>
                  <div style={{ fontSize: '12px', color: '#6B6B6B' }}>Organization type</div>
                  <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A' }}>
                    {organizationTypes.find(t => t.value === formData.organizationType)?.label || formData.organizationType}
                  </div>
                </div>
              </div>
            )}
            {(formData.streetNumber || formData.street || formData.city || formData.state || formData.zipCode) && (
              <div style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
                <MapPin size={18} color="#6B6B6B" style={{ marginTop: '2px' }} />
                <div>
                  <div style={{ fontSize: '12px', color: '#6B6B6B', marginBottom: '4px' }}>Address</div>
                  <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A', lineHeight: '1.5' }}>
                    {[formData.streetNumber, formData.street].filter(Boolean).join(' ')}
                    {formData.city && <><br />{formData.city}</>}
                    {formData.state && <>, {formData.state}</>}
                    {formData.zipCode && <> {formData.zipCode}</>}
                  </div>
                </div>
              </div>
            )}
            <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
              <Clock size={18} color="#6B6B6B" />
              <div>
                <div style={{ fontSize: '12px', color: '#6B6B6B' }}>Test type</div>
                <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A' }}>
                  {selectedTestType ? selectedTestType.label : formData.testType}
                </div>
              </div>
            </div>
            {formData.date && (
              <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
                <Calendar size={18} color="#6B6B6B" />
                <div>
                  <div style={{ fontSize: '12px', color: '#6B6B6B' }}>Event date</div>
                  <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A' }}>
                    {new Date(formData.date).toLocaleDateString()}
                  </div>
                </div>
              </div>
            )}
            {formData.participants && (
              <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
                <Users size={18} color="#6B6B6B" />
                <div>
                  <div style={{ fontSize: '12px', color: '#6B6B6B' }}>Expected participants</div>
                  <div style={{ fontSize: '14px', fontWeight: '600', color: '#1A1A1A' }}>
                    {formData.participants}
                  </div>
                </div>
              </div>
            )}
          </div>

          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <button
              onClick={() => setStep(3)}
              style={{
                padding: '6px 12px',
                backgroundColor: 'transparent',
                color: '#6B6B6B',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: 'pointer',
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.backgroundColor = '#F3F4F6';
                e.currentTarget.style.color = '#1A1A1A';
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.backgroundColor = 'transparent';
                e.currentTarget.style.color = '#6B6B6B';
              }}
            >
              ← Back
            </button>
            <button
              onClick={handleSubmit}
              disabled={!isStep1Valid() || !isStep2Valid() || !isStep3Valid()}
              style={{
                padding: '8px 16px',
                backgroundColor: (isStep1Valid() && isStep2Valid() && isStep3Valid()) ? '#132A53' : '#E5E7EB',
                color: (isStep1Valid() && isStep2Valid() && isStep3Valid()) ? '#FFFFFF' : '#9CA3AF',
                border: 'none',
                borderRadius: '6px',
                fontSize: '14px',
                fontWeight: '500',
                cursor: (isStep1Valid() && isStep2Valid() && isStep3Valid()) ? 'pointer' : 'not-allowed',
                transition: 'all 0.2s ease',
              }}
            >
              Submit request
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Mobile app view for requesting an event.

Lighthouse event coordination: request event, event information, and type of test selection

Components

Date range picker

A calendar component for selecting date ranges, used throughout the platform for scheduling events and filtering results by time periods.

Live Preview
February
Mo
Tu
We
Th
Fr
Sa
Su
March
Mo
Tu
We
Th
Fr
Sa
Su
0 days selected
tsx
'use client'

import React, { useState, useRef, useEffect } from 'react';
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react';

export default function LighthouseDateRangePicker() {
  const [currentView, setCurrentView] = useState(new Date());
  const [selectedStart, setSelectedStart] = useState<Date | null>(null);
  const [selectedEnd, setSelectedEnd] = useState<Date | null>(null);
  const [hoverDate, setHoverDate] = useState<Date | null>(null);
  const [isYearDropdownOpen, setIsYearDropdownOpen] = useState(false);
  const yearDropdownRef = useRef<HTMLDivElement>(null);

  const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  const dayNames = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];

  // Get years for dropdown (current year ± 5 years)
  const getYears = () => {
    const currentYear = new Date().getFullYear();
    const years = [];
    for (let i = -5; i <= 5; i++) {
      years.push(currentYear + i);
    }
    return years;
  };

  // Get months to display in the left panel (just the 12 months of the selected year)
  const getMonthList = () => {
    const months = [];
    const year = currentView.getFullYear();
    for (let i = 0; i < 12; i++) {
      months.push(new Date(year, i, 1));
    }
    return months;
  };

  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (yearDropdownRef.current && !yearDropdownRef.current.contains(e.target as Node)) {
        setIsYearDropdownOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const getDaysInMonth = (date: Date) => {
    const year = date.getFullYear();
    const month = date.getMonth();
    const firstDay = new Date(year, month, 1);
    const lastDay = new Date(year, month + 1, 0);
    const daysInMonth = lastDay.getDate();
    const startingDayOfWeek = firstDay.getDay();
    // Adjust for Monday as first day (0 = Sunday, so we shift)
    const adjustedStart = startingDayOfWeek === 0 ? 6 : startingDayOfWeek - 1;

    const days = [];
    // Empty cells for days before month starts
    for (let i = 0; i < adjustedStart; i++) {
      days.push(null);
    }
    // Days of the month
    for (let i = 1; i <= daysInMonth; i++) {
      days.push(new Date(year, month, i));
    }
    return days;
  };

  const handleDateClick = (date: Date) => {
    if (!selectedStart || (selectedStart && selectedEnd)) {
      setSelectedStart(date);
      setSelectedEnd(null);
    } else if (selectedStart && !selectedEnd) {
      if (date < selectedStart) {
        setSelectedEnd(selectedStart);
        setSelectedStart(date);
      } else {
        setSelectedEnd(date);
      }
    }
  };

  const normalizeDate = (date: Date) => {
    const normalized = new Date(date);
    normalized.setHours(0, 0, 0, 0);
    return normalized;
  };

  const isInRange = (date: Date) => {
    if (!selectedStart) return false;
    if (!selectedEnd) return false; // Only show range when both start and end are selected
    
    const normalizedDate = normalizeDate(date);
    const normalizedStart = normalizeDate(selectedStart);
    const normalizedEnd = normalizeDate(selectedEnd);
    
    // Determine the actual range (handle reverse selection)
    const rangeStart = normalizedStart < normalizedEnd ? normalizedStart : normalizedEnd;
    const rangeEnd = normalizedStart > normalizedEnd ? normalizedStart : normalizedEnd;
    
    // Include all dates between start and end (inclusive)
    // The backgroundColor logic will prioritize selected dates over range dates
    return normalizedDate >= rangeStart && normalizedDate <= rangeEnd;
  };

  const isSelected = (date: Date) => {
    if (!selectedStart) return false;
    const normalizedDate = normalizeDate(date);
    const normalizedStart = normalizeDate(selectedStart);
    if (normalizedStart.getTime() === normalizedDate.getTime()) return true;
    if (selectedEnd) {
      const normalizedEnd = normalizeDate(selectedEnd);
      if (normalizedEnd.getTime() === normalizedDate.getTime()) return true;
    }
    return false;
  };

  const getSelectedDaysCount = () => {
    if (!selectedStart) return 0;
    if (!selectedEnd) return 1;
    const diffTime = Math.abs(selectedEnd.getTime() - selectedStart.getTime());
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
    return diffDays;
  };

  const handleClear = () => {
    setSelectedStart(null);
    setSelectedEnd(null);
    setHoverDate(null);
  };

  const handleSelectDates = () => {
    // This would typically close the picker or trigger a callback
    console.log('Dates selected:', selectedStart, selectedEnd);
  };

  const monthList = getMonthList();
  const currentMonth = currentView;
  const nextMonth = new Date(currentView.getFullYear(), currentView.getMonth() + 1, 1);
  const currentMonthDays = getDaysInMonth(currentMonth);
  const nextMonthDays = getDaysInMonth(nextMonth);
  const today = new Date();
  today.setHours(0, 0, 0, 0);

  return (
    <div style={{
      backgroundColor: '#FFFFFF',
      borderRadius: '12px',
      border: '1px solid #E5E7EB',
      fontFamily: '"DM Sans", -apple-system, BlinkMacSystemFont, sans-serif',
      width: '100%',
      maxWidth: '800px',
      overflow: 'hidden',
    }}>
      <div style={{
        display: 'flex',
        height: '500px',
      }}>
        {/* Left Panel - Month Selection */}
        <div style={{
          width: '200px',
          borderRight: '1px solid #E5E7EB',
          overflowY: 'auto',
          backgroundColor: '#FAFAFA',
        }}>
          {monthList.map((month, idx) => {
            const isActive = month.getMonth() === currentView.getMonth() && month.getFullYear() === currentView.getFullYear();
            const monthName = monthNames[month.getMonth()];
            
            return (
              <button
                key={idx}
                onClick={() => setCurrentView(month)}
                style={{
                  width: '100%',
                  padding: '12px 16px',
                  textAlign: 'left',
                  fontSize: '14px',
                  fontWeight: isActive ? '600' : '400',
                  color: isActive ? '#1A1A1A' : '#6B6B6B',
                  backgroundColor: isActive ? '#E8EDF4' : 'transparent',
                  border: 'none',
                  borderRight: isActive ? '3px solid #132A53' : '3px solid transparent',
                  cursor: 'pointer',
                  transition: 'all 0.2s ease',
                }}
                onMouseEnter={(e) => {
                  if (!isActive) {
                    e.currentTarget.style.backgroundColor = '#F3F4F6';
                  }
                }}
                onMouseLeave={(e) => {
                  if (!isActive) {
                    e.currentTarget.style.backgroundColor = 'transparent';
                  }
                }}
              >
                {monthName}
              </button>
            );
          })}
        </div>

        {/* Right Panel - Calendar Grids */}
        <div style={{
          flex: 1,
          padding: '24px',
          overflowY: 'auto',
        }}>
          {/* Year Picker */}
          <div ref={yearDropdownRef} style={{ position: 'relative', marginBottom: '20px' }}>
            <button
              onClick={() => setIsYearDropdownOpen(!isYearDropdownOpen)}
              style={{
                padding: '8px 12px',
                border: '1px solid #E5E7EB',
                borderRadius: '6px',
                fontSize: '14px',
                backgroundColor: '#FFFFFF',
                color: '#1A1A1A',
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                gap: '8px',
              }}
            >
              <span>{currentView.getFullYear()}</span>
              <ChevronDown size={16} color="#6B6B6B" style={{ transform: isYearDropdownOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }} />
            </button>
            {isYearDropdownOpen && (
              <div style={{
                position: 'absolute',
                top: '100%',
                left: 0,
                marginTop: '4px',
                backgroundColor: '#FFFFFF',
                border: '1px solid #E5E7EB',
                borderRadius: '8px',
                boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
                zIndex: 10,
                maxHeight: '200px',
                overflowY: 'auto',
                minWidth: '100px',
              }}>
                {getYears().map((year) => (
                  <button
                    key={year}
                    onClick={() => {
                      setCurrentView(new Date(year, currentView.getMonth(), 1));
                      setIsYearDropdownOpen(false);
                    }}
                    style={{
                      width: '100%',
                      padding: '8px 12px',
                      textAlign: 'left',
                      fontSize: '14px',
                      color: currentView.getFullYear() === year ? '#1A1A1A' : '#6B6B6B',
                      backgroundColor: currentView.getFullYear() === year ? '#F3F4F6' : '#FFFFFF',
                      border: 'none',
                      cursor: 'pointer',
                      transition: 'background-color 0.2s',
                    }}
                    onMouseEnter={(e) => {
                      if (currentView.getFullYear() !== year) {
                        e.currentTarget.style.backgroundColor = '#F9FAFB';
                      }
                    }}
                    onMouseLeave={(e) => {
                      if (currentView.getFullYear() !== year) {
                        e.currentTarget.style.backgroundColor = '#FFFFFF';
                      }
                    }}
                  >
                    {year}
                  </button>
                ))}
              </div>
            )}
          </div>

          {/* October Calendar */}
          <div style={{ marginBottom: '32px' }}>
            <div style={{
              fontSize: '16px',
              fontWeight: '600',
              color: '#1A1A1A',
              marginBottom: '16px',
            }}>
              {monthNames[currentMonth.getMonth()]}
            </div>
            <div style={{
              display: 'grid',
              gridTemplateColumns: 'repeat(7, 1fr)',
              gap: '0px',
            }}>
              {dayNames.map(day => (
                <div
                  key={day}
                  style={{
                    textAlign: 'center',
                    fontSize: '12px',
                    fontWeight: '500',
                    color: '#6B6B6B',
                    minHeight: '40px',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                  }}
                >
                  {day}
                </div>
              ))}
              {currentMonthDays.map((date, idx) => {
                if (!date) {
                  return <div key={idx} style={{ 
                    minHeight: '40px',
                  }} />;
                }

                const inRange = isInRange(date);
                const selected = isSelected(date);
                const isPast = date < today;
                const isHovered = hoverDate && normalizeDate(date).getTime() === normalizeDate(hoverDate).getTime();
                
                // Determine background color explicitly
                let bgColor = 'transparent';
                if (selected) {
                  bgColor = '#132A53';
                } else if (inRange) {
                  bgColor = '#E8EDF4';
                } else if (!isPast && isHovered) {
                  bgColor = '#F3F4F6';
                }
                
                // Remove border radius from ALL dates in range for seamless appearance
                // Only dates not in range get rounded corners
                const borderRadius = inRange ? '0px' : '8px';

                return (
                  <div
                    key={idx}
                    style={{
                      width: '100%',
                      minHeight: '40px',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      backgroundColor: bgColor,
                      borderRadius: borderRadius,
                    }}
                  >
                    <button
                      onClick={() => !isPast && handleDateClick(date)}
                      onMouseEnter={() => {
                        if (!isPast) {
                          setHoverDate(date);
                        }
                      }}
                      onMouseLeave={() => {
                        setHoverDate(null);
                      }}
                      disabled={isPast}
                      style={{
                        width: '100%',
                        height: '100%',
                        minHeight: '40px',
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        border: 'none',
                        borderRadius: borderRadius,
                        fontSize: '14px',
                        fontWeight: selected ? '600' : '400',
                        cursor: isPast ? 'not-allowed' : 'pointer',
                        backgroundColor: 'transparent',
                        color: selected
                          ? '#FFFFFF'
                          : inRange
                          ? '#1A1A1A'
                          : isPast
                          ? '#D1D5DB'
                          : '#1A1A1A',
                        transition: 'all 0.2s ease',
                        opacity: isPast ? 0.5 : 1,
                      }}
                    >
                      {date.getDate()}
                    </button>
                  </div>
                );
              })}
            </div>
          </div>

          {/* November Calendar */}
          <div>
            <div style={{
              fontSize: '16px',
              fontWeight: '600',
              color: '#1A1A1A',
              marginBottom: '16px',
            }}>
              {monthNames[nextMonth.getMonth()]}
            </div>
            <div style={{
              display: 'grid',
              gridTemplateColumns: 'repeat(7, 1fr)',
              gap: '0px',
            }}>
              {dayNames.map(day => (
                <div
                  key={day}
                  style={{
                    textAlign: 'center',
                    fontSize: '12px',
                    fontWeight: '500',
                    color: '#6B6B6B',
                    minHeight: '40px',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                  }}
                >
                  {day}
                </div>
              ))}
              {nextMonthDays.map((date, idx) => {
                if (!date) {
                  return <div key={idx} style={{ 
                    minHeight: '40px',
                  }} />;
                }

                const inRange = isInRange(date);
                const selected = isSelected(date);
                const isPast = date < today;
                const isHovered = hoverDate && normalizeDate(date).getTime() === normalizeDate(hoverDate).getTime();
                
                // Determine background color explicitly
                let bgColor = 'transparent';
                if (selected) {
                  bgColor = '#132A53';
                } else if (inRange) {
                  bgColor = '#E8EDF4';
                } else if (!isPast && isHovered) {
                  bgColor = '#F3F4F6';
                }
                
                // Remove border radius from ALL dates in range for seamless appearance
                // Only dates not in range get rounded corners
                const borderRadius = inRange ? '0px' : '8px';

                return (
                  <div
                    key={idx}
                    style={{
                      width: '100%',
                      minHeight: '40px',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      backgroundColor: bgColor,
                      borderRadius: borderRadius,
                    }}
                  >
                    <button
                      onClick={() => !isPast && handleDateClick(date)}
                      onMouseEnter={() => {
                        if (!isPast) {
                          setHoverDate(date);
                        }
                      }}
                      onMouseLeave={() => {
                        setHoverDate(null);
                      }}
                      disabled={isPast}
                      style={{
                        width: '100%',
                        height: '100%',
                        minHeight: '40px',
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        border: 'none',
                        borderRadius: borderRadius,
                        fontSize: '14px',
                        fontWeight: selected ? '600' : '400',
                        cursor: isPast ? 'not-allowed' : 'pointer',
                        backgroundColor: 'transparent',
                        color: selected
                          ? '#FFFFFF'
                          : inRange
                          ? '#1A1A1A'
                          : isPast
                          ? '#D1D5DB'
                          : '#1A1A1A',
                        transition: 'all 0.2s ease',
                        opacity: isPast ? 0.5 : 1,
                      }}
                    >
                      {date.getDate()}
                    </button>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      </div>

      {/* Footer */}
      <div style={{
        borderTop: '1px solid #E5E7EB',
        padding: '16px 24px',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        backgroundColor: '#FAFAFA',
      }}>
        <div style={{
          fontSize: '14px',
          color: '#6B6B6B',
        }}>
          {getSelectedDaysCount()} {getSelectedDaysCount() === 1 ? 'day' : 'days'} selected
        </div>
        <div style={{
          display: 'flex',
          gap: '12px',
        }}>
          <button
            onClick={handleClear}
            style={{
              padding: '8px 16px',
              backgroundColor: '#FFFFFF',
              color: '#132A53',
              border: '1px solid #132A53',
              borderRadius: '6px',
              fontSize: '14px',
              fontWeight: '500',
              cursor: 'pointer',
              transition: 'all 0.2s ease',
            }}
            onMouseEnter={(e) => {
              e.currentTarget.style.backgroundColor = '#E8EDF4';
            }}
            onMouseLeave={(e) => {
              e.currentTarget.style.backgroundColor = '#FFFFFF';
            }}
          >
            Clear date
          </button>
          <button
            onClick={handleSelectDates}
            disabled={!selectedStart || !selectedEnd}
            style={{
              padding: '8px 16px',
              backgroundColor: selectedStart && selectedEnd ? '#132A53' : '#E5E7EB',
              color: selectedStart && selectedEnd ? '#FFFFFF' : '#9CA3AF',
              border: 'none',
              borderRadius: '6px',
              fontSize: '14px',
              fontWeight: '500',
              cursor: selectedStart && selectedEnd ? 'pointer' : 'not-allowed',
              transition: 'all 0.2s ease',
            }}
            onMouseEnter={(e) => {
              if (selectedStart && selectedEnd) {
                e.currentTarget.style.backgroundColor = '#0F2140';
              }
            }}
            onMouseLeave={(e) => {
              if (selectedStart && selectedEnd) {
                e.currentTarget.style.backgroundColor = '#132A53';
              }
            }}
          >
            Select dates
          </button>
        </div>
      </div>
    </div>
  );
}

Impact

Lighthouse transformed how organizations manage population health by consolidating testing coordination, compliance tracking, and result management into a single, intuitive platform. By providing real-time visibility into testing status and results, Lighthouse helped productions prevent costly outbreaks through proactive monitoring, respond quickly when positive cases emerged to minimize shutdown risk, reduce administrative burden and eliminate manual tracking processes, and optimize COVID compliance spending by streamlining testing coordination.

For an industry where a single outbreak could cost hundreds of thousands of dollars and halt production for weeks, Lighthouse delivered the visibility and control needed to keep sets safe and productions on schedule.