import React, {FC, useCallback, useEffect, useLayoutEffect, useRef} from "react"; import {MultiSelect} from "../fields/MultiSelect"; import {pad, range} from "lodash"; import {__, CouponsPlus} from "../../globals"; import {MultiSelectSearch} from "../fields/MultiSelectSearch"; import {TimeField} from "../fields/TimeField"; import {EmulatedButton} from "../EmulatedButton"; import {useOpenSelectMenuPopupWindow} from "../useOpenSelectMenuPopupWindow"; import {Tabs} from "../fields/tabs"; import {CustomAttributeOption} from "./AttributeFields"; import {SingleFieldRenderer} from "../../Fields"; import classNames from "classnames"; export type TimeSlot = { days: number[]; // [0 is sunday, 1 is monday, ..., 6 is saturday] time: { type: 'hours' | 'range'; value: string[] | [string, string]; // e.g., ['00:00', '10:00'] for range or [0, 10, 18] for hours // this isn't imported or exported and is for use on the ui only because switching types expect a different value format, so we need to keep both values in the state to avoid losing or corrupting data when switching between types. __local: { value_hours: string[]; value_range: [string, string]; open_days_popup_on_mount?: boolean; } }; } export type TimeSlotProps = { timeSlot: TimeSlot, onAddDay: (day: number) => void, onRemoveDay: (day: number) => void, onTimeTypeChange: (type: 'hours' | 'range') => void, onTimeValueChange: (value: [string, string] | string[]) => void, onDaysChange?: (days: number[]) => void, autoOpenDaysPopupOnMount?: boolean, onAutoOpenDaysPopupHandled?: () => void, } export function getCarbonWeekdayLabel(day: number): string { const sundayFirstDaysList = CouponsPlus?.time?.daysListSundayFirst; if (Array.isArray(sundayFirstDaysList) && sundayFirstDaysList.length === 7) { return sundayFirstDaysList[day] ?? ''; } return CouponsPlus?.time?.daysList?.[(day + 6) % 7] ?? ''; } function getDayOptions(days: number[] = range(7)): { id: string, label: string; value: string }[] { return days.map(day => ({ id: day.toString(), label: getCarbonWeekdayLabel(day), value: day.toString() })) } function getTimeTypeOptions(): { id: string, label: string; value: string }[] { return [ {id: 'range', label: 'Range', value: 'range'}, {id: 'hours', label: 'Hours', value: 'hours'}, ] } function timeForRange(value: undefined | any[] | [string, string]): [string, string] { if (!Array.isArray(value) || value.length < 2) { return ['00:00', '23:59']; // Default to full day if no value is provided } return value as [string, string] } export function getHour12Format(hour: number): { hour: number; label: string } { const hour12 = (hour % 12) || 12; const label = hour < 12 ? 'AM' : 'PM'; return { hour: hour12, label }; } function getHourOptions(): { id: string, label: string; value: string }[] { return range(24).map(hour => { const { hour: hour12, label } = getHour12Format(hour); return { id: hour.toString(), // show am or pm label in 12-hour format label: `${hour12} ${label}`, value: hour.toString() } }); } export const TimeSlotField: FC = ({ timeSlot: {days, time}, onAddDay, onRemoveDay, onTimeTypeChange, onTimeValueChange, onDaysChange, autoOpenDaysPopupOnMount, onAutoOpenDaysPopupHandled }) => { const openSelectMenu = useOpenSelectMenuPopupWindow() const shouldAttemptAutoOpenOnMount = useRef(true) const openDaysPopup = useCallback(() => { openSelectMenu({ context: { id: 'time-slot-days', data: { options: getDayOptions(), selectedValues: days.map(String), window: { title: __('Select the days that this time slot will be active for'), description: __('You can select multiple days, and combine this with other time slots to create more specific schedules. For example, you can create one time slot for weekdays, and another for weekends, and set different time ranges for each.') } } }, onClose: ({status, values}) => { if (status === 'success') { const selectedDays = values.map(value => parseInt(value)); onDaysChange?.(selectedDays); } } }) }, [days, onDaysChange, openSelectMenu]) useLayoutEffect(() => { if (!shouldAttemptAutoOpenOnMount.current) { return } shouldAttemptAutoOpenOnMount.current = false if (!autoOpenDaysPopupOnMount) { return } openDaysPopup() onAutoOpenDaysPopupHandled?.() }, [autoOpenDaysPopupOnMount, onAutoOpenDaysPopupHandled, openDaysPopup]) const openHoursPopup = () => { openSelectMenu({ context: { id: 'time-slot-hours', data: { options: getHourOptions(), selectedValues: time.value.map(String), window: { title: __('Select the hours that this time slot will be active for'), description: __('This is useful for creating gaps in hours and discontinuous ranges. Each hour represents a full hour, e.g., 2 PM means from 2:00 until 2:59.') } } }, onClose: ({status, values}) => { if (status === 'success') { const selectedHours = values.map(value => parseInt(value)); onTimeValueChange?.(selectedHours); } } }) } return

{__('Days')}

{!(!!days.length) &&
{__('Select available days for this slot')}
} {!!days.length &&
    {days.sort((day, nextDay) => day - nextDay ).map(day =>
  • {/* lets render the days by name from 0 index */} {getCarbonWeekdayLabel(day) || 'day not found'}
  • )}
}
{/*here the tabs for hours or range*/} { onTimeTypeChange(id as 'hours' | 'range') }} selectedTabId={time.type} connectedToThe={'bottom'} /> {/*here the state/clickable button that changes hours or ranges.*/}
{time.type === 'range' && ([0, 1].map(index => <>

{index === 0? __('From') : __('To')}

{ const otherTime = timeForRange(time.value)[index === 0 ? 1 : 0]; const firstTime = index === 0 ? newTime : otherTime; const secondTime = index === 0 ? otherTime : newTime; onTimeValueChange([ // DO NOT CHANGE THE ORDER OF THESE VALUES firstTime, secondTime ]) }} narrow={true} /> ) )} {time.type === 'hours' && {time.value.length === 0 || time.value.filter(value => typeof value !== 'number').length ?
{__('Select hours for this slot')}
:
    {time.value.sort((a, b) => (typeof a === 'number' && typeof b === 'number') ? a - b : 0).map(hour => { if (typeof hour !== 'number') { return null; } return
  • {getHour12Format(hour).hour} {getHour12Format(hour).label}
  • })}
}
}
return

{__('Days')}

onAddDay(parseInt(id))} options={getDayOptions()} onRemovedValue={({id}) => onRemoveDay(parseInt(id))} />

{__('Time')}

onTimeTypeChange(id as 'hours' | 'range')} selectedValue={time.type} style={'gray'} size={'small'} width={'auto'} /> {time.type === 'range' && ( [0, 1].map(index => { const otherTime = timeForRange(time.value)[index === 0 ? 1 : 0]; const firstTime = index === 0 ? newTime : otherTime; const secondTime = index === 0 ? otherTime : newTime; onTimeValueChange([ // DO NOT CHANGE THE ORDER OF THESE VALUES firstTime, secondTime ]) }} narrow={true} />) )} {time.type === 'hours' && onTimeValueChange([ ...time.value, id ])} onRemovedValue={({id}) => { onTimeValueChange(time.value.filter(hour => hour !== id)) }} /> }
; }