145 lines
3.7 KiB
TypeScript
145 lines
3.7 KiB
TypeScript
import { toISODate } from '../utils/format';
|
|
|
|
export type PeriodMode = 'week' | 'month' | 'year' | 'custom';
|
|
|
|
export interface PeriodState {
|
|
mode: PeriodMode;
|
|
from: string;
|
|
to: string;
|
|
}
|
|
|
|
interface Props {
|
|
period: PeriodState;
|
|
onChange: (period: PeriodState) => void;
|
|
}
|
|
|
|
const MODE_LABELS: Record<PeriodMode, string> = {
|
|
week: 'Неделя',
|
|
month: 'Месяц',
|
|
year: 'Год',
|
|
custom: 'Период',
|
|
};
|
|
|
|
export function PeriodSelector({ period, onChange }: Props) {
|
|
const setMode = (mode: PeriodMode) => {
|
|
const now = new Date();
|
|
let from: Date;
|
|
switch (mode) {
|
|
case 'week': {
|
|
const day = now.getDay();
|
|
const diff = day === 0 ? 6 : day - 1;
|
|
from = new Date(now);
|
|
from.setDate(now.getDate() - diff);
|
|
break;
|
|
}
|
|
case 'month':
|
|
from = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
break;
|
|
case 'year':
|
|
from = new Date(now.getFullYear(), 0, 1);
|
|
break;
|
|
case 'custom':
|
|
onChange({ mode, from: period.from, to: period.to });
|
|
return;
|
|
}
|
|
onChange({ mode, from: toISODate(from), to: toISODate(now) });
|
|
};
|
|
|
|
const navigate = (direction: -1 | 1) => {
|
|
const fromDate = new Date(period.from);
|
|
let newFrom: Date;
|
|
let newTo: Date;
|
|
|
|
switch (period.mode) {
|
|
case 'week':
|
|
newFrom = new Date(fromDate);
|
|
newFrom.setDate(fromDate.getDate() + 7 * direction);
|
|
newTo = new Date(newFrom);
|
|
newTo.setDate(newFrom.getDate() + 6);
|
|
break;
|
|
case 'month':
|
|
newFrom = new Date(
|
|
fromDate.getFullYear(),
|
|
fromDate.getMonth() + direction,
|
|
1,
|
|
);
|
|
newTo = new Date(
|
|
newFrom.getFullYear(),
|
|
newFrom.getMonth() + 1,
|
|
0,
|
|
);
|
|
break;
|
|
case 'year':
|
|
newFrom = new Date(fromDate.getFullYear() + direction, 0, 1);
|
|
newTo = new Date(newFrom.getFullYear(), 11, 31);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
onChange({
|
|
mode: period.mode,
|
|
from: toISODate(newFrom),
|
|
to: toISODate(newTo),
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="period-picker">
|
|
<div className="segmented-control">
|
|
{(['week', 'month', 'year', 'custom'] as PeriodMode[]).map(
|
|
(m) => (
|
|
<button
|
|
key={m}
|
|
className={`segmented-control__button ${period.mode === m ? 'segmented-control__button--active' : ''}`}
|
|
onClick={() => setMode(m)}
|
|
>
|
|
{MODE_LABELS[m]}
|
|
</button>
|
|
),
|
|
)}
|
|
</div>
|
|
|
|
<div className="period-picker__nav">
|
|
{period.mode !== 'custom' && (
|
|
<button
|
|
className="icon-button"
|
|
onClick={() => navigate(-1)}
|
|
aria-label="Предыдущий период"
|
|
title="Предыдущий период"
|
|
>
|
|
←
|
|
</button>
|
|
)}
|
|
<div className="period-picker__dates">
|
|
<input
|
|
type="date"
|
|
value={period.from}
|
|
onChange={(e) =>
|
|
onChange({ ...period, mode: 'custom', from: e.target.value })
|
|
}
|
|
/>
|
|
<span className="period-picker__separator">—</span>
|
|
<input
|
|
type="date"
|
|
value={period.to}
|
|
onChange={(e) =>
|
|
onChange({ ...period, mode: 'custom', to: e.target.value })
|
|
}
|
|
/>
|
|
</div>
|
|
{period.mode !== 'custom' && (
|
|
<button
|
|
className="icon-button"
|
|
onClick={() => navigate(1)}
|
|
aria-label="Следующий период"
|
|
title="Следующий период"
|
|
>
|
|
→
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|