Add lessons module, rate cycles, EC2 deploy scripts, and help content
- Lessons module: lesson types, instructors, schedule slots, enrollments, sessions (list + week grid view), lesson plans, grading scales, templates - Rate cycles: replace monthly_rate with billing_interval + billing_unit on enrollments; add weekly/monthly/quarterly rate presets to lesson types and schedule slots with auto-fill on enrollment form - Member detail page: tabbed layout for details, identity documents, enrollments - Sessions week view: custom 7-column grid replacing react-big-calendar - Music store seed: instructors, lesson types, slots, enrollments, sessions, grading scale, lesson plan template - Scrollbar styling: themed to match sidebar/app palette - deploy/: EC2 setup and redeploy scripts, nginx config, systemd service - Help: add Lessons category (overview, types, instructors, slots, enrollments, sessions, plans/grading); collapsible sidebar with independent scroll; remove POS/accounting references from docs
This commit is contained in:
124
packages/admin/src/components/lessons/schedule-slot-form.tsx
Normal file
124
packages/admin/src/components/lessons/schedule-slot-form.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import type { LessonType, ScheduleSlot } from '@/types/lesson'
|
||||
|
||||
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
||||
|
||||
interface Props {
|
||||
lessonTypes: LessonType[]
|
||||
defaultValues?: Partial<ScheduleSlot>
|
||||
onSubmit: (data: Record<string, unknown>) => void
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function ScheduleSlotForm({ lessonTypes, defaultValues, onSubmit, loading }: Props) {
|
||||
const { register, handleSubmit, setValue, watch } = useForm({
|
||||
defaultValues: {
|
||||
dayOfWeek: String(defaultValues?.dayOfWeek ?? 1),
|
||||
startTime: defaultValues?.startTime ?? '',
|
||||
lessonTypeId: defaultValues?.lessonTypeId ?? '',
|
||||
room: defaultValues?.room ?? '',
|
||||
maxStudents: String(defaultValues?.maxStudents ?? 1),
|
||||
rateWeekly: defaultValues?.rateWeekly ?? '',
|
||||
rateMonthly: defaultValues?.rateMonthly ?? '',
|
||||
rateQuarterly: defaultValues?.rateQuarterly ?? '',
|
||||
},
|
||||
})
|
||||
|
||||
const dayOfWeek = watch('dayOfWeek')
|
||||
const lessonTypeId = watch('lessonTypeId')
|
||||
|
||||
function handleFormSubmit(data: {
|
||||
dayOfWeek: string
|
||||
startTime: string
|
||||
lessonTypeId: string
|
||||
room: string
|
||||
maxStudents: string
|
||||
rateWeekly: string
|
||||
rateMonthly: string
|
||||
rateQuarterly: string
|
||||
}) {
|
||||
onSubmit({
|
||||
dayOfWeek: Number(data.dayOfWeek),
|
||||
startTime: data.startTime,
|
||||
lessonTypeId: data.lessonTypeId,
|
||||
room: data.room || undefined,
|
||||
maxStudents: Number(data.maxStudents) || 1,
|
||||
rateWeekly: data.rateWeekly || undefined,
|
||||
rateMonthly: data.rateMonthly || undefined,
|
||||
rateQuarterly: data.rateQuarterly || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Day *</Label>
|
||||
<Select value={dayOfWeek} onValueChange={(v) => setValue('dayOfWeek', v)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{DAYS.map((day, i) => (
|
||||
<SelectItem key={i} value={String(i)}>{day}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="slot-time">Start Time *</Label>
|
||||
<Input id="slot-time" type="time" {...register('startTime')} required />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Lesson Type *</Label>
|
||||
<Select value={lessonTypeId} onValueChange={(v) => setValue('lessonTypeId', v)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select lesson type..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{lessonTypes.map((lt) => (
|
||||
<SelectItem key={lt.id} value={lt.id}>
|
||||
{lt.name} ({lt.durationMinutes} min)
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="slot-room">Room</Label>
|
||||
<Input id="slot-room" {...register('room')} placeholder="e.g. Studio A" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="slot-max">Max Students</Label>
|
||||
<Input id="slot-max" type="number" min={1} {...register('maxStudents')} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="block mb-2">Instructor Rates (override lesson type defaults)</Label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="slot-rate-weekly" className="text-xs text-muted-foreground">Weekly</Label>
|
||||
<Input id="slot-rate-weekly" type="number" step="0.01" min="0" {...register('rateWeekly')} placeholder="—" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="slot-rate-monthly" className="text-xs text-muted-foreground">Monthly</Label>
|
||||
<Input id="slot-rate-monthly" type="number" step="0.01" min="0" {...register('rateMonthly')} placeholder="—" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="slot-rate-quarterly" className="text-xs text-muted-foreground">Quarterly</Label>
|
||||
<Input id="slot-rate-quarterly" type="number" step="0.01" min="0" {...register('rateQuarterly')} placeholder="—" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" disabled={loading || !lessonTypeId} className="w-full">
|
||||
{loading ? 'Saving...' : defaultValues?.id ? 'Save Changes' : 'Add Slot'}
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user