diff --git a/components/common/Button.js b/components/common/Button.js
index 2bdbee5b..d72fb662 100644
--- a/components/common/Button.js
+++ b/components/common/Button.js
@@ -13,6 +13,8 @@ export default function Button({
className,
tooltip,
tooltipId,
+ disabled = false,
+ onClick = () => {},
...props
}) {
return (
@@ -27,7 +29,10 @@ export default function Button({
[styles.xsmall]: size === 'xsmall',
[styles.action]: variant === 'action',
[styles.danger]: variant === 'danger',
+ [styles.disabled]: disabled,
})}
+ disabled={disabled}
+ onClick={!disabled ? onClick : null}
{...props}
>
{icon && }
diff --git a/components/common/Button.module.css b/components/common/Button.module.css
index 4bf5e05a..99c7168f 100644
--- a/components/common/Button.module.css
+++ b/components/common/Button.module.css
@@ -14,7 +14,7 @@
}
.button:hover {
- background: #eaeaea;
+ background: var(--gray200);
}
.button:active {
@@ -38,19 +38,32 @@
}
.action {
- color: var(--gray50) !important;
- background: var(--gray900) !important;
+ color: var(--gray50);
+ background: var(--gray900);
}
.action:hover {
- background: var(--gray800) !important;
+ background: var(--gray800);
}
.danger {
- color: var(--gray50) !important;
- background: var(--red500) !important;
+ color: var(--gray50);
+ background: var(--red500);
}
.danger:hover {
- background: var(--red400) !important;
+ background: var(--red400);
+}
+
+.button:disabled {
+ color: var(--gray500);
+ background: var(--gray75);
+}
+
+.button:disabled:active {
+ color: var(--gray500);
+}
+
+.button:disabled:hover {
+ background: var(--gray75);
}
diff --git a/components/common/Calendar.js b/components/common/Calendar.js
new file mode 100644
index 00000000..8ecf76a8
--- /dev/null
+++ b/components/common/Calendar.js
@@ -0,0 +1,258 @@
+import React, { useState } from 'react';
+import classNames from 'classnames';
+import {
+ startOfWeek,
+ startOfMonth,
+ startOfYear,
+ endOfMonth,
+ addDays,
+ subDays,
+ addYears,
+ subYears,
+ addMonths,
+ setMonth,
+ setYear,
+ isSameDay,
+ isBefore,
+ isAfter,
+} from 'date-fns';
+import Button from './Button';
+import useLocale from 'hooks/useLocale';
+import { dateFormat } from 'lib/lang';
+import { chunk } from 'lib/array';
+import Chevron from 'assets/chevron-down.svg';
+import Cross from 'assets/times.svg';
+import styles from './Calendar.module.css';
+import Icon from './Icon';
+
+export default function Calendar({ date, minDate, maxDate, onChange }) {
+ const [locale] = useLocale();
+ const [selectMonth, setSelectMonth] = useState(false);
+ const [selectYear, setSelectYear] = useState(false);
+
+ const month = dateFormat(date, 'MMMM', locale);
+ const year = date.getFullYear();
+
+ function toggleMonthSelect() {
+ setSelectYear(false);
+ setSelectMonth(state => !state);
+ }
+
+ function toggleYearSelect() {
+ setSelectMonth(false);
+ setSelectYear(state => !state);
+ }
+
+ function handleChange(value) {
+ setSelectMonth(false);
+ setSelectYear(false);
+ if (value) {
+ onChange(value);
+ }
+ }
+
+ return (
+
+
+
{date.getDate()}
+
+ {month}
+ : } size="small" />
+
+
+ {year}
+ : } size="small" />
+
+
+ {!selectMonth && !selectYear && (
+
+ )}
+ {selectMonth && (
+
+ )}
+ {selectYear && (
+
+ )}
+
+ );
+}
+
+const DaySelector = ({ date, minDate, maxDate, locale, onSelect }) => {
+ const startWeek = startOfWeek(date);
+ const startMonth = startOfMonth(date);
+ const startDay = subDays(startMonth, startMonth.getDay() + 1);
+ const month = date.getMonth();
+ const year = date.getFullYear();
+
+ const daysOfWeek = [];
+ for (let i = 0; i < 7; i++) {
+ daysOfWeek.push(addDays(startWeek, i));
+ }
+
+ const days = [];
+ for (let i = 0; i < 35; i++) {
+ days.push(addDays(startDay, i));
+ }
+
+ return (
+
+
+
+ {daysOfWeek.map((day, i) => (
+
+ {dateFormat(day, 'EEE', locale)}
+ |
+ ))}
+
+
+
+ {chunk(days, 7).map((week, i) => (
+
+ {week.map((day, j) => {
+ const disabled = isBefore(day, minDate) || isAfter(day, maxDate);
+ return (
+ onSelect(day) : null}
+ >
+ {day.getDate()}
+ |
+ );
+ })}
+
+ ))}
+
+
+ );
+};
+
+const MonthSelector = ({ date, minDate, maxDate, locale, onSelect }) => {
+ const start = startOfYear(date);
+ const months = [];
+ for (let i = 0; i < 12; i++) {
+ months.push(endOfMonth(addMonths(start, i)));
+ }
+
+ function handleSelect(value) {
+ onSelect(setMonth(date, value));
+ }
+
+ return (
+
+
+ {chunk(months, 3).map((row, i) => (
+
+ {row.map((month, j) => {
+ const disabled = isBefore(month, minDate) || isAfter(month, maxDate);
+ return (
+ handleSelect(month.getMonth()) : null}
+ >
+ {dateFormat(month, 'MMMM', locale)}
+ |
+ );
+ })}
+
+ ))}
+
+
+ );
+};
+
+const YearSelector = ({ date, minDate, maxDate, onSelect }) => {
+ const [currentDate, setCurrentDate] = useState(date);
+ const year = date.getFullYear();
+ const currentYear = currentDate.getFullYear();
+ const minYear = minDate.getFullYear();
+ const maxYear = maxDate.getFullYear();
+ const years = [];
+ for (let i = 0; i < 15; i++) {
+ years.push(currentYear - 7 + i);
+ }
+
+ function handleSelect(value) {
+ onSelect(setYear(date, value));
+ }
+
+ function handlePrevClick() {
+ setCurrentDate(state => subYears(state, 15));
+ }
+
+ function handleNextClick() {
+ setCurrentDate(state => addYears(state, 15));
+ }
+
+ return (
+
+
}
+ size="xsmall"
+ className={styles.left}
+ onClick={handlePrevClick}
+ disabled={years[0] <= minYear}
+ />
+
+
+ {chunk(years, 5).map((row, i) => (
+
+ {row.map((n, j) => (
+ maxYear,
+ })}
+ onClick={() => (n < minYear || n > maxYear ? null : handleSelect(n))}
+ >
+ {n}
+ |
+ ))}
+
+ ))}
+
+
+
}
+ size="xsmall"
+ className={styles.right}
+ onClick={handleNextClick}
+ disabled={years[years.length - 1] > maxYear}
+ />
+
+ );
+};
diff --git a/components/common/Calendar.module.css b/components/common/Calendar.module.css
new file mode 100644
index 00000000..ccfde683
--- /dev/null
+++ b/components/common/Calendar.module.css
@@ -0,0 +1,84 @@
+.calendar {
+ display: flex;
+ flex-direction: column;
+ font-size: var(--font-size-small);
+ flex: 1;
+}
+
+.calendar table {
+ flex: 1;
+}
+
+.calendar td {
+ color: var(--gray800);
+ cursor: pointer;
+ text-align: center;
+ vertical-align: center;
+ height: 40px;
+ min-width: 40px;
+ border-radius: 5px;
+}
+
+.calendar td:hover {
+ background: var(--gray100);
+}
+
+.calendar td.faded {
+ color: var(--gray500);
+}
+
+.calendar td.selected {
+ font-weight: 600;
+ border: 1px solid var(--gray600);
+}
+
+.calendar td.selected:hover {
+ background: transparent;
+}
+
+.calendar td.disabled {
+ color: var(--gray300);
+ background: var(--gray75);
+}
+
+.calendar td.disabled:hover {
+ cursor: default;
+ background: var(--gray75);
+}
+
+.calendar td.faded.disabled {
+ color: var(--gray200);
+}
+
+.header {
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ font-weight: 700;
+ line-height: 40px;
+ font-size: var(--font-size-normal);
+}
+
+.selector {
+ cursor: pointer;
+}
+
+.pager {
+ display: flex;
+}
+
+.pager button {
+ align-self: center;
+}
+
+.left svg {
+ transform: rotate(90deg);
+}
+
+.right svg {
+ transform: rotate(-90deg);
+}
+
+.icon {
+ margin-left: 10px;
+}
diff --git a/components/common/DateFilter.js b/components/common/DateFilter.js
index aaba8725..a7ac6372 100644
--- a/components/common/DateFilter.js
+++ b/components/common/DateFilter.js
@@ -1,7 +1,12 @@
-import React from 'react';
-import { getDateRange } from 'lib/date';
-import DropDown from './DropDown';
+import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
+import { endOfYear } from 'date-fns';
+import Modal from './Modal';
+import DropDown from './DropDown';
+import DatePickerForm from 'components/forms/DatePickerForm';
+import useLocale from 'hooks/useLocale';
+import { getDateRange } from 'lib/date';
+import { dateFormat } from 'lib/lang';
const filterOptions = [
{
@@ -35,14 +40,53 @@ const filterOptions = [
value: '1month',
},
{ label: , value: '1year' },
+ {
+ label: ,
+ value: 'custom',
+ },
];
-export default function DateFilter({ value, onChange, className }) {
+export default function DateFilter({ value, startDate, endDate, onChange, className }) {
+ const [locale] = useLocale();
+ const [showPicker, setShowPicker] = useState(false);
+ const displayValue =
+ value === 'custom'
+ ? `${dateFormat(startDate, 'd LLL y', locale)} — ${dateFormat(endDate, 'd LLL y', locale)}`
+ : value;
+
function handleChange(value) {
+ if (value === 'custom') {
+ setShowPicker(true);
+ return;
+ }
onChange(getDateRange(value));
}
+ function handlePickerChange(value) {
+ setShowPicker(false);
+ onChange(value);
+ }
+
return (
-
+ <>
+
+ {showPicker && (
+
+ setShowPicker(false)}
+ />
+
+ )}
+ >
);
}
diff --git a/components/common/DropDown.js b/components/common/DropDown.js
index 8f1ca4f9..03fa8bb6 100644
--- a/components/common/DropDown.js
+++ b/components/common/DropDown.js
@@ -23,9 +23,8 @@ export default function DropDown({
function handleSelect(selected, e) {
e.stopPropagation();
setShowMenu(false);
- if (selected !== value) {
- onChange(selected);
- }
+
+ onChange(selected);
}
useDocumentClick(e => {
@@ -37,7 +36,7 @@ export default function DropDown({
return (
- {options.find(e => e.value === value)?.label}
+
{options.find(e => e.value === value)?.label || value}
} size="small" />
{showMenu && (
diff --git a/components/common/Dropdown.module.css b/components/common/Dropdown.module.css
index ec63552f..958305e7 100644
--- a/components/common/Dropdown.module.css
+++ b/components/common/Dropdown.module.css
@@ -1,16 +1,15 @@
.dropdown {
+ flex: 1;
position: relative;
- font-size: var(--font-size-small);
- min-width: 140px;
+ border: 1px solid var(--gray500);
+ border-radius: 4px;
+ cursor: pointer;
}
.value {
display: flex;
justify-content: space-between;
- white-space: nowrap;
- position: relative;
+ font-size: var(--font-size-small);
+ min-width: 140px;
padding: 4px 16px;
- border: 1px solid var(--gray500);
- border-radius: 4px;
- cursor: pointer;
}
diff --git a/components/common/Icon.js b/components/common/Icon.js
index 1b6fd1d9..8a794f61 100644
--- a/components/common/Icon.js
+++ b/components/common/Icon.js
@@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import styles from './Icon.module.css';
-export default function Icon({ icon, className, size = 'medium' }) {
+export default function Icon({ icon, className, size = 'medium', ...props }) {
return (
{icon}
diff --git a/components/common/LanguageButton.js b/components/common/LanguageButton.js
index 74c55ba3..714ae7f6 100644
--- a/components/common/LanguageButton.js
+++ b/components/common/LanguageButton.js
@@ -35,7 +35,7 @@ export default function LanguageButton({ menuPosition = 'bottom', menuAlign = 'l
rel="stylesheet"
/>
)}
- {locale === 'jp-JP' && (
+ {locale === 'ja-JP' && (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/forms/DatePickerForm.module.css b/components/forms/DatePickerForm.module.css
new file mode 100644
index 00000000..dcedc17a
--- /dev/null
+++ b/components/forms/DatePickerForm.module.css
@@ -0,0 +1,25 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ width: 800px;
+ max-width: 100vw;
+}
+
+.calendars {
+ display: flex;
+}
+
+.calendars > div:first-child {
+ padding-right: 20px;
+ border-right: 1px solid var(--gray300);
+}
+
+.calendars > div:last-child {
+ padding-left: 20px;
+}
+
+@media only screen and (max-width: 768px) {
+ .calendars {
+ flex-direction: column;
+ }
+}
diff --git a/components/forms/DeleteForm.js b/components/forms/DeleteForm.js
index 1ba81626..f53b286f 100644
--- a/components/forms/DeleteForm.js
+++ b/components/forms/DeleteForm.js
@@ -10,10 +10,12 @@ import FormLayout, {
} from 'components/layout/FormLayout';
import { FormattedMessage } from 'react-intl';
+const CONFIRMATION_WORD = 'DELETE';
+
const validate = ({ confirmation }) => {
const errors = {};
- if (confirmation !== 'DELETE') {
+ if (confirmation !== CONFIRMATION_WORD) {
errors.confirmation = !confirmation ? (
) : (
@@ -44,7 +46,7 @@ export default function DeleteForm({ values, onSave, onClose }) {
validate={validate}
onSubmit={handleSubmit}
>
- {() => (
+ {props => (