|
@@ -78,6 +78,23 @@ const TIME_OPTIONS = [
|
|
|
'20:00', '20:30', '21:00', '21:30', '22:00', '22:30', '23:00', '23:30'
|
|
'20:00', '20:30', '21:00', '21:30', '22:00', '22:30', '23:00', '23:30'
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
+// Helper to convert time string to minutes for comparison
|
|
|
|
|
+const timeToMinutes = (time: string): number => {
|
|
|
|
|
+ const [hours, minutes] = time.split(':').map(Number);
|
|
|
|
|
+ return hours * 60 + minutes;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// Helper to check if closing time is after opening time
|
|
|
|
|
+const isValidTimeRange = (openTime: string, closeTime: string): boolean => {
|
|
|
|
|
+ return timeToMinutes(closeTime) > timeToMinutes(openTime);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// Get valid closing time options (only times after opening time)
|
|
|
|
|
+const getValidClosingTimes = (openTime: string): string[] => {
|
|
|
|
|
+ const openMinutes = timeToMinutes(openTime);
|
|
|
|
|
+ return TIME_OPTIONS.filter(time => timeToMinutes(time) > openMinutes);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
const DEFAULT_DAILY_HOURS: DailyHours = {
|
|
const DEFAULT_DAILY_HOURS: DailyHours = {
|
|
|
monday: { is_open: true, open: '09:00', close: '18:00' },
|
|
monday: { is_open: true, open: '09:00', close: '18:00' },
|
|
|
tuesday: { is_open: true, open: '09:00', close: '18:00' },
|
|
tuesday: { is_open: true, open: '09:00', close: '18:00' },
|
|
@@ -406,6 +423,16 @@ export function AIConfigContent() {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Validate time range if not closed
|
|
|
|
|
+ if (!newSpecialIsClosed && !isValidTimeRange(newSpecialOpeningTime, newSpecialClosingTime)) {
|
|
|
|
|
+ toast({
|
|
|
|
|
+ title: t('common.error'),
|
|
|
|
|
+ description: t('aiConfig.businessHours.invalidTimeRange', 'Closing time must be after opening time'),
|
|
|
|
|
+ variant: "destructive"
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
const newEntry: Omit<SpecialHours, 'id'> = {
|
|
const newEntry: Omit<SpecialHours, 'id'> = {
|
|
|
store_id: selectedShop.id,
|
|
store_id: selectedShop.id,
|
|
@@ -479,6 +506,19 @@ export function AIConfigContent() {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // Validate all business hours time ranges
|
|
|
|
|
+ const validateBusinessHours = (): { isValid: boolean; invalidDay?: string } => {
|
|
|
|
|
+ if (!businessHours.is_enabled) return { isValid: true };
|
|
|
|
|
+
|
|
|
|
|
+ for (const day of WEEKDAYS) {
|
|
|
|
|
+ const dayHours = businessHours.daily_hours[day];
|
|
|
|
|
+ if (dayHours.is_open && !isValidTimeRange(dayHours.open, dayHours.close)) {
|
|
|
|
|
+ return { isValid: false, invalidDay: day };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return { isValid: true };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const handleSaveConfig = async () => {
|
|
const handleSaveConfig = async () => {
|
|
|
if (!selectedShop) return;
|
|
if (!selectedShop) return;
|
|
|
|
|
|
|
@@ -492,6 +532,20 @@ export function AIConfigContent() {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Validate business hours time ranges
|
|
|
|
|
+ const hoursValidation = validateBusinessHours();
|
|
|
|
|
+ if (!hoursValidation.isValid) {
|
|
|
|
|
+ const dayName = hoursValidation.invalidDay
|
|
|
|
|
+ ? t(`aiConfig.businessHours.days.${hoursValidation.invalidDay}`, hoursValidation.invalidDay)
|
|
|
|
|
+ : '';
|
|
|
|
|
+ toast({
|
|
|
|
|
+ title: t('common.error'),
|
|
|
|
|
+ description: t('aiConfig.businessHours.invalidTimeRangeDay', { day: dayName }) || `Invalid time range for ${dayName}: closing time must be after opening time`,
|
|
|
|
|
+ variant: "destructive"
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Validate transfer phone number before saving
|
|
// Validate transfer phone number before saving
|
|
|
const isTransferPhoneValid = await validateTransferPhone(aiConfig.transfer_phone_number);
|
|
const isTransferPhoneValid = await validateTransferPhone(aiConfig.transfer_phone_number);
|
|
|
if (!isTransferPhoneValid) {
|
|
if (!isTransferPhoneValid) {
|
|
@@ -860,11 +914,16 @@ export function AIConfigContent() {
|
|
|
<Select
|
|
<Select
|
|
|
value={dayHours.open}
|
|
value={dayHours.open}
|
|
|
onValueChange={(value) => {
|
|
onValueChange={(value) => {
|
|
|
|
|
+ // Auto-adjust closing time if it would be invalid
|
|
|
|
|
+ const validClosingTimes = getValidClosingTimes(value);
|
|
|
|
|
+ const newClose = validClosingTimes.includes(dayHours.close)
|
|
|
|
|
+ ? dayHours.close
|
|
|
|
|
+ : validClosingTimes[0] || '23:30';
|
|
|
setBusinessHours({
|
|
setBusinessHours({
|
|
|
...businessHours,
|
|
...businessHours,
|
|
|
daily_hours: {
|
|
daily_hours: {
|
|
|
...businessHours.daily_hours,
|
|
...businessHours.daily_hours,
|
|
|
- [day]: { ...dayHours, open: value }
|
|
|
|
|
|
|
+ [day]: { ...dayHours, open: value, close: newClose }
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}}
|
|
}}
|
|
@@ -873,13 +932,13 @@ export function AIConfigContent() {
|
|
|
<SelectValue />
|
|
<SelectValue />
|
|
|
</SelectTrigger>
|
|
</SelectTrigger>
|
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
|
- {TIME_OPTIONS.map((time) => (
|
|
|
|
|
|
|
+ {TIME_OPTIONS.slice(0, -1).map((time) => (
|
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
|
))}
|
|
))}
|
|
|
</SelectContent>
|
|
</SelectContent>
|
|
|
</Select>
|
|
</Select>
|
|
|
<span className="text-slate-400">-</span>
|
|
<span className="text-slate-400">-</span>
|
|
|
- {/* Closing Time */}
|
|
|
|
|
|
|
+ {/* Closing Time - only show times after opening time */}
|
|
|
<Select
|
|
<Select
|
|
|
value={dayHours.close}
|
|
value={dayHours.close}
|
|
|
onValueChange={(value) => {
|
|
onValueChange={(value) => {
|
|
@@ -896,7 +955,7 @@ export function AIConfigContent() {
|
|
|
<SelectValue />
|
|
<SelectValue />
|
|
|
</SelectTrigger>
|
|
</SelectTrigger>
|
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
|
- {TIME_OPTIONS.map((time) => (
|
|
|
|
|
|
|
+ {getValidClosingTimes(dayHours.open).map((time) => (
|
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
|
))}
|
|
))}
|
|
|
</SelectContent>
|
|
</SelectContent>
|
|
@@ -970,13 +1029,20 @@ export function AIConfigContent() {
|
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex items-center gap-2">
|
|
|
<Select
|
|
<Select
|
|
|
value={newSpecialOpeningTime}
|
|
value={newSpecialOpeningTime}
|
|
|
- onValueChange={setNewSpecialOpeningTime}
|
|
|
|
|
|
|
+ onValueChange={(value) => {
|
|
|
|
|
+ setNewSpecialOpeningTime(value);
|
|
|
|
|
+ // Auto-adjust closing time if it would be invalid
|
|
|
|
|
+ const validClosingTimes = getValidClosingTimes(value);
|
|
|
|
|
+ if (!validClosingTimes.includes(newSpecialClosingTime)) {
|
|
|
|
|
+ setNewSpecialClosingTime(validClosingTimes[0] || '23:30');
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
>
|
|
>
|
|
|
<SelectTrigger className="bg-slate-700 border-slate-600 text-white w-[85px]">
|
|
<SelectTrigger className="bg-slate-700 border-slate-600 text-white w-[85px]">
|
|
|
<SelectValue />
|
|
<SelectValue />
|
|
|
</SelectTrigger>
|
|
</SelectTrigger>
|
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
|
- {TIME_OPTIONS.map((time) => (
|
|
|
|
|
|
|
+ {TIME_OPTIONS.slice(0, -1).map((time) => (
|
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
|
))}
|
|
))}
|
|
|
</SelectContent>
|
|
</SelectContent>
|
|
@@ -990,7 +1056,7 @@ export function AIConfigContent() {
|
|
|
<SelectValue />
|
|
<SelectValue />
|
|
|
</SelectTrigger>
|
|
</SelectTrigger>
|
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
<SelectContent className="bg-slate-700 border-slate-600 max-h-[200px]">
|
|
|
- {TIME_OPTIONS.map((time) => (
|
|
|
|
|
|
|
+ {getValidClosingTimes(newSpecialOpeningTime).map((time) => (
|
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
|
))}
|
|
))}
|
|
|
</SelectContent>
|
|
</SelectContent>
|