Skip to content

Commit

Permalink
FeaturePanel: implement a new library for opening hours (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dlurak authored Jun 23, 2024
1 parent 10108b9 commit ccfa83c
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 28 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@
"next-codegrid": "^1.0.3",
"next-cookies": "^2.0.3",
"next-pwa": "^5.2.21",
"opening_hours": "^3.8.0",
"osm-auth": "^2.4.0",
"react": "^18.2.0",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^18.2.0",
"react-jss": "^10.6.0",
"react-split-pane": "^0.1.92",
"react-zoom-pan-pinch": "^3.3.0",
"simple-opening-hours": "^0.1.1",
"styled-components": "^6.1.11",
"styled-jsx": "^3.4.4"
},
Expand Down
36 changes: 14 additions & 22 deletions src/components/FeaturePanel/renderers/OpeningHoursRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
import React from 'react';
import { SimpleOpeningHours } from 'simple-opening-hours';
import styled from 'styled-components';
import AccessTime from '@mui/icons-material/AccessTime';
import { useToggleState } from '../../helpers';
import { t } from '../../../services/intl';
import { ToggleButton } from '../helpers/ToggleButton';

interface SimpleOpeningHoursTable {
su: string[];
mo: string[];
tu: string[];
we: string[];
th: string[];
fr: string[];
sa: string[];
ph: string[];
}

const parseOpeningHours = (value) => {
const sanitized = value.match(/^[0-9:]+-[0-9:]+$/) ? `Mo-Su ${value}` : value;
const opening = new SimpleOpeningHours(sanitized);
const daysTable = opening.getTable() as SimpleOpeningHoursTable;
const isOpen = opening.isOpenNow();
return { daysTable, isOpen };
};
import { parseOpeningHours } from './openingHours';
import { SimpleOpeningHoursTable } from './openingHours/types';
import { useFeatureContext } from '../../utils/FeatureContext';

const Table = styled.table`
margin: 1em;
Expand All @@ -39,7 +22,7 @@ const Table = styled.table`
// const weekDays = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa'];
const weekDays = t('opening_hours.days_su_mo_tu_we_th_fr_sa').split('|');

const formatTimes = (times) =>
const formatTimes = (times: string[]) =>
times.length ? times.map((x) => x.replace(/:00/g, '')).join(', ') : '-';

const formatDescription = (isOpen: boolean, days: SimpleOpeningHoursTable) => {
Expand All @@ -60,7 +43,16 @@ const formatDescription = (isOpen: boolean, days: SimpleOpeningHoursTable) => {

const OpeningHoursRenderer = ({ v }) => {
const [isExpanded, toggle] = useToggleState(false);
const { daysTable, isOpen } = parseOpeningHours(v);

const { countryCode, center } = useFeatureContext().feature;

const openingHours = parseOpeningHours(v, center, {
country_code: countryCode,
state: '',
});
if (!openingHours) return null;
const { daysTable, isOpen } = openingHours;

const { ph, ...days } = daysTable;
const timesByDay = Object.values(days).map((times, idx) => ({
times,
Expand Down
62 changes: 62 additions & 0 deletions src/components/FeaturePanel/renderers/openingHours/complex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import OpeningHours from 'opening_hours';
import { isInRange } from './utils';
import { Address, SimpleOpeningHoursTable } from './types';
import { LonLat } from '../../../../services/types';

type Weekday = keyof SimpleOpeningHoursTable;
const weekdays: Weekday[] = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'ph'];
const weekdayMappings: Record<string, Weekday> = {
Sun: 'su',
Mon: 'mo',
Tue: 'tu',
Wed: 'we',
Thu: 'th',
Fri: 'fr',
Sat: 'sa',
};

const fmtDate = (d: Date) =>
d.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' });

export const parseComplexOpeningHours = (
value: string,
[lon, lat]: LonLat,
address: Address,
) => {
const oh = new OpeningHours(value, {
lat,
lon,
address,
});

const today = new Date();
today.setHours(0, 0, 0, 0);

const oneWeekLater = new Date(today);
oneWeekLater.setDate(oneWeekLater.getDate() + 7);

const intervals = oh.getOpenIntervals(today, oneWeekLater);
const grouped = weekdays.map((w) => {
const daysIntervals = intervals.filter(
([from]) =>
w === weekdayMappings[from.toLocaleString('en', { weekday: 'short' })],
);

return [w, daysIntervals] as const;
});

const daysTable = Object.fromEntries(
grouped.map((entry) => {
const strings = entry[1].map(
([from, due]) => `${fmtDate(from)}-${fmtDate(due)}`,
);

return [entry[0], strings] as const;
}),
) as unknown as SimpleOpeningHoursTable;

return {
daysTable,
isOpen: intervals.some(([from, due]) => isInRange([from, due], new Date())),
};
};
15 changes: 15 additions & 0 deletions src/components/FeaturePanel/renderers/openingHours/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LonLat } from '../../../../services/types';
import { parseComplexOpeningHours } from './complex';
import { Address } from './types';

export const parseOpeningHours = (
value: string,
coords: LonLat,
address: Address,
): ReturnType<typeof parseComplexOpeningHours> | null => {
try {
return parseComplexOpeningHours(value, coords, address);
} catch {
return null;
}
};
15 changes: 15 additions & 0 deletions src/components/FeaturePanel/renderers/openingHours/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface SimpleOpeningHoursTable {
su: string[];
mo: string[];
tu: string[];
we: string[];
th: string[];
fr: string[];
sa: string[];
ph: string[];
}

export type Address = {
country_code: string;
state: string;
};
2 changes: 2 additions & 0 deletions src/components/FeaturePanel/renderers/openingHours/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const isInRange = ([startDate, endDate]: [Date, Date], date: Date) =>
date.getTime() >= startDate.getTime() && date.getTime() <= endDate.getTime();
40 changes: 35 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,13 @@
dependencies:
regenerator-runtime "^0.14.0"

"@babel/runtime@^7.17.2", "@babel/runtime@^7.19.0":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
dependencies:
regenerator-runtime "^0.14.0"

"@babel/template@^7.12.13", "@babel/template@^7.18.10", "@babel/template@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
Expand Down Expand Up @@ -4448,6 +4455,20 @@ hyphenate-style-name@^1.0.3:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==

i18next-browser-languagedetector@^6.1.4:
version "6.1.8"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.8.tgz#8e9c61b32a4dfe9b959b38bc9d2a8b95f799b27c"
integrity sha512-Svm+MduCElO0Meqpj1kJAriTC6OhI41VhlT/A0UPjGoPZBhAHIaGE5EfsHlTpgdH09UVX7rcc72pSDDBeKSQQA==
dependencies:
"@babel/runtime" "^7.19.0"

i18next@^21.8.3:
version "21.10.0"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d"
integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==
dependencies:
"@babel/runtime" "^7.17.2"

iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
Expand Down Expand Up @@ -6130,6 +6151,15 @@ opencollective-postinstall@^2.0.2:
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==

opening_hours@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/opening_hours/-/opening_hours-3.8.0.tgz#e6d0a0bfd4e0f2cb8f62321e468dcaa8a798bd79"
integrity sha512-bRJroECQSe/itVcNmC3j9PPicxn/LBowdd1Hi+4Aa7hCswdt7w81WHfUwrEMbtk1BBYmGJEbSepl8oYYPviSuA==
dependencies:
i18next "^21.8.3"
i18next-browser-languagedetector "^6.1.4"
suncalc "^1.9.0"

optionator@^0.8.1:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
Expand Down Expand Up @@ -7006,11 +7036,6 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==

simple-opening-hours@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/simple-opening-hours/-/simple-opening-hours-0.1.1.tgz#f6059d8aaae63953bbcb4705ffe3250b5bee4d30"
integrity sha512-IbHhKCeh0AMUIdgTTGA6R3ZJIfvnDm/jml7+OCygoH3ZQTmxwTnq2ZwBc1dr9VK0XQbAAeBUBVfIRwGKRzdXhQ==

sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
Expand Down Expand Up @@ -7354,6 +7379,11 @@ stylis@4.3.2:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.2.tgz#8f76b70777dd53eb669c6f58c997bf0a9972e444"
integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==

suncalc@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/suncalc/-/suncalc-1.9.0.tgz#26212353fae61edb287c2d558fc4932ecf0e1532"
integrity sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==

supercluster@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5"
Expand Down

0 comments on commit ccfa83c

Please sign in to comment.