Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import React, { useEffect } from 'react';
import dayjs from 'dayjs';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { omrsDateFormat } from '../constants';
import { parseDate, toCalendar, getLocalTimeZone } from '@internationalized/date';
import { useAppointmentsCalendar } from '../hooks/useAppointmentsCalendar';
import AppointmentsHeader from '../header/appointments-header.component';
import CalendarHeader from './header/calendar-header.component';
import MonthlyCalendarView from './monthly/monthly-calendar-view.component';
import { useAppointmentsStore, setSelectedDate } from '../store';
import { setSelectedDate, getSelectedCalendarDate, calendar } from '../store';

const AppointmentsCalendarView: React.FC = () => {
const { t } = useTranslation();
const { selectedDate } = useAppointmentsStore();
const { calendarEvents } = useAppointmentsCalendar(dayjs(selectedDate).toISOString(), 'monthly');
const calendarObject = getSelectedCalendarDate();
const { calendarEvents } = useAppointmentsCalendar(calendarObject.toString(), 'monthly');

let params = useParams();

useEffect(() => {
if (params.date) {
setSelectedDate(dayjs(params.date).startOf('day').format(omrsDateFormat));
const date = toCalendar(parseDate(params.date), calendar);
setSelectedDate(date.toDate(getLocalTimeZone()).toISOString());
}
}, [params.date]);

return (
<div data-testid="appointments-calendar">
<AppointmentsHeader title={t('calendar', 'Calendar')} />
<CalendarHeader />
<MonthlyCalendarView events={calendarEvents} />
<CalendarHeader calendar={calendar} />
<MonthlyCalendarView events={calendarEvents} calendar={calendar} />
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React from 'react';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { Button } from '@carbon/react';
import { type Calendar } from '@internationalized/date';
import { ArrowLeft } from '@carbon/react/icons';
import { navigate } from '@openmrs/esm-framework';
import { spaHomePage } from '../../constants';
import { useAppointmentsStore } from '../../store';
import { getSelectedCalendarDate } from '../../store';
import styles from './calendar-header.scss';

const CalendarHeader: React.FC = () => {
interface CalendarHeaderProps {
calendar: Calendar;
}
const CalendarHeader: React.FC<CalendarHeaderProps> = ({ calendar }) => {
const { t } = useTranslation();
const { selectedDate } = useAppointmentsStore();

const handleClick = () => {
navigate({ to: `${spaHomePage}/appointments/${dayjs(selectedDate).format('YYYY-MM-DD')}` });
const date = getSelectedCalendarDate();
navigate({ to: `${spaHomePage}/appointments/${encodeURI(date.toString())}` });
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import styles from './days-of-week.scss';

// Translations for the days of the week
Expand All @@ -15,14 +13,13 @@ import styles from './days-of-week.scss';

interface DaysOfWeekProps {
dayOfWeek: string;
isToday: boolean;
}

const DaysOfWeekCard: React.FC<DaysOfWeekProps> = ({ dayOfWeek }) => {
const { t } = useTranslation();
const isToday = dayjs(new Date()).format('ddd').toUpperCase() === dayOfWeek;
const DaysOfWeekCard: React.FC<DaysOfWeekProps> = ({ dayOfWeek, isToday }) => {
return (
<div tabIndex={0} role="button" className={styles.tileContainer}>
<span className={classNames({ [styles.bold]: isToday })}>{t(dayOfWeek)}</span>
<span className={classNames({ [styles.bold]: isToday })}>{dayOfWeek}</span>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import React from 'react';
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
import { type Calendar } from '@internationalized/date';
import { type DailyAppointmentsCountByService } from '../../types';
import { useAppointmentsStore } from '../../store';
import { getSelectedCalendarDate } from '../../store';
import { monthDays } from '../../helpers';
import MonthlyViewWorkload from './monthly-workload-view.component';
import MonthlyHeader from './monthly-header.component';
import styles from '../appointments-calendar-view-view.scss';

dayjs.extend(isBetween);

interface MonthlyCalendarViewProps {
events: Array<DailyAppointmentsCountByService>;
calendar: Calendar;
}

const MonthlyCalendarView: React.FC<MonthlyCalendarViewProps> = ({ events }) => {
const { selectedDate } = useAppointmentsStore();
const MonthlyCalendarView: React.FC<MonthlyCalendarViewProps> = ({ events, calendar }) => {
const date = getSelectedCalendarDate();

return (
<div className={styles.calendarViewContainer}>
<MonthlyHeader />
<div className={styles.wrapper}>
<div className={styles.monthlyCalendar}>
{monthDays(dayjs(selectedDate)).map((dateTime, i) => (
<MonthlyViewWorkload key={i} dateTime={dateTime} events={events} />
{monthDays(date).map((dateTime, i) => (
<MonthlyViewWorkload key={i} dateTime={dateTime} events={events} calendar={calendar} />
))}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
import React, { useCallback } from 'react';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { Button } from '@carbon/react';
import { formatDate } from '@openmrs/esm-framework';
import { omrsDateFormat } from '../../constants';
import { useAppointmentsStore, setSelectedDate } from '../../store';
import { getLocalTimeZone } from '@internationalized/date';
import { locale, setSelectedDate, getSelectedCalendarDate } from '../../store';
import DaysOfWeekCard from './days-of-week.component';
import styles from './monthly-header.scss';

const DAYS_IN_WEEK = ['SUN', 'MON', 'TUE', 'WED', 'THUR', 'FRI', 'SAT'];

const MonthlyHeader: React.FC = () => {
const { t } = useTranslation();
const { selectedDate } = useAppointmentsStore();
const date = getSelectedCalendarDate();

const todayShort = new Intl.DateTimeFormat(locale, { weekday: 'short' })
.format(date.toDate(getLocalTimeZone()))
.toUpperCase();

const daysInWeeks = React.useMemo(() => {
const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short' });

const baseDate = new Date(Date.UTC(2021, 7, 1));

return Array.from({ length: 7 }).map((_, index) => {
const date = new Date(baseDate);
date.setUTCDate(baseDate.getUTCDate() + index);

const label = formatter.format(date).toUpperCase();
return {
label,
isToday: label === todayShort,
};
});
}, [todayShort]);

const handleSelectPrevMonth = useCallback(() => {
setSelectedDate(dayjs(selectedDate).subtract(1, 'month').format(omrsDateFormat));
}, [selectedDate]);
setSelectedDate(date.subtract({ months: 1 }).toDate(getLocalTimeZone()).toISOString());
}, [date]);

const handleSelectNextMonth = useCallback(() => {
setSelectedDate(dayjs(selectedDate).add(1, 'month').format(omrsDateFormat));
}, [selectedDate]);
setSelectedDate(date.add({ months: 1 }).toDate(getLocalTimeZone()).toISOString());
}, [date]);

return (
<>
Expand All @@ -32,14 +50,14 @@ const MonthlyHeader: React.FC = () => {
size="sm">
{t('prev', 'Prev')}
</Button>
<span>{formatDate(new Date(selectedDate), { day: false, time: false, noToday: true })}</span>
<span>{formatDate(new Date(date.toDate(getLocalTimeZone())), { day: false, time: false, noToday: true })}</span>
<Button aria-label={t('nextMonth', 'Next month')} kind="tertiary" onClick={handleSelectNextMonth} size="sm">
{t('next', 'Next')}
</Button>
</div>
<div className={styles.workLoadCard}>
{DAYS_IN_WEEK.map((day) => (
<DaysOfWeekCard key={day} dayOfWeek={day} />
{daysInWeeks.map(({ label, isToday }) => (
<DaysOfWeekCard key={label} dayOfWeek={label} isToday={isToday} />
))}
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ interface MonthlyWorkloadViewExpandedProps extends MonthlyWorkloadViewProps {
count: number;
}

const MonthlyWorkloadViewExpanded: React.FC<MonthlyWorkloadViewExpandedProps> = ({ count, events, dateTime }) => {
const MonthlyWorkloadViewExpanded: React.FC<MonthlyWorkloadViewExpandedProps> = ({
count,
calendar,
events,
dateTime,
}) => {
const { t } = useTranslation();
const [isOpen, setIsOpen] = React.useState(false);
const popoverRef = useRef(null);
Expand Down Expand Up @@ -38,7 +43,7 @@ const MonthlyWorkloadViewExpanded: React.FC<MonthlyWorkloadViewExpandedProps> =
{t('countMore', '{{count}} more', { count })}
</button>
<PopoverContent>
<MonthlyWorkloadView events={events} dateTime={dateTime} showAllServices={true} />
<MonthlyWorkloadView events={events} dateTime={dateTime} showAllServices={true} calendar={calendar} />
</PopoverContent>
</Popover>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import React, { useMemo } from 'react';
import classNames from 'classnames';
import dayjs, { type Dayjs } from 'dayjs';
import { User } from '@carbon/react/icons';
import { parseDate, type CalendarDate, toCalendar, type Calendar } from '@internationalized/date';
import { navigate, useLayoutType } from '@openmrs/esm-framework';
import { spaHomePage } from '../../constants';
import { isSameMonth } from '../../helpers';
import { isSameCalendarMonth } from '../../helpers';
import { type DailyAppointmentsCountByService } from '../../types';
import { useAppointmentsStore } from '../../store';
import { getSelectedCalendarDate, useAppointmentsStore } from '../../store';
import MonthlyWorkloadViewExpanded from './monthly-workload-view-expanded.component';
import styles from './monthly-view-workload.scss';

export interface MonthlyWorkloadViewProps {
events: Array<DailyAppointmentsCountByService>;
dateTime: Dayjs;
dateTime: CalendarDate;
showAllServices?: boolean;
calendar: Calendar;
}

const MonthlyWorkloadView: React.FC<MonthlyWorkloadViewProps> = ({ dateTime, events, showAllServices = false }) => {
const MonthlyWorkloadView: React.FC<MonthlyWorkloadViewProps> = ({
dateTime,
calendar,
events,
showAllServices = false,
}) => {
const layout = useLayoutType();
const { selectedDate } = useAppointmentsStore();
const date = getSelectedCalendarDate();

const currentData = useMemo(
() =>
events?.find(
(event) => dayjs(event.appointmentDate)?.format('YYYY-MM-DD') === dayjs(dateTime)?.format('YYYY-MM-DD'),
(event) => toCalendar(parseDate(event.appointmentDate), calendar).toString() === dateTime.toString(),
),
[dateTime, events],
[calendar, dateTime, events],
);

const visibleServices = useMemo(() => {
Expand All @@ -45,22 +52,22 @@ const MonthlyWorkloadView: React.FC<MonthlyWorkloadViewProps> = ({ dateTime, eve
}, [currentData?.services, layout, showAllServices]);

const navigateToAppointmentsByDate = (serviceUuid: string) => {
navigate({ to: `${spaHomePage}/appointments/${dayjs(dateTime).format('YYYY-MM-DD')}/${serviceUuid}` });
navigate({ to: `${spaHomePage}/appointments/${dateTime.toString()}/${serviceUuid}` });
};

return (
<div
onClick={() => navigateToAppointmentsByDate('')}
className={classNames(
styles[isSameMonth(dateTime, dayjs(selectedDate)) ? 'monthly-cell' : 'monthly-cell-disabled'],
styles[isSameCalendarMonth(dateTime, date) ? 'monthly-cell' : 'monthly-cell-disabled'],
showAllServices
? {}
: {
[styles.smallDesktop]: layout === 'small-desktop',
[styles.largeDesktop]: layout !== 'small-desktop',
},
)}>
{isSameMonth(dateTime, dayjs(selectedDate)) && (
{isSameCalendarMonth(dateTime, date) && (
<div>
<span className={classNames(styles.totals)}>
{currentData?.services ? (
Expand All @@ -71,7 +78,7 @@ const MonthlyWorkloadView: React.FC<MonthlyWorkloadViewProps> = ({ dateTime, eve
) : (
<div />
)}
<b className={styles.calendarDate}>{dateTime.format('D')}</b>
<b className={styles.calendarDate}>{dateTime.day}</b>
</span>
{currentData?.services && (
<div className={styles.currentData}>
Expand All @@ -94,6 +101,7 @@ const MonthlyWorkloadView: React.FC<MonthlyWorkloadViewProps> = ({ dateTime, eve
count={currentData.services.length - (layout === 'small-desktop' ? 2 : 4)}
events={events}
dateTime={dateTime}
calendar={calendar}
/>
) : (
''
Expand Down
32 changes: 17 additions & 15 deletions packages/esm-appointments-app/src/helpers/functions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import dayjs, { type Dayjs } from 'dayjs';
import { type TFunction } from 'i18next';
import { launchWorkspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
import { type AppointmentSummary, type AppointmentCountMap } from '../types';
import { appointmentsFormWorkspace } from '../constants';
import { CalendarDate, startOfMonth, endOfMonth, getDayOfWeek, isSameMonth } from '@internationalized/date';
import { getLocale } from '@openmrs/esm-utils';

interface FlattenedAppointmentSummary {
serviceName: string;
Expand Down Expand Up @@ -62,30 +63,31 @@ export const formatAMPM = (date: Date): string => {
return `${hours12}:${minutesStr} ${ampm}`;
};

export const isSameMonth = (cellDate: Dayjs, currentDate: Dayjs) => {
return cellDate.isSame(currentDate, 'month');
export const isSameCalendarMonth = (cellDate: CalendarDate, currentDate: CalendarDate) => {
return isSameMonth(cellDate, currentDate);
};

export const monthDays = (currentDate: Dayjs) => {
const monthStart = dayjs(currentDate).startOf('month');
const monthEnd = dayjs(currentDate).endOf('month');
const monthDays = dayjs(currentDate).daysInMonth();
const lastMonth = dayjs(currentDate).subtract(1, 'month');
const nextMonth = dayjs(currentDate).add(1, 'month');
let days: Dayjs[] = [];
export const monthDays = (currentDate: CalendarDate) => {
const monthStart = startOfMonth(currentDate);
const monthEnd = endOfMonth(currentDate);
const monthDays = currentDate.calendar.getDaysInMonth(currentDate);
const lastMonth = currentDate.subtract({ months: 1 });
const nextMonth = currentDate.add({ months: 1 });
let days: CalendarDate[] = [];

for (let i = lastMonth.daysInMonth() - monthStart.day() + 1; i <= lastMonth.daysInMonth(); i++) {
days.push(lastMonth.date(i));
const lastMonthDays = lastMonth.calendar.getDaysInMonth(lastMonth);
for (let i = lastMonthDays - getDayOfWeek(monthStart, getLocale()) + 1; i <= lastMonthDays; i++) {
days.push(new CalendarDate(lastMonth.year, lastMonth.month, i));
}

for (let i = 1; i <= monthDays; i++) {
days.push(currentDate.date(i));
days.push(new CalendarDate(currentDate.year, currentDate.month, i));
}

const dayLen = days.length > 30 ? 7 : 14;

for (let i = 1; i < dayLen - monthEnd.day(); i++) {
days.push(nextMonth.date(i));
for (let i = 1; i < dayLen - getDayOfWeek(monthEnd, getLocale()); i++) {
days.push(new CalendarDate(nextMonth.year, nextMonth.month, i));
}
return days;
};
Expand Down
Loading