docs: add comprehensive code comments, update README and TASKS, create training and troubleshooting guides

- Add JSDoc comments to API routes and business logic functions
- Update README.md with Phase 2 status and deployment/production notes
- Enhance TASKS.md with estimated timelines and dependencies
- Create docs/STAFF_TRAINING.md for team onboarding
- Create docs/CLIENT_ONBOARDING.md for customer experience
- Create docs/OPERATIONAL_PROCEDURES.md for daily operations
- Create docs/TROUBLESHOOTING.md for common setup issues
- Fix TypeScript errors in hq/page.tsx
This commit is contained in:
Marco Gallegos
2026-01-16 18:42:45 -06:00
parent 28e98a2a44
commit 8fc9d3717e
63 changed files with 973 additions and 101 deletions

View File

@@ -8,6 +8,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
/** @description Admin enrollment system component for creating and managing staff members and kiosk devices. */
export default function EnrollmentPage() {
const [adminKey, setAdminKey] = useState('')
const [isAuthenticated, setIsAuthenticated] = useState(false)

View File

@@ -9,6 +9,7 @@ import { format } from 'date-fns'
import { es } from 'date-fns/locale'
import { useAuth } from '@/lib/auth/context'
/** @description Admin dashboard component for managing salon operations including bookings, staff, resources, reports, and permissions. */
export default function ApertureDashboard() {
const { user, loading: authLoading, signOut } = useAuth()
const router = useRouter()

View File

@@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) {
return true
}
/**
* @description Retrieves kiosks with filters for admin
*/
export async function GET(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)
@@ -77,6 +80,9 @@ export async function GET(request: NextRequest) {
}
}
/**
* @description Creates a new kiosk
*/
export async function POST(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)

View File

@@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) {
return true
}
/**
* @description Retrieves all locations for admin
*/
export async function GET(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)

View File

@@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) {
return true
}
/**
* @description Retrieves staff users with filters for admin
*/
export async function GET(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)
@@ -78,6 +81,9 @@ export async function GET(request: NextRequest) {
}
}
/**
* @description Creates a new staff user
*/
export async function POST(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Fetches bookings with filters for dashboard view
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -37,6 +37,9 @@ const mockPermissions = [
}
]
/**
* @description Retrieves permissions data for different roles
*/
export async function GET() {
return NextResponse.json({
success: true,
@@ -44,6 +47,9 @@ export async function GET() {
})
}
/**
* @description Toggles a specific permission for a role
*/
export async function POST(request: NextRequest) {
const { roleId, permId } = await request.json()

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Fetches recent payments report
*/
export async function GET() {
try {
// Get recent payments (assuming bookings with payment_intent_id are paid)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Fetches payroll report for staff based on recent bookings
*/
export async function GET() {
try {
// Get staff and their bookings this week

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Fetches sales report including total sales, completed bookings, average service price, and sales by service
*/
export async function GET() {
try {
// Get total sales

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Retrieves active resources, optionally filtered by location
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Gets available staff for a location and date
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Retrieves staff availability schedule with optional filters
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
@@ -60,6 +63,9 @@ export async function GET(request: NextRequest) {
}
}
/**
* @description Creates or updates staff availability
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json()
@@ -145,6 +151,9 @@ export async function POST(request: NextRequest) {
}
}
/**
* @description Deletes staff availability by ID
*/
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) {
return true
}
/**
* @description Creates a booking block for a resource
*/
export async function POST(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)
@@ -76,6 +79,9 @@ export async function POST(request: NextRequest) {
}
}
/**
* @description Retrieves booking blocks with filters
*/
export async function GET(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)
@@ -151,6 +157,9 @@ export async function GET(request: NextRequest) {
}
}
/**
* @description Deletes a booking block by ID
*/
export async function DELETE(request: NextRequest) {
try {
const isAdmin = await validateAdmin(request)

View File

@@ -17,6 +17,9 @@ async function validateAdminOrStaff(request: NextRequest) {
return true
}
/**
* @description Marks staff as unavailable for a time period
*/
export async function POST(request: NextRequest) {
try {
const hasAccess = await validateAdminOrStaff(request)
@@ -119,6 +122,9 @@ export async function POST(request: NextRequest) {
}
}
/**
* @description Retrieves staff unavailability records
*/
export async function GET(request: NextRequest) {
try {
const hasAccess = await validateAdminOrStaff(request)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Retrieves available staff for a time range
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Retrieves detailed availability time slots for a date
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Updates the status of a specific booking
*/
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }

View File

@@ -2,6 +2,9 @@ import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
import { generateShortId } from '@/lib/utils/short-id'
/**
* @description Creates a new booking
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json()
@@ -70,6 +73,7 @@ export async function POST(request: NextRequest) {
const endTimeUtc = endTime.toISOString()
// Check staff availability for the requested time slot
const { data: availableStaff, error: staffError } = await supabaseAdmin.rpc('get_available_staff', {
p_location_id: location_id,
p_start_time_utc: start_time_utc,
@@ -93,6 +97,7 @@ export async function POST(request: NextRequest) {
const assignedStaff = availableStaff[0]
// Check resource availability with service priority
const { data: availableResources, error: resourcesError } = await supabaseAdmin.rpc('get_available_resources_with_priority', {
p_location_id: location_id,
p_start_time: start_time_utc,
@@ -117,6 +122,7 @@ export async function POST(request: NextRequest) {
const assignedResource = availableResources[0]
// Create or find customer based on email
const { data: customer, error: customerError } = await supabaseAdmin
.from('customers')
.upsert({
@@ -141,6 +147,7 @@ export async function POST(request: NextRequest) {
const shortId = await generateShortId()
// Create the booking record with all assigned resources
const { data: booking, error: bookingError } = await supabaseAdmin
.from('bookings')
.insert({
@@ -208,6 +215,9 @@ export async function POST(request: NextRequest) {
}
}
/**
* @description Retrieves bookings with filters
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -4,6 +4,11 @@ import { supabaseAdmin } from '@/lib/supabase/client'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
/**
* @description Creates a Stripe payment intent for booking deposit (50% of service price, max $200)
* @param {NextRequest} request - Request containing booking details
* @returns {NextResponse} Payment intent client secret and amount
*/
export async function POST(request: NextRequest) {
try {
const {

View File

@@ -2,6 +2,9 @@ import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
import { Kiosk } from '@/lib/db/types'
/**
* @description Authenticates a kiosk using API key
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json()

View File

@@ -18,6 +18,9 @@ async function validateKiosk(request: NextRequest) {
return kiosk
}
/**
* @description Confirms a pending booking by short ID for kiosk
*/
export async function POST(
request: NextRequest,
{ params }: { params: { shortId: string } }

View File

@@ -18,6 +18,9 @@ async function validateKiosk(request: NextRequest) {
return kiosk
}
/**
* @description Retrieves pending/confirmed bookings for kiosk
*/
export async function GET(request: NextRequest) {
try {
const kiosk = await validateKiosk(request)
@@ -72,6 +75,9 @@ export async function GET(request: NextRequest) {
}
}
/**
* @description Creates a new booking for kiosk
*/
export async function POST(request: NextRequest) {
try {
const kiosk = await validateKiosk(request)

View File

@@ -18,6 +18,9 @@ async function validateKiosk(request: NextRequest) {
return kiosk
}
/**
* @description Retrieves available resources for kiosk, filtered by time and service
*/
export async function GET(request: NextRequest) {
try {
const kiosk = await validateKiosk(request)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* Validates kiosk API key and returns kiosk info if valid
*/
async function validateKiosk(request: NextRequest) {
const apiKey = request.headers.get('x-kiosk-api-key')
@@ -18,6 +21,9 @@ async function validateKiosk(request: NextRequest) {
return kiosk
}
/**
* @description Creates a walk-in booking for kiosk
*/
export async function POST(request: NextRequest) {
try {
const kiosk = await validateKiosk(request)
@@ -45,6 +51,7 @@ export async function POST(request: NextRequest) {
)
}
// Validate service exists and is active
const { data: service, error: serviceError } = await supabaseAdmin
.from('services')
.select('*')
@@ -75,6 +82,7 @@ export async function POST(request: NextRequest) {
const assignedStaff = availableStaff[0]
// For walk-ins, booking starts immediately
const startTime = new Date()
const endTime = new Date(startTime)
endTime.setMinutes(endTime.getMinutes() + service.duration_minutes)

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Retrieves all active locations
*/
export async function GET(request: NextRequest) {
try {
const { data: locations, error } = await supabaseAdmin

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
/**
* @description Retrieves active services, optionally filtered by location
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)

View File

@@ -12,6 +12,7 @@ import { format } from 'date-fns'
import { es } from 'date-fns/locale'
import { useAuth } from '@/lib/auth/context'
/** @description Booking confirmation and payment page component for completing appointment reservations. */
export default function CitaPage() {
const { user, loading: authLoading } = useAuth()
const router = useRouter()

View File

@@ -7,6 +7,7 @@ import { CheckCircle2, Calendar, Clock, MapPin, User, Mail } from 'lucide-react'
import { format } from 'date-fns'
import { es } from 'date-fns/locale'
/** @description Booking confirmation page component displaying appointment details and important information after successful booking. */
export default function ConfirmacionPage() {
const [bookingDetails, setBookingDetails] = useState<any>(null)
const [loading, setLoading] = useState(true)

View File

@@ -8,6 +8,7 @@ import { Label } from '@/components/ui/label'
import { Mail, CheckCircle } from 'lucide-react'
import { useAuth } from '@/lib/auth/context'
/** @description Login page component for customer authentication using magic link emails. */
export default function LoginPage() {
const { signIn } = useAuth()
const [email, setEmail] = useState('')

View File

@@ -7,6 +7,7 @@ import { Calendar, Clock, MapPin, User, DollarSign } from 'lucide-react'
import { format } from 'date-fns'
import { es } from 'date-fns/locale'
/** @description Customer appointments management page component for viewing and managing existing bookings. */
export default function MisCitasPage() {
const [bookings, setBookings] = useState<any[]>([])
const [loading, setLoading] = useState(false)

View File

@@ -11,6 +11,7 @@ import { format } from 'date-fns'
import { es } from 'date-fns/locale'
import { useAuth } from '@/lib/auth/context'
/** @description Customer profile management page component for viewing and editing personal information and booking history. */
export default function PerfilPage() {
const { user, loading: authLoading } = useAuth()
const router = useRouter()

View File

@@ -3,6 +3,7 @@
import { useState } from 'react'
import { MapPin, Phone, Mail, Clock } from 'lucide-react'
/** @description Contact page component with contact information and contact form for inquiries. */
export default function ContactoPage() {
const [formData, setFormData] = useState({
nombre: '',

View File

@@ -3,6 +3,7 @@
import { useState } from 'react'
import { Building2, Map, CheckCircle, Mail, Phone } from 'lucide-react'
/** @description Franchise information and application page component for potential franchise partners. */
export default function FranchisesPage() {
const [formData, setFormData] = useState({
nombre: '',

View File

@@ -1,3 +1,4 @@
/** @description Company history and philosophy page component explaining the brand's foundation and values. */
export default function HistoriaPage() {
return (
<div className="section">

View File

@@ -10,62 +10,30 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Badge } from '@/components/ui/badge'
import { format } from 'date-fns'
import { Calendar, Clock, MapPin, Users, CheckCircle, XCircle, AlertCircle } from 'lucide-react'
import type { Location, Staff, Booking } from '@/lib/db/types'
interface Booking {
id: string
short_id: string
status: string
start_time_utc: string
end_time_utc: string
notes: string | null
is_paid: boolean
customer: {
id: string
email: string
first_name: string | null
last_name: string | null
phone: string | null
}
service: {
id: string
name: string
duration_minutes: number
base_price: number
}
resource?: {
id: string
name: string
type: string
}
staff: {
id: string
display_name: string
}
location: {
id: string
name: string
}
}
interface Location {
id: string
name: string
}
interface Staff {
type ApiStaff = {
staff_id: string
staff_name: string
role: string
work_hours_start: string | null
work_hours_end: string | null
work_days: string | null
location_id: string
work_hours_start?: string
work_hours_end?: string
}
type ApiBooking = Omit<Booking, 'status'> & {
status: string
service?: any
customer?: any
staff?: any
location?: any
resource?: any
}
/** @description HQ operations dashboard component for managing bookings, staff availability, and location operations. */
export default function HQDashboard() {
const [locations, setLocations] = useState<Location[]>([])
const [staffList, setStaffList] = useState<Staff[]>([])
const [bookings, setBookings] = useState<Booking[]>([])
const [staffList, setStaffList] = useState<ApiStaff[]>([])
const [bookings, setBookings] = useState<ApiBooking[]>([])
const [selectedLocation, setSelectedLocation] = useState<string>('')
const [selectedDate, setSelectedDate] = useState(format(new Date(), 'yyyy-MM-dd'))
const [loading, setLoading] = useState(false)
@@ -117,11 +85,11 @@ export default function HQDashboard() {
})
const data = await response.json()
if (data.bookings) {
const filtered = data.bookings.filter((b: Booking) => {
const filtered = data.bookings.filter((b: any) => {
const bookingDate = new Date(b.start_time_utc)
return bookingDate >= new Date(startDate) && bookingDate < new Date(endDate)
})
setBookings(filtered)
setBookings(filtered as Booking[])
}
} catch (error) {
console.error('Failed to fetch bookings:', error)
@@ -267,14 +235,14 @@ export default function HQDashboard() {
{format(new Date(booking.start_time_utc), 'HH:mm')} - {format(new Date(booking.end_time_utc), 'HH:mm')}
</span>
</div>
<h3 className="font-semibold text-lg">{booking.service.name}</h3>
<h3 className="font-semibold text-lg">{booking.service?.name || 'Service'}</h3>
<p className="text-sm text-gray-600 mt-1">
{booking.customer.first_name} {booking.customer.last_name} ({booking.customer.email})
{booking.customer?.first_name} {booking.customer?.last_name} ({booking.customer?.email})
</p>
<div className="flex items-center gap-4 mt-2 text-sm text-gray-500">
<div className="flex items-center gap-1">
<Users className="w-4 h-4" />
{booking.staff.display_name}
{booking.staff?.display_name || 'Staff'}
</div>
<div className="flex items-center gap-1">
<MapPin className="w-4 h-4" />
@@ -289,10 +257,10 @@ export default function HQDashboard() {
</div>
<div className="text-right">
<p className="text-lg font-semibold">
${booking.service.base_price.toFixed(2)}
${booking.service?.base_price?.toFixed(2) || '0.00'}
</p>
<p className="text-xs text-gray-500">
{booking.service.duration_minutes} min
<p className="text-sm text-gray-500">
{booking.service?.duration_minutes || 0} min
</p>
</div>
</div>
@@ -329,9 +297,9 @@ export default function HQDashboard() {
</Badge>
{getStatusBadge(booking.status)}
</div>
<h3 className="font-semibold">{booking.service.name}</h3>
<h3 className="font-semibold">{booking.service?.name || 'Service'}</h3>
<p className="text-sm text-gray-600">
{booking.customer.first_name} {booking.customer.last_name}
{booking.customer?.first_name} {booking.customer?.last_name}
</p>
</div>
<div className="text-right">
@@ -339,7 +307,7 @@ export default function HQDashboard() {
{format(new Date(booking.start_time_utc), 'HH:mm')}
</p>
<p className="text-sm text-gray-500">
{booking.staff.display_name}
{booking.staff?.display_name || 'Staff'}
</p>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import { BookingConfirmation } from '@/components/kiosk/BookingConfirmation'
import { WalkInFlow } from '@/components/kiosk/WalkInFlow'
import { Calendar, UserPlus, MapPin, Clock } from 'lucide-react'
/** @description Kiosk interface component for location-based check-in confirmations and walk-in booking creation. */
export default function KioskPage({ params }: { params: { locationId: string } }) {
const [apiKey, setApiKey] = useState<string | null>(null)
const [location, setLocation] = useState<any>(null)

View File

@@ -1,3 +1,4 @@
/** @description Legal terms and conditions page component outlining salon policies and user agreements. */
export default function LegalPage() {
return (
<div className="section">

View File

@@ -3,6 +3,7 @@
import { useState } from 'react'
import { Crown, Star, Award, Diamond } from 'lucide-react'
/** @description Membership tiers page component displaying exclusive membership options and application forms. */
export default function MembresiasPage() {
const [selectedTier, setSelectedTier] = useState<string | null>(null)
const [formData, setFormData] = useState({

View File

@@ -1,3 +1,4 @@
/** @description Home page component for the salon website, featuring hero section, services preview, and testimonials. */
export default function HomePage() {
return (
<>

View File

@@ -1,3 +1,4 @@
/** @description Privacy policy page component explaining data collection, usage, and user rights. */
export default function PrivacyPolicyPage() {
return (
<div className="section">

View File

@@ -1,3 +1,4 @@
/** @description Static services page component displaying available salon services and categories. */
export default function ServiciosPage() {
const services = [
{