Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add time picker #83

Merged
merged 2 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions suncityla/app/booking/components/BookingCard/BookingEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { format } from 'date-fns';
import { add, format } from 'date-fns';
import { Booking } from '@prisma/client';
import {
Table,
Expand Down Expand Up @@ -28,6 +28,7 @@ const BookingEntry = ({ booking }: { booking: Booking }) => {
<TableHead>Firstname</TableHead>
<TableHead>Lastname</TableHead>
<TableHead>Booking date</TableHead>
<TableHead>Booking time</TableHead>
<TableHead>Address line 1</TableHead>
<TableHead>Address line 2</TableHead>
<TableHead>State</TableHead>
Expand All @@ -39,7 +40,11 @@ const BookingEntry = ({ booking }: { booking: Booking }) => {
<TableRow>
<TableCell>{booking.firstname}</TableCell>
<TableCell>{booking.lastname}</TableCell>
<TableCell>{format(new Date(booking.bookingDate), 'yyyy-MM-dd')}</TableCell>
<TableCell>{format(new Date(booking.bookingDate), 'EE dd/MM/yy')}</TableCell>
<TableCell>{`${format(new Date(booking.bookingDate), 'h:mm a')} - ${format(
add(new Date(booking.bookingDate), { hours: 1 }),
'h:mm a',
)}`}</TableCell>
<TableCell>{booking.streetAddress}</TableCell>
<TableCell>{booking.postalCode}</TableCell>
<TableCell>{booking.state}</TableCell>
Expand Down
41 changes: 34 additions & 7 deletions suncityla/app/booking/components/BookingForm/BookingForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useActionState, useRef, startTransition, useEffect } from 'react';
import { useActionState, useRef, startTransition, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
Expand All @@ -13,6 +13,8 @@ import LoadingModal from '@/components/modals/Loading';
import onSubmitAction from '@/app/booking/actions/onSubmitAction';
import { bookingFormSchema } from '../../schemas/newBookingForm';
import { Booking } from '@prisma/client';
import TimeSelect from '../TimeSelect/TimeSelect';
import createAvailableTimes from '../TimeSelect/times';

export type BookingFormData = z.infer<typeof bookingFormSchema>;

Expand All @@ -32,15 +34,21 @@ export default function BookingForm({ booking }: { booking?: Booking | null }) {
'street-address': booking?.streetAddress ?? '',
'postal-code': booking?.postalCode ?? '',
state: 'LA',
bookingDate: booking?.bookingDate.toISOString() ?? new Date().toISOString(),
bookingDate: booking?.bookingDate.toISOString() || '',
...(state.fields ?? {}),
},
});

// Time Stamp
const [time, setSelectedTime] = useState<number | undefined>(booking?.bookingDate.getTime());
const watchDate = form.watch('bookingDate');
const availableTimes = createAvailableTimes(new Date(watchDate));

const formRef = useRef<HTMLFormElement>(null);

const handleClearForm = () => {
form.reset();
setSelectedTime(undefined);
};

useEffect(() => {
Expand All @@ -49,6 +57,16 @@ export default function BookingForm({ booking }: { booking?: Booking | null }) {
}
}, [state.bookingRef, isPending, router, state.message]);

useEffect(() => {
if (time) {
const newDateWithTime = new Date(watchDate);
const hours = new Date(time).getHours();
newDateWithTime.setHours(hours);
form.setValue('bookingDate', newDateWithTime.toISOString());
return;
}
}, [watchDate, time, form]);

return (
<div className="w-96 border p-4 bg-slate-100 rounded-md">
<LoadingModal show={isPending} />
Expand All @@ -75,12 +93,21 @@ export default function BookingForm({ booking }: { booking?: Booking | null }) {
<BookingFormField placeholder="Address" name="street-address" />
<BookingFormField placeholder="Postcode" name="postal-code" />
<BookingFormField placeholder="State" name="state" disabled />
<DateTimePickerForm />
<DateTimePickerForm onChange={() => setSelectedTime(undefined)} />
{watchDate && (
<TimeSelect
selectedTime={time}
availableTimes={availableTimes}
onTimeSelect={setSelectedTime}
/>
)}
<div className="flex gap-1 justify-end mt-4">
<Button onClick={handleClearForm} variant="outline">
Clear
</Button>
<Button type="submit" disabled={form.formState.isSubmitting}>
{!booking?.id && (
<Button onClick={handleClearForm} variant="outline">
Clear
</Button>
)}
<Button type="submit" disabled={form.formState.isSubmitting || !time}>
Submit
</Button>
</div>
Expand Down
43 changes: 15 additions & 28 deletions suncityla/app/booking/components/TimeDatePicker/TimeDatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
"use client";
'use client';

import { useState } from "react";
import { format } from "date-fns";
import { Calendar as CalendarIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useState } from 'react';
import { format } from 'date-fns';
import { Calendar as CalendarIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
} from '@/components/ui/form';

export function DateTimePickerForm() {
export function DateTimePickerForm({ onChange }: { onChange: () => void }) {
const [open, setOpen] = useState(false);

return (
Expand All @@ -34,16 +30,12 @@ export function DateTimePickerForm() {
onClick={() => setOpen(true)}
variant="outline"
className={cn(
"w-full justify-start font-normal",
!field.value && "text-muted-foreground"
'w-full justify-start font-normal',
!field.value && 'text-muted-foreground',
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
{field.value ? format(field.value, 'PPP') : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
</FormControl>
Expand All @@ -54,17 +46,12 @@ export function DateTimePickerForm() {
onSelect={(date) => {
field.onChange(date?.toISOString());
setOpen(false);
onChange();
}}
initialFocus
/>
</PopoverContent>
</Popover>
<input
type="hidden"
{...field}
name={field.name}
value={field.value}
/>
<input type="hidden" {...field} name={field.name} value={field.value} />
<FormDescription />
<FormMessage />
</FormItem>
Expand Down
59 changes: 59 additions & 0 deletions suncityla/app/booking/components/TimeSelect/TimeSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import * as React from 'react';

import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { FormField, FormItem } from '@/components/ui/form';
import { format } from 'date-fns';

export function TimeSelect({
availableTimes,
selectedTime,
onTimeSelect,
}: {
availableTimes: { from: number; to: number }[];
selectedTime?: number;
onTimeSelect: (time: number) => void;
}) {
return (
<FormField
name=""
render={() => {
return (
<FormItem>
<Select
onValueChange={(time) => onTimeSelect(Number(time))}
value={selectedTime?.toString()}
>
<SelectTrigger className="w-[180px] bg-white">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<div className="bg-white">
<SelectContent>
<SelectGroup>
<SelectLabel>Times</SelectLabel>
{availableTimes.map(({ from, to }, i) => (
<SelectItem className="cursor-pointer" key={i} value={from.toString()}>
{format(from, 'h:mm a')} - {format(to, 'h:mm a')}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</div>
</Select>
</FormItem>
);
}}
/>
);
}

export default TimeSelect;
12 changes: 12 additions & 0 deletions suncityla/app/booking/components/TimeSelect/times.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const createAvailableTimes = (date: Date) => {
const availableTimes = [];
for (let i = 8; i < 20; i++) {
availableTimes.push({
from: date.setHours(i, 0, 0, 0),
to: date.setHours(i + 1, 0, 0, 0),
});
}
return availableTimes;
};

export default createAvailableTimes;
81 changes: 37 additions & 44 deletions suncityla/components/ui/calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,60 @@
"use client";
'use client';

import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DayPicker } from 'react-day-picker';

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';

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

function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
className={cn('p-3', className)}
classNames={{
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",
nav: "space-x-1 flex items-center",
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',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: cn(
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
props.mode === "range"
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
: "[&:has([aria-selected])]:rounded-md"
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
props.mode === 'range'
? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
: '[&:has([aria-selected])]:rounded-md',
),
day: cn(
buttonVariants({ variant: "ghost" }),
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
buttonVariants({ variant: 'ghost' }),
'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
),
day_range_start: "day-range-start",
day_range_end: "day-range-end",
day_range_start: 'day-range-start',
day_range_end: 'day-range-end',
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
Chevron(props) {
if (props.orientation === "left") {
if (props.orientation === 'left') {
return <ChevronLeft className="h-4 w-4" {...props} />;
}
return <ChevronRight className="h-4 w-4" {...props} />;
Expand All @@ -71,6 +64,6 @@ function Calendar({
/>
);
}
Calendar.displayName = "Calendar";
Calendar.displayName = 'Calendar';

export { Calendar };
Loading
Loading