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

Multiple Selection #9

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ const availableDates: IAvailableDates[] = [
];

export default function App() {
const [dateOfAppointment, setDateOfAppointment] =
useState<IAppointment | null>(null);
const [scheduledAppointments, setScheduledAppointments] =
useState<IAppointment[]>([]);

useEffect(() => {
// Contains the selected date, time slot in the following format
Expand All @@ -91,8 +91,9 @@ export default function App() {
<SafeAreaView>
<StatusBar backgroundColor="transparent" barStyle="dark-content" />
<TimeSlotPicker
availableDates={availableDates}
setDateOfAppointment={setDateOfAppointment}
scheduledAppointments={scheduledAppointments}
setScheduledAppointments={setScheduledAppointments}
availableDates={dummyAvailableDates}
/>
</SafeAreaView>
);
Expand All @@ -105,9 +106,11 @@ You can find a detailed example [here](example/src/App.tsx).

| Prop name | Description | Type | Default |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `setDateOfAppointment` | A component to use on top of header image. It can also be used without header image to render a custom component as header. | `(data: IAppointment \| null) => void` | **REQUIRED** |
| `availableDates` | The array of the available slot times per date. | `IAvailableDates[]` | [fixedAvailableDates](src/utils/dateHelpers.ts) |
| `scheduledAppointment` | An already existed appointment, which is going to mark the specific date as `with appointment`. | `IAppointment` | `undefined` |
| `scheduledAppointments` | An already existing array of appointments, which is going to mark the specific dates as `with appointment`. | `IAppointment[]` | `[]`
| `setScheduledAppointments` | Callback called when the user selects or deselects a time slot. It contains all currently selected time slots. | `(data: IAppointment[]) => void` | **REQUIRED** |
| `multipleSelection` | Enables the selection of multiple slots. | `boolean` | `false` | |
| `multipleSelectionStrategy` | Strategy used to restrict selection and deselection. | `"consecutive" \| "same-day-consecutive" \| "non-consecutive"` | `non-consecutive` | |
| `marginTop` | Margin top for the whole component. | `number` | `0` |
| `datePickerBackgroundColor` | Background color of the section with the horizontal scroll, which contains the days. | `hex string` | `'#FFFFFF'` |
| `timeSlotsBackgroundColor` | Background color of the section that contains the time slots. | `hex string` | `'#FFFFFF'` |
Expand Down
55 changes: 35 additions & 20 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
import * as React from 'react';
import { useState } from 'react';
import { StatusBar } from 'react-native';
import { ScrollView, StatusBar, StyleSheet } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import {
IAppointment,
TimeSlotPicker,
generateAvailableDates,
} from '@dgreasi/react-native-time-slot-picker';
import { SelectedTimeSlot } from './SelectedTimeSlot';
import { bookedData, dummyAvailableDates } from './data';

export default function App() {
const [dateOfAppointment, setDateOfAppointment] =
useState<IAppointment | null>(null);

const [scheduledAppointments, setScheduledAppointments] = useState<
IAppointment[] | undefined
>();
const dummyAvailableDates = React.useMemo(() => {
return generateAvailableDates(2, 60);
}, []);
return (
<SafeAreaProvider>
<SafeAreaView>
<StatusBar backgroundColor="transparent" barStyle="dark-content" />
<TimeSlotPicker
setDateOfAppointment={setDateOfAppointment}
scheduledAppointment={bookedData}
availableDates={dummyAvailableDates}
// marginTop={24}
// datePickerBackgroundColor="#F4CC58"
// timeSlotsBackgroundColor="#F4CC58"
// mainColor="#B4CC51"
// timeSlotWidth={116}
// dayNamesOverride={greekDayNames}
// monthNamesOverride={greekMonthNames}
// dayNamesOverride={spanishDayNames}
// monthNamesOverride={spanishMonthNames}
/>
<SelectedTimeSlot dateOfAppointment={dateOfAppointment} />
<ScrollView contentContainerStyle={styles.scrollViewContainer}>
<TimeSlotPicker
scheduledAppointments={scheduledAppointments}
setScheduledAppointments={setScheduledAppointments}
availableDates={dummyAvailableDates}
multipleSelection
multipleSelectionStrategy="consecutive"
// marginTop={24}
// datePickerBackgroundColor="#F4CC58"
// timeSlotsBackgroundColor="#F4CC58"
// mainColor="#B4CC51"
// timeSlotWidth={116}
// dayNamesOverride={greekDayNames}
// monthNamesOverride={greekMonthNames}
// dayNamesOverride={spanishDayNames}
// monthNamesOverride={spanishMonthNames}
/>
<SelectedTimeSlot
scheduledAppointments={scheduledAppointments ?? []}
/>
</ScrollView>
</SafeAreaView>
</SafeAreaProvider>
);
}

const styles = StyleSheet.create({
scrollViewContainer: {
minHeight: '100%',
},
});
16 changes: 12 additions & 4 deletions example/src/SelectedTimeSlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { StyleSheet, Text, View } from 'react-native';
import { IAppointment } from '@dgreasi/react-native-time-slot-picker';

interface Props {
dateOfAppointment: IAppointment | null;
scheduledAppointments: IAppointment[] | null;
}

export const SelectedTimeSlot = ({ dateOfAppointment }: Props) => {
export const SelectedTimeSlot = ({ scheduledAppointments }: Props) => {
return (
<View style={styles.container}>
<Text style={styles.boldFont}>Selected time slot</Text>
Expand All @@ -20,13 +20,21 @@ export const SelectedTimeSlot = ({ dateOfAppointment }: Props) => {
<View style={styles.valueContainer}>
<Text style={styles.boldFont}>appointmentDate: </Text>
<View style={styles.borderOfValue}>
<Text>{dateOfAppointment?.appointmentDate}</Text>
<Text>
{scheduledAppointments && scheduledAppointments[0]
? scheduledAppointments[0].appointmentDate
: ''}
</Text>
</View>
</View>
<View style={styles.valueContainer}>
<Text style={styles.boldFont}>appointmentTime: </Text>
<View style={styles.borderOfValue}>
<Text>{dateOfAppointment?.appointmentTime}</Text>
<Text>
{scheduledAppointments && scheduledAppointments[0]
? scheduledAppointments[0].appointmentTime
: ''}
</Text>
</View>
</View>
</View>
Expand Down
85 changes: 47 additions & 38 deletions src/TimeSlotPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { IAppointment, IAvailableDates } from './interfaces/app.interface';
import { View } from 'react-native';
import ScheduleDatePicker from './components/ScheduleDatePicker';
Expand All @@ -13,9 +13,14 @@ import {
import { LocalContext } from './components/LocalContext';

interface Props {
setDateOfAppointment: (data: IAppointment | null) => void;
availableDates?: IAvailableDates[];
scheduledAppointment?: IAppointment | undefined;
scheduledAppointments?: IAppointment[];
setScheduledAppointments: (data: IAppointment[]) => void;
multipleSelection?: boolean;
multipleSelectionStrategy?:
| 'consecutive'
| 'same-day-consecutive'
| 'non-consecutive';
marginTop?: number;
datePickerBackgroundColor?: string;
timeSlotsBackgroundColor?: string;
Expand All @@ -28,8 +33,10 @@ interface Props {

const TimeSlotPicker = ({
availableDates = fixedAvailableDates,
setDateOfAppointment,
scheduledAppointment,
scheduledAppointments,
setScheduledAppointments,
multipleSelection = false,
multipleSelectionStrategy = 'non-consecutive',
marginTop = 0,
datePickerBackgroundColor,
timeSlotsBackgroundColor,
Expand All @@ -39,36 +46,32 @@ const TimeSlotPicker = ({
dayNamesOverride = defaultDayNames,
monthNamesOverride = defaultMonthNames,
}: Props) => {
const [selectedTime, setSelectedTime] = useState<string>('');
const [selectedDate, setSelectedDate] = useState<IAvailableDates | undefined>(
availableDates[0]
);

useEffect(() => {
// Get first day with available appointments
const firstAvailableDay =
availableDates.findIndex((date) => date.slotTimes.length > 0) || 0;
setSelectedDate(availableDates?.[firstAvailableDay]);
setSelectedTime(availableDates?.[firstAvailableDay]?.slotTimes?.[0] || '');
const sortedAppointments = useMemo(() => {
return scheduledAppointments?.sort((a, b) => {
return (
new Date(a.appointmentDate).getTime() -
new Date(b.appointmentDate).getTime()
);
});
}, [scheduledAppointments]);
const sortedAvailableDates = useMemo(() => {
return availableDates.sort((a, b) => {
return new Date(a.date).getTime() - new Date(b.date).getTime();
});
}, [availableDates]);

// If any changes on date and time selected update data of appointment
useEffect(() => {
if (selectedDate && selectedTime) {
setDateOfAppointment({
appointmentDate: selectedDate.date,
appointmentTime: selectedTime,
});
} else {
setDateOfAppointment(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDate, selectedTime]);
const [selectedDay, setSelectedDay] = useState<string>(
sortedAppointments && sortedAppointments[0]
? sortedAppointments[0].appointmentDate
: sortedAvailableDates && sortedAvailableDates[0]
? sortedAvailableDates[0].date
: ''
);

return (
<LocalContext
slotDate={selectedDate?.date || ''}
scheduledAppointment={scheduledAppointment}
slotDate={selectedDay}
scheduledAppointments={scheduledAppointments}
overrideData={{
mainColor,
timeSlotWidth,
Expand All @@ -79,20 +82,26 @@ const TimeSlotPicker = ({
<View style={{ marginTop }}>
<View>
<ScheduleDatePicker
selectedDate={selectedDate}
selectedDay={selectedDay}
availableDates={availableDates}
setSelectedDate={setSelectedDate}
setSelectedTime={setSelectedTime}
scheduledAppointment={scheduledAppointment}
setSelectedDay={setSelectedDay}
scheduledAppointments={scheduledAppointments}
backgroundColor={datePickerBackgroundColor}
/>
</View>
{selectedDate && (
{selectedDay && (
<TimeSlots
title={timeSlotsTitle}
selectedTime={selectedTime}
setSelectedTime={setSelectedTime}
slotTimes={selectedDate.slotTimes}
selectedDay={selectedDay}
slotTimes={
availableDates.find((data) => data.date === selectedDay)
?.slotTimes || []
}
availableDates={availableDates}
scheduledAppointments={scheduledAppointments}
setScheduledAppointments={setScheduledAppointments}
multipleSelection={multipleSelection}
multipleSelectionStrategy={multipleSelectionStrategy}
backgroundColor={timeSlotsBackgroundColor}
/>
)}
Expand Down
18 changes: 9 additions & 9 deletions src/components/LocalContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ interface IOverrideData {
monthNamesOverride: string[];
}

export const SelectedDateContext = createContext<string>('');
export const ScheduledAppointmentContext = createContext<
IAppointment | undefined
export const selectedDayContext = createContext<string>('');
export const scheduledAppointmentsContext = createContext<
IAppointment[] | undefined
>(undefined);

export const OverrideDataContext = createContext<IOverrideData>({
Expand All @@ -30,23 +30,23 @@ export const OverrideDataContext = createContext<IOverrideData>({
interface Props {
children: React.ReactNode;
slotDate: string;
scheduledAppointment: IAppointment | undefined;
scheduledAppointments: IAppointment[] | undefined;
overrideData: IOverrideData;
}

export const LocalContext = ({
children,
slotDate,
scheduledAppointment,
scheduledAppointments,
overrideData,
}: Props) => {
return (
<SelectedDateContext.Provider value={slotDate}>
<ScheduledAppointmentContext.Provider value={scheduledAppointment}>
<selectedDayContext.Provider value={slotDate}>
<scheduledAppointmentsContext.Provider value={scheduledAppointments}>
<OverrideDataContext.Provider value={overrideData}>
{children}
</OverrideDataContext.Provider>
</ScheduledAppointmentContext.Provider>
</SelectedDateContext.Provider>
</scheduledAppointmentsContext.Provider>
</selectedDayContext.Provider>
);
};
20 changes: 10 additions & 10 deletions src/components/ScheduleDateElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ import CalendarDay from './CalendarDay';

interface ScheduleDateElementProps {
slotDate: IAvailableDates;
selectedDate?: IAvailableDates;
onPress: () => void;
selectedDay: string;
currentDay: number;
appointmentDay: number;
onPress: () => void;
appointmentDays: number[];
}

const ScheduleDateElement = ({
slotDate,
onPress,
selectedDate,
selectedDay,
currentDay,
appointmentDay,
onPress,
appointmentDays,
}: ScheduleDateElementProps) => {
const day = useMemo(() => new Date(slotDate.date).getDate(), [slotDate.date]);
const isAppointmentToday = useMemo(
() => appointmentDay === day,
[appointmentDay, day]
() => !!appointmentDays.find((data) => data === day),
[appointmentDays, day]
);

const isSelected = useCallback(() => {
return slotDate.date === selectedDate?.date;
}, [slotDate.date, selectedDate?.date]);
return slotDate.date === selectedDay;
}, [slotDate.date, selectedDay]);

return (
<CalendarDay
Expand Down
Loading
Loading