mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 13:24:27 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 } }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 } }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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('')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** @description Home page component for the salon website, featuring hero section, services preview, and testimonials. */
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** @description Privacy policy page component explaining data collection, usage, and user rights. */
|
||||
export default function PrivacyPolicyPage() {
|
||||
return (
|
||||
<div className="section">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** @description Static services page component displaying available salon services and categories. */
|
||||
export default function ServiciosPage() {
|
||||
const services = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user