|
|
|
|
@ -84,3 +84,132 @@ export function calculateEndTime(startTime: string, durationMinutes: number): st
|
|
|
|
|
const endMinutes = totalMinutes % 60;
|
|
|
|
|
return `${String(endHours).padStart(2, '0')}:${String(endMinutes).padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Bulk Operations (Generate & Clone)
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Available presets for generate endpoint
|
|
|
|
|
*/
|
|
|
|
|
export type SlotDefinitionPreset =
|
|
|
|
|
| 'workday_standard' // Mon-Fri, 8am-10pm, 90min
|
|
|
|
|
| 'weekend_extended' // Sat-Sun, 7am-11pm, 90min
|
|
|
|
|
| 'all_week_uniform' // Every day, 8am-10pm, 90min
|
|
|
|
|
| 'hourly_daytime'; // Mon-Fri, 9am-6pm, 60min
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Preset metadata for UI display
|
|
|
|
|
*/
|
|
|
|
|
export interface PresetInfo {
|
|
|
|
|
id: SlotDefinitionPreset;
|
|
|
|
|
name: string;
|
|
|
|
|
description: string;
|
|
|
|
|
days: string;
|
|
|
|
|
hours: string;
|
|
|
|
|
duration: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const PRESET_OPTIONS: PresetInfo[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 'workday_standard',
|
|
|
|
|
name: 'Weekday Standard',
|
|
|
|
|
description: 'Standard weekday operations',
|
|
|
|
|
days: 'Mon-Fri',
|
|
|
|
|
hours: '8am-10pm',
|
|
|
|
|
duration: '90min slots',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'weekend_extended',
|
|
|
|
|
name: 'Weekend Extended',
|
|
|
|
|
description: 'Weekend with early/late slots',
|
|
|
|
|
days: 'Sat-Sun',
|
|
|
|
|
hours: '7am-11pm',
|
|
|
|
|
duration: '90min slots',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'all_week_uniform',
|
|
|
|
|
name: 'All Week Uniform',
|
|
|
|
|
description: 'Uniform schedule year-round',
|
|
|
|
|
days: 'Every day',
|
|
|
|
|
hours: '8am-10pm',
|
|
|
|
|
duration: '90min slots',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'hourly_daytime',
|
|
|
|
|
name: 'Hourly Daytime',
|
|
|
|
|
description: 'Short slots for busy periods',
|
|
|
|
|
days: 'Mon-Fri',
|
|
|
|
|
hours: '9am-6pm',
|
|
|
|
|
duration: '60min slots',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pattern overrides for customizing presets
|
|
|
|
|
*/
|
|
|
|
|
export interface PatternOverrides {
|
|
|
|
|
days?: DayOfWeek[];
|
|
|
|
|
start_time?: string; // HH:MM:SS
|
|
|
|
|
end_time?: string; // HH:MM:SS
|
|
|
|
|
duration_minutes?: number;
|
|
|
|
|
interval_minutes?: number;
|
|
|
|
|
capacity?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate endpoint request
|
|
|
|
|
*/
|
|
|
|
|
export interface GenerateSlotDefinitionsRequest {
|
|
|
|
|
preset: SlotDefinitionPreset;
|
|
|
|
|
court_ids: number[];
|
|
|
|
|
pattern_overrides?: PatternOverrides;
|
|
|
|
|
valid_from?: string; // YYYY-MM-DD
|
|
|
|
|
valid_to?: string; // YYYY-MM-DD
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Skipped slot info
|
|
|
|
|
*/
|
|
|
|
|
export interface SkippedSlot {
|
|
|
|
|
court_id: number;
|
|
|
|
|
dow: DayOfWeek;
|
|
|
|
|
starts_at: string;
|
|
|
|
|
reason: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate endpoint response
|
|
|
|
|
*/
|
|
|
|
|
export interface GenerateSlotDefinitionsResponse {
|
|
|
|
|
status: 'success';
|
|
|
|
|
data: {
|
|
|
|
|
created_count: number;
|
|
|
|
|
skipped_count?: number;
|
|
|
|
|
created_definitions: SlotDefinition[];
|
|
|
|
|
skipped?: SkippedSlot[];
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clone endpoint request
|
|
|
|
|
*/
|
|
|
|
|
export interface CloneSlotDefinitionRequest {
|
|
|
|
|
target_court_ids: number[];
|
|
|
|
|
target_days?: DayOfWeek[];
|
|
|
|
|
valid_from?: string; // YYYY-MM-DD
|
|
|
|
|
valid_to?: string; // YYYY-MM-DD
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clone endpoint response
|
|
|
|
|
*/
|
|
|
|
|
export interface CloneSlotDefinitionResponse {
|
|
|
|
|
status: 'success';
|
|
|
|
|
data: {
|
|
|
|
|
created_count: number;
|
|
|
|
|
skipped_count?: number;
|
|
|
|
|
created_definitions: SlotDefinition[];
|
|
|
|
|
skipped?: SkippedSlot[];
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|