Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]

### Added

- Lite mode local-only workflow for event setup and dispatch, including browser-local persistence without Firebase sync.
- Lite dispatch navbar controls for Posting Schedule, Clear Event, and Export Summary actions.

### Changed

- Unified Lite and Cloud dispatch onto a shared dispatch UI flow to reduce duplication and keep feature parity.
- Lite dispatch navbar behavior now mirrors main app navbar behavior (clock placement, auth controls, desktop/mobile parity).
- Lite dispatch route now uses a lightweight wrapper that delegates to the shared dispatch page.

### Fixed

- Resolved Next.js route export/type issues on dispatch pages that could fail production builds.
- Cleared lint/type build blockers across dispatch, venue management, profile, and modal components.
- Updated venue map icon rendering to satisfy Next.js image lint requirements.

- Suppressed non-actionable React hydration mismatch warnings in development when browser extensions inject attributes on root HTML/body before client hydration.

---
Expand Down
8 changes: 8 additions & 0 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ This document is a short, user-focused guide to the primary workflows in CrowdCA
4. Create or join a Team/Unit assigned to the Event.
5. Use the Dispatch interface to log calls, assign teams, and close incidents.

## Lite mode (local-only)

- Open `/lite` to use CrowdCAD Lite without cloud sync.
- Lite mode stores data in your browser (IndexedDB/local storage) on the current device.
- In Lite Event Setup, you can add/edit locations, equipment, and teams before starting dispatch.
- Teams can be edited from the Teams panel using the pencil icon.
- Use this mode for quick local workflows when internet or account access is unavailable.

## Basic concepts

- Event: an organized occurrence (concert, festival, sports match) with its own roster, venues and logs.
Expand Down
20 changes: 4 additions & 16 deletions src/app/(main)/events/[eventId]/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import { useRouter, useParams } from 'next/navigation';
import { db } from '@/app/firebase';
import { doc, getDoc, updateDoc, deleteDoc, collection, addDoc } from 'firebase/firestore';
import { doc, getDoc, updateDoc, collection, addDoc } from 'firebase/firestore';
import React, { useEffect, useRef, useState } from 'react';
import { Event, Venue, Staff, Supervisor, Post, Equipment, EventEquipment } from '@/app/types';
import { Event, Venue, Staff, Supervisor, Post, EventEquipment } from '@/app/types';
import { getAuth } from 'firebase/auth';
import Image from 'next/image';
import { Tabs, Tab, Input, DatePicker, Select, SelectItem, Checkbox, Button, Card, ScrollShadow, Chip, TimeInput } from '@heroui/react';
Expand All @@ -19,11 +19,6 @@ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';

const LICENSES = ['CPR', 'EMT-B', 'EMT-A', 'EMT-P', 'RN', 'MD/DO'];

// Helper to check if Post is an object with name property
const isPostObject = (post: Post): post is { name: string; x: number | null; y: number | null } => {
return typeof post === 'object' && post !== null && 'name' in post;
};

// Helper to get post name regardless of type
const getPostName = (post: Post): string => {
return typeof post === 'string' ? post : post.name;
Expand Down Expand Up @@ -56,8 +51,8 @@ export default function EventCreation() {

const containerRef = useRef<HTMLDivElement>(null);
const imgContainerRef = useRef<HTMLDivElement>(null);
const [containerSize, setContainerSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });
const [naturalSize, setNaturalSize] = useState<{ width: number; height: number } | null>(null);
const [, setContainerSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });
const [, setNaturalSize] = useState<{ width: number; height: number } | null>(null);
const imgRef = useRef<HTMLImageElement>(null);
const submittedRef = useRef(false);

Expand Down Expand Up @@ -149,10 +144,8 @@ export default function EventCreation() {
try {
const docRef = doc(db, 'events', eventId);
await updateDoc(docRef, stripUndefined({ postingTimes: times }));
// eslint-disable-next-line no-console
console.log('Autosaved postingTimes to draft:', { eventId, postingTimes: times });
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to autosave postingTimes:', err);
}
}, 600);
Expand Down Expand Up @@ -402,7 +395,6 @@ export default function EventCreation() {
};

const computedTimes = computePostingTimes();
// eslint-disable-next-line no-console
console.log('handleSubmit computed postingTimes:', computedTimes, 'eventData.postingTimes:', eventData.postingTimes);

let eventDocId = eventId;
Expand All @@ -419,7 +411,6 @@ export default function EventCreation() {
updatedAt: new Date().toISOString(),
status: 'active',
}));
// eslint-disable-next-line no-console
console.log('Event updated:', { eventId: eventDocId, postingTimes: eventData.postingTimes || [] });
} else {
const newDocRef = await addDoc(collection(db, 'events'), stripUndefined({
Expand All @@ -431,7 +422,6 @@ export default function EventCreation() {
status: 'active',
}));
eventDocId = newDocRef.id;
// eslint-disable-next-line no-console
console.log('Event created (branch new):', { eventId: eventDocId, postingTimes: eventData.postingTimes || [] });
}
} catch (error) {
Expand All @@ -444,7 +434,6 @@ export default function EventCreation() {
status: 'active',
}));
eventDocId = newDocRef.id;
// eslint-disable-next-line no-console
console.log('Event created (catch):', { eventId: eventDocId, postingTimes: eventData.postingTimes || [] });
}
} else {
Expand All @@ -456,7 +445,6 @@ export default function EventCreation() {
status: 'active',
}));
eventDocId = docRef.id;
// eslint-disable-next-line no-console
console.log('Event created (no eventId):', { eventId: eventDocId, postingTimes: eventData.postingTimes || [] });
}
router.push(`/events/${eventDocId}/dispatch`);
Expand Down
Loading
Loading