Skip to content

Commit

Permalink
Merge pull request #76 from avantifellows/feature/date-picker
Browse files Browse the repository at this point in the history
[36] Date Time Fixtures
  • Loading branch information
Drish-xD authored Aug 21, 2024
2 parents 1c85a5e + b3d6eb6 commit 9f96277
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 122 deletions.
4 changes: 4 additions & 0 deletions src/Constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200];
// TABLE POLLING CONSTANTS
export const POLLING_INTERVAL = 30000; // 30 seconds

// Date Picker Allowed Range
export const ALLOWED_YEARS = 5;
export const UTC_IST_OFFSET = 5.5 * 60; // +5:30 (in minutes)

/**
* Keys to be deleted before duplicating a session
* from preventing the fields from being duplicated
Expand Down
22 changes: 15 additions & 7 deletions src/app/session/[type]/Steps/Timeline/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { ActiveDaysOptions } from '@/Constants';
import { ActiveDaysOptions, ALLOWED_YEARS } from '@/Constants';
import { FormBuilder } from '@/components/FormBuilder';
import { useFormContext } from '@/hooks/useFormContext';
import { FieldSchema, Session, timelineFields, timelineSchema } from '@/types';
Expand All @@ -15,15 +15,23 @@ const TimelineForm: FC = () => {
return {
startDate: {
type: 'datetime',
label: 'Start Date And Time',
label: {
date: 'Start Date',
time: 'Start Time',
},
placeholder: 'Select start date and time',
disableRange: (date: Date) => date < startOfToday() || date > addYears(startOfToday(), 1),
disableRange: (date: Date) =>
date < startOfToday() || date > addYears(startOfToday(), ALLOWED_YEARS),
},
endDate: {
type: 'datetime',
label: 'End Date And Time',
label: {
date: 'End Date',
time: 'End Time',
},
placeholder: 'Select end date and time',
disableRange: (date: Date) => date < startOfToday() || date > addYears(startOfToday(), 1),
disableRange: (date: Date) =>
date < startOfToday() || date > addYears(startOfToday(), ALLOWED_YEARS),
},
testTakers: {
type: 'number',
Expand Down Expand Up @@ -65,8 +73,8 @@ const TimelineForm: FC = () => {
params: data.activeDays.sort((a, b) => a - b),
},
is_active: data.isEnabled,
start_time: new Date(data.startDate).toISOString(),
end_time: new Date(data.endDate).toISOString(),
start_time: new Date(data.startDate).toUTCString(),
end_time: new Date(data.endDate).toUTCString(),
};

updateFormData(addedData);
Expand Down
39 changes: 38 additions & 1 deletion src/components/ui/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DayPicker } from 'react-day-picker';

import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';

export type CalendarProps = React.ComponentProps<typeof DayPicker>;

Expand All @@ -18,7 +19,8 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
caption_label: 'hidden',
caption_dropdowns: 'flex items-center justify-center gap-2',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
Expand Down Expand Up @@ -49,6 +51,41 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
Dropdown: ({ name, value, children, onChange }) => {
type OptionElement = React.ReactElement<React.HTMLProps<HTMLOptionElement>>;
const options = React.Children.toArray(children) as OptionElement[];

return (
<Select
value={value?.toString()}
onValueChange={(value) => {
const changeEvent = {
target: {
value,
},
} as React.ChangeEvent<HTMLSelectElement>;
onChange?.(changeEvent);
}}
name={name}
>
<SelectTrigger className="h-fit w-fit border-none p-0 focus:bg-none focus:ring-0 focus:ring-offset-0">
<SelectValue />
</SelectTrigger>
<SelectContent position="popper" side="bottom" align="center" sideOffset={2}>
{options.map((option, index) => {
return (
<SelectItem
key={index}
value={option.props.value?.toString() || index.toString()}
>
{option.props.children}
</SelectItem>
);
})}
</SelectContent>
</Select>
);
},
}}
{...props}
/>
Expand Down
93 changes: 58 additions & 35 deletions src/components/ui/date-time.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Button } from '@/components/ui/button';
import { FormControl, FormLabel } from '@/components/ui/form';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { ALLOWED_YEARS } from '@/Constants';
import { cn } from '@/lib/utils';
import { MyDateTimeProps } from '@/types';
import { format } from 'date-fns';
import { addDays, addYears, format, startOfToday } from 'date-fns';
import { CalendarIcon } from 'lucide-react';
import { ElementRef, forwardRef } from 'react';
import { ControllerRenderProps, UseFormReturn } from 'react-hook-form';
Expand All @@ -17,41 +18,63 @@ const DateTimePicker = forwardRef<
const { value, onChange, ref: refField, ...restFieldProps } = field;
const { label, disabled, disableRange, ...restSchemaProps } = schema;

const handleSelect = (newDay: Date | undefined) => {
if (!newDay) return;
if (!value) {
onChange?.(newDay);
return;
}
const diff = newDay.getTime() - value.getTime();
const diffInDays = diff / (1000 * 60 * 60 * 24);
const newDateFull = addDays(value, Math.ceil(diffInDays));
onChange?.(newDateFull);
};

return (
<div className="flex flex-col gap-4">
<FormLabel className="self-start">{label}</FormLabel>
<Popover>
<FormControl>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
'justify-start text-left font-normal',
!value && 'text-muted-foreground'
)}
ref={refField}
disabled={disabled}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{value ? format(value, 'PPp') : <span>Pick a date and time</span>}
</Button>
</PopoverTrigger>
</FormControl>
<PopoverContent className="w-auto p-0">
<Calendar
{...restSchemaProps}
{...restFieldProps}
mode="single"
selected={value}
onSelect={onChange}
initialFocus
disabled={disableRange}
/>
<div className="p-3 border-t border-border">
<TimePicker setDate={onChange} date={value} />
</div>
</PopoverContent>
</Popover>
<div className="flex flex-col sm:flex-row gap-4 sm:gap-10 sm:items-center">
<div className="flex flex-col gap-4 flex-1">
<FormLabel className="self-start">{label?.date}</FormLabel>
<Popover>
<FormControl>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn('justify-start text-left font-normal')}
ref={refField}
disabled={disabled}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{value ? (
format(value, 'PP')
) : (
<span className="text-muted-foreground/80 italic font-medium">
Pick a date and time
</span>
)}
</Button>
</PopoverTrigger>
</FormControl>
<PopoverContent className="w-auto p-0">
<Calendar
{...restSchemaProps}
{...restFieldProps}
captionLayout="dropdown-buttons"
fromYear={startOfToday().getFullYear()}
toYear={addYears(startOfToday(), ALLOWED_YEARS).getFullYear()}
mode="single"
selected={value}
onSelect={(d) => handleSelect(d)}
onMonthChange={handleSelect}
initialFocus
disabled={disableRange}
/>
</PopoverContent>
</Popover>
</div>
<div className="flex flex-col gap-4 flex-1">
<FormLabel className="self-start">{label?.time}</FormLabel>
<TimePicker setDate={onChange} date={value} />
</div>
</div>
);
});
Expand Down
83 changes: 34 additions & 49 deletions src/components/ui/time-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
Expand All @@ -16,6 +15,7 @@ import {
setDateByType,
} from '@/lib/time-picker-utils';
import { cn } from '@/lib/utils';
import { ClockIcon } from 'lucide-react';
import React from 'react';

export interface TimePickerInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
Expand Down Expand Up @@ -73,7 +73,7 @@ const TimePickerInput = React.forwardRef<HTMLInputElement, TimePickerInputProps>
* If picker is '12hours' and the first digit is 0, then the second digit is automatically set to 1.
* The second entered digit will break the condition and the value will be set to 10-12.
*/
if (picker === '12hours') {
if (picker === 'hours') {
if (flag && calculatedValue.slice(1, 2) === '1' && prevIntKey === '0') return '0' + key;
}

Expand All @@ -93,7 +93,7 @@ const TimePickerInput = React.forwardRef<HTMLInputElement, TimePickerInputProps>
setDate(setDateByType(tempDate, newValue, picker, period));
}
if (e.key >= '0' && e.key <= '9') {
if (picker === '12hours') setPrevIntKey(e.key);
if (picker === 'hours') setPrevIntKey(e.key);

const newValue = calculateNewValue(e.key);
if (flag) onRightFocus?.();
Expand Down Expand Up @@ -157,9 +157,7 @@ const TimePeriodSelect = React.forwardRef<HTMLButtonElement, PeriodSelectorProps
if (date) {
const tempDate = new Date(date);
const hours = display12HourValue(date.getHours());
setDate(
setDateByType(tempDate, hours.toString(), '12hours', period === 'AM' ? 'PM' : 'AM')
);
setDate(setDateByType(tempDate, hours.toString(), 'hours', period === 'AM' ? 'PM' : 'AM'));
}
};

Expand Down Expand Up @@ -191,55 +189,42 @@ interface TimePickerProps {
}

function TimePicker({ date, setDate }: TimePickerProps) {
const [period, setPeriod] = React.useState<Period>('PM');
const [period, setPeriod] = React.useState<Period>(date && date.getHours() >= 12 ? 'PM' : 'AM');

const minuteRef = React.useRef<HTMLInputElement>(null);
const hourRef = React.useRef<HTMLInputElement>(null);
const periodRef = React.useRef<HTMLButtonElement>(null);

return (
<div className="flex items-end gap-2 justify-center">
<div className="grid gap-1 text-center">
<Label htmlFor="hours" className="text-xs">
Hours
</Label>
<TimePickerInput
id="hours"
picker="12hours"
period={period}
date={date}
setDate={setDate}
ref={hourRef}
onRightFocus={() => minuteRef.current?.focus()}
/>
</div>
<div className="grid gap-1 text-center">
<Label htmlFor="minutes" className="text-xs">
Minutes
</Label>
<TimePickerInput
id="minutes"
picker="minutes"
date={date}
setDate={setDate}
ref={minuteRef}
onLeftFocus={() => hourRef.current?.focus()}
onRightFocus={() => periodRef.current?.focus()}
/>
</div>
<div className="grid gap-1 text-center">
<Label htmlFor="period" className="text-xs">
Period
</Label>
<TimePeriodSelect
period={period}
setPeriod={setPeriod}
date={date}
setDate={setDate}
ref={periodRef}
onLeftFocus={() => periodRef.current?.focus()}
/>
</div>
<div className="flex items-center gap-4">
<ClockIcon className="size-5 text-current" />
<TimePickerInput
id="hours"
picker="hours"
period={period}
date={date}
setDate={setDate}
ref={hourRef}
onRightFocus={() => minuteRef.current?.focus()}
/>
<span className="-mx-1.5">:</span>
<TimePickerInput
id="minutes"
picker="minutes"
date={date}
setDate={setDate}
ref={minuteRef}
onLeftFocus={() => hourRef.current?.focus()}
onRightFocus={() => periodRef.current?.focus()}
/>
<TimePeriodSelect
period={period}
setPeriod={setPeriod}
date={date}
setDate={setDate}
ref={periodRef}
onLeftFocus={() => periodRef.current?.focus()}
/>
</div>
);
}
Expand Down
Loading

0 comments on commit 9f96277

Please sign in to comment.