Улучшены datetimepicker элементы

This commit is contained in:
Первов Артем
2026-06-21 04:10:38 +03:00
parent 638eaea5fa
commit fe7d051d4a
2 changed files with 198 additions and 15 deletions

View File

@@ -1,5 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { CalendarClock, ChevronDown, DatabaseZap, Search } from 'lucide-react'; import type { RefObject } from 'react';
import { useRef } from 'react';
import { CalendarDays, CalendarClock, ChevronDown, Clock3, DatabaseZap, Search } from 'lucide-react';
import type { CurrentItem } from '../../../entities/current/types'; import type { CurrentItem } from '../../../entities/current/types';
import type { HistoryResponse } from '../../../entities/history/types'; import type { HistoryResponse } from '../../../entities/history/types';
import { HistoryChart } from '../../../features/history-chart/HistoryChart'; import { HistoryChart } from '../../../features/history-chart/HistoryChart';
@@ -27,6 +29,54 @@ type ArchiveViewProps = {
onToggleTag: (tag: string) => void; onToggleTag: (tag: string) => void;
}; };
type RangePart = 'from' | 'to';
type RangeInputPart = 'date' | 'time';
function getRangeInputPart(value: string, part: RangeInputPart): string {
const [date = '', time = ''] = value.split('T');
return part === 'date' ? date : time;
}
function updateRangeInputPart(value: string, part: RangeInputPart, nextValue: string): string {
const date = getRangeInputPart(value, 'date');
const time = getRangeInputPart(value, 'time');
return part === 'date' ? `${nextValue}T${time}` : `${date}T${nextValue}`;
}
function openInputPicker(input: HTMLInputElement | null): void {
input?.focus();
input?.showPicker?.();
}
type DateRangeFieldProps = {
icon: 'date' | 'time';
inputRef: RefObject<HTMLInputElement | null>;
type: 'date' | 'time';
value: string;
onChange: (value: string) => void;
};
function DateRangeField({ icon, inputRef, type, value, onChange }: DateRangeFieldProps) {
const Icon = icon === 'date' ? CalendarDays : Clock3;
const label = icon === 'date' ? 'Открыть календарь' : 'Открыть выбор времени';
return (
<div className="archive-date-field" onClick={() => openInputPicker(inputRef.current)}>
<button type="button" className="archive-date-picker-button" aria-label={label}>
<Icon size={16} />
</button>
<input
ref={inputRef}
aria-label={label}
className={`archive-date-input archive-date-input--${type}`}
type={type}
value={value}
onChange={(event) => onChange(event.target.value)}
/>
</div>
);
}
export function ArchiveView({ export function ArchiveView({
getTagLabel, getTagLabel,
history, history,
@@ -49,6 +99,16 @@ export function ArchiveView({
const [selectorOpen, setSelectorOpen] = useState(false); const [selectorOpen, setSelectorOpen] = useState(false);
const selectedPreview = selectedTags.slice(0, 10); const selectedPreview = selectedTags.slice(0, 10);
const hiddenSelectedCount = Math.max(0, selectedTags.length - selectedPreview.length); const hiddenSelectedCount = Math.max(0, selectedTags.length - selectedPreview.length);
const fromDateRef = useRef<HTMLInputElement>(null);
const fromTimeRef = useRef<HTMLInputElement>(null);
const toDateRef = useRef<HTMLInputElement>(null);
const toTimeRef = useRef<HTMLInputElement>(null);
const updateRangePart = (rangePart: RangePart, inputPart: RangeInputPart, value: string) => {
onRangeChange({
...range,
[rangePart]: updateRangeInputPart(range[rangePart], inputPart, value),
});
};
return ( return (
<section className="chart-section chart-section--archive"> <section className="chart-section chart-section--archive">
@@ -148,22 +208,40 @@ export function ArchiveView({
</button> </button>
))} ))}
</div> </div>
<label> <div className="archive-date-range">
С <span>С</span>
<input <DateRangeField
type="datetime-local" icon="date"
value={range.from} inputRef={fromDateRef}
onChange={(event) => onRangeChange({ ...range, from: event.target.value })} type="date"
value={getRangeInputPart(range.from, 'date')}
onChange={(value) => updateRangePart('from', 'date', value)}
/> />
</label> <DateRangeField
<label> icon="time"
По inputRef={fromTimeRef}
<input type="time"
type="datetime-local" value={getRangeInputPart(range.from, 'time')}
value={range.to} onChange={(value) => updateRangePart('from', 'time', value)}
onChange={(event) => onRangeChange({ ...range, to: event.target.value })}
/> />
</label> </div>
<div className="archive-date-range">
<span>По</span>
<DateRangeField
icon="date"
inputRef={toDateRef}
type="date"
value={getRangeInputPart(range.to, 'date')}
onChange={(value) => updateRangePart('to', 'date', value)}
/>
<DateRangeField
icon="time"
inputRef={toTimeRef}
type="time"
value={getRangeInputPart(range.to, 'time')}
onChange={(value) => updateRangePart('to', 'time', value)}
/>
</div>
</div> </div>
{selectedTags.length ? ( {selectedTags.length ? (

View File

@@ -181,6 +181,97 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
.archive-date-range {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: 0.84rem;
}
.archive-date-input {
color-scheme: dark;
min-height: 36px;
border: 0;
background: transparent;
color: var(--text);
font: inherit;
padding: 0;
cursor: text;
outline: none;
}
.archive-date-input--date {
width: 118px;
}
.archive-date-input--time {
width: 55px;
}
.archive-date-field {
min-height: 38px;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0 10px;
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: var(--radius);
background: rgba(15, 23, 42, 0.72);
cursor: pointer;
}
.archive-date-picker-button {
width: 28px;
height: 28px;
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
border: 1px solid rgba(201, 122, 61, 0.34);
border-radius: calc(var(--radius) - 4px);
background: rgba(201, 122, 61, 0.14);
color: var(--accent-soft);
cursor: pointer;
}
.archive-date-picker-button:hover,
.archive-date-picker-button:focus-visible {
border-color: rgba(201, 122, 61, 0.62);
background: rgba(201, 122, 61, 0.24);
color: #fed7aa;
outline: none;
}
.archive-date-field:hover,
.archive-date-field:focus-within {
border-color: var(--line-strong);
background: rgba(201, 122, 61, 0.14);
}
.archive-date-picker-button svg {
flex: 0 0 auto;
color: currentColor;
}
.archive-date-input::-webkit-calendar-picker-indicator {
opacity: 0;
width: 0;
margin: 0;
padding: 0;
}
.archive-date-input::-webkit-datetime-edit,
.archive-date-input::-webkit-datetime-edit-fields-wrapper,
.archive-date-input::-webkit-datetime-edit-text,
.archive-date-input::-webkit-datetime-edit-day-field,
.archive-date-input::-webkit-datetime-edit-month-field,
.archive-date-input::-webkit-datetime-edit-year-field,
.archive-date-input::-webkit-datetime-edit-hour-field,
.archive-date-input::-webkit-datetime-edit-minute-field {
color: var(--text);
}
@media (max-width: 1200px) { @media (max-width: 1200px) {
.tag-selector__header { .tag-selector__header {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -191,4 +282,18 @@
.tag-selector__tools { .tag-selector__tools {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.archive-date-range {
width: 100%;
}
.archive-date-field {
flex: 1;
}
.archive-date-input--date,
.archive-date-input--time {
width: 100%;
min-width: 0;
}
} }