Skip to content

Commit 5d37687

Browse files
authored
Merge pull request #57 from moevm/filter-look-update
better ui for filters, no behavior changes
2 parents e429b92 + 7c46039 commit 5d37687

8 files changed

Lines changed: 355 additions & 174 deletions

File tree

frontend/src/pages/ClusterHistoryPage.tsx

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button, TextInput, Select, Label } from "@gravity-ui/uikit";
55
import { useClusteringRuns } from "../entities/clustering/model/hooks";
66
import { runStatusLabels } from "../shared/config/ui";
77
import { dateFilterValue, matchesDateRange, matchesNumberRange, matchesText } from "../shared/lib/clientFilters";
8-
import { DateTimeIsoInput } from "../shared/ui/DateTimeIsoInput";
8+
import { FilterDateTimeRange, FilterFormField, FilterNumberRange } from "../shared/ui/FilterField";
99

1010
type SortField = "id" | "startedAt" | "algorithm" | "status";
1111

@@ -116,31 +116,30 @@ export function ClusterHistoryPage() {
116116
<span className="text-[13px]" style={{ fontWeight: 500 }}>Составной фильтр</span>
117117
{hasFilters && <button onClick={resetFilters} className="ml-auto flex items-center gap-1 text-[12px] text-muted-foreground hover:text-foreground"><X className="w-3 h-3" />Сбросить</button>}
118118
</div>
119-
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
120-
<TextInput placeholder="ID запуска" size="l" value={idFilter} onUpdate={setIdFilter} startContent={<Search className="w-3.5 h-3.5 text-muted-foreground" />} />
121-
<div className="grid grid-cols-2 gap-2 xl:col-span-2">
122-
<DateTimeIsoInput label="Начало от" value={startedFrom} onUpdate={setStartedFrom} />
123-
<DateTimeIsoInput label="Начало до" value={startedTo} onUpdate={setStartedTo} />
119+
<div className="space-y-4">
120+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
121+
<FilterFormField label="ID запуска">
122+
<TextInput placeholder="Поиск по ID" size="l" value={idFilter} onUpdate={setIdFilter} startContent={<Search className="w-3.5 h-3.5 text-muted-foreground" />} />
123+
</FilterFormField>
124+
<FilterFormField label="Алгоритм">
125+
<Select value={[algoFilter]} onUpdate={(v) => setAlgoFilter(v[0] ?? "all")} options={[{ value: "all", content: "Все алгоритмы" }, { value: "K-Means", content: "K-Means" }, { value: "DBSCAN", content: "DBSCAN" }]} size="l" width="max" />
126+
</FilterFormField>
127+
<FilterFormField label="Статус">
128+
<Select value={[statusFilter]} onUpdate={(v) => setStatusFilter(v[0] ?? "all")} options={[{ value: "all", content: "Все статусы" }, { value: "success", content: "Завершено" }, { value: "running", content: "Выполняется" }, { value: "error", content: "Ошибка" }]} size="l" width="max" />
129+
</FilterFormField>
130+
<FilterFormField label="Подмножество">
131+
<TextInput placeholder="Текст в описании выборки" size="l" value={subsetFilter} onUpdate={setSubsetFilter} />
132+
</FilterFormField>
124133
</div>
125-
<div className="grid grid-cols-2 gap-2 xl:col-span-2">
126-
<DateTimeIsoInput label="Конец от" value={finishedFrom} onUpdate={setFinishedFrom} />
127-
<DateTimeIsoInput label="Конец до" value={finishedTo} onUpdate={setFinishedTo} />
134+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
135+
<FilterDateTimeRange label="Начало запуска" from={startedFrom} to={startedTo} onFromChange={setStartedFrom} onToChange={setStartedTo} />
136+
<FilterDateTimeRange label="Окончание запуска" from={finishedFrom} to={finishedTo} onFromChange={setFinishedFrom} onToChange={setFinishedTo} />
128137
</div>
129-
<div className="grid grid-cols-2 gap-2">
130-
<input className="w-full h-10" type="number" placeholder="Длит. от, сек" value={durationMin} onChange={(event) => setDurationMin(event.target.value)} />
131-
<input className="w-full h-10" type="number" placeholder="Длит. до, сек" value={durationMax} onChange={(event) => setDurationMax(event.target.value)} />
138+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
139+
<FilterNumberRange label="Длительность, сек" from={durationMin} to={durationMax} onFromChange={setDurationMin} onToChange={setDurationMax} />
140+
<FilterNumberRange label="Кластеров" from={clustersMin} to={clustersMax} onFromChange={setClustersMin} onToChange={setClustersMax} />
141+
<FilterNumberRange label="Аномалий" from={anomaliesMin} to={anomaliesMax} onFromChange={setAnomaliesMin} onToChange={setAnomaliesMax} />
132142
</div>
133-
<Select value={[algoFilter]} onUpdate={(v) => setAlgoFilter(v[0] ?? "all")} options={[{ value: "all", content: "Все алгоритмы" }, { value: "K-Means", content: "K-Means" }, { value: "DBSCAN", content: "DBSCAN" }]} size="m" />
134-
<div className="grid grid-cols-2 gap-2">
135-
<input className="w-full h-10" type="number" placeholder="Кластеров от" value={clustersMin} onChange={(event) => setClustersMin(event.target.value)} />
136-
<input className="w-full h-10" type="number" placeholder="Кластеров до" value={clustersMax} onChange={(event) => setClustersMax(event.target.value)} />
137-
</div>
138-
<div className="grid grid-cols-2 gap-2">
139-
<input className="w-full h-10" type="number" placeholder="Аномалий от" value={anomaliesMin} onChange={(event) => setAnomaliesMin(event.target.value)} />
140-
<input className="w-full h-10" type="number" placeholder="Аномалий до" value={anomaliesMax} onChange={(event) => setAnomaliesMax(event.target.value)} />
141-
</div>
142-
<Select value={[statusFilter]} onUpdate={(v) => setStatusFilter(v[0] ?? "all")} options={[{ value: "all", content: "Все статусы" }, { value: "success", content: "Завершено" }, { value: "running", content: "Выполняется" }, { value: "error", content: "Ошибка" }]} size="m" />
143-
<TextInput placeholder="Подмножество" size="l" value={subsetFilter} onUpdate={setSubsetFilter} />
144143
</div>
145144
</div>
146145

frontend/src/pages/ProcessingPage.tsx

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type { ProcessingLogRow, ProcessingStageState, ProcessingState } from "..
1919
import { useClusteringRuns } from "../entities/clustering/model/hooks";
2020
import { formatDate, formatNumber } from "../shared/lib/format";
2121
import { dateFilterValue, matchesDateRange, matchesNumberRange } from "../shared/lib/clientFilters";
22-
import { DateTimeIsoInput } from "../shared/ui/DateTimeIsoInput";
22+
import { FilterDateTimeRange, FilterFormField, FilterNumberRange } from "../shared/ui/FilterField";
2323
import { getUploadStatusLabel } from "../shared/config/ui";
2424
import { isActiveProcessingStatus, isRetryableProcessingStatus } from "../entities/upload/model/adapters";
2525

@@ -339,50 +339,59 @@ export function ProcessingPage() {
339339
</button>
340340
)}
341341
</div>
342-
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
343-
<div className="grid grid-cols-2 gap-2 xl:col-span-2">
344-
<DateTimeIsoInput label="Время от" value={logTimeFrom} onUpdate={setLogTimeFrom} />
345-
<DateTimeIsoInput label="Время до" value={logTimeTo} onUpdate={setLogTimeTo} />
342+
<div className="space-y-4">
343+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
344+
<FilterFormField label="Поиск">
345+
<TextInput
346+
placeholder="Текст в сообщениях"
347+
size="l"
348+
value={logSearch}
349+
onUpdate={setLogSearch}
350+
startContent={<Search className="w-3.5 h-3.5 text-muted-foreground" />}
351+
/>
352+
</FilterFormField>
353+
<FilterFormField label="Уровень">
354+
<Select
355+
value={[logLevelFilter]}
356+
onUpdate={(value) => setLogLevelFilter(value[0] ?? "all")}
357+
options={[
358+
{ value: "all", content: "Все уровни" },
359+
{ value: "info", content: "info" },
360+
{ value: "warn", content: "warn" },
361+
{ value: "error", content: "error" },
362+
]}
363+
size="l"
364+
width="max"
365+
/>
366+
</FilterFormField>
367+
<FilterFormField label="Файл">
368+
<Select
369+
value={[logFileFilter]}
370+
onUpdate={(value) => setLogFileFilter(value[0] ?? "all")}
371+
options={[{ value: "all", content: "Все файлы" }, ...logFiles.map((file) => ({ value: file, content: file }))]}
372+
size="l"
373+
width="max"
374+
/>
375+
</FilterFormField>
376+
<FilterFormField label="Сущность">
377+
<Select
378+
value={[logEntityFilter]}
379+
onUpdate={(value) => setLogEntityFilter(value[0] ?? "all")}
380+
options={[
381+
{ value: "all", content: "Все сущности" },
382+
{ value: "student", content: "Студент" },
383+
{ value: "moodle", content: "Строка Moodle" },
384+
{ value: "camera", content: "Запись камеры" },
385+
]}
386+
size="l"
387+
width="max"
388+
/>
389+
</FilterFormField>
346390
</div>
347-
<Select
348-
value={[logLevelFilter]}
349-
onUpdate={(value) => setLogLevelFilter(value[0] ?? "all")}
350-
options={[
351-
{ value: "all", content: "Все уровни" },
352-
{ value: "info", content: "info" },
353-
{ value: "warn", content: "warn" },
354-
{ value: "error", content: "error" },
355-
]}
356-
size="m"
357-
/>
358-
<Select
359-
value={[logFileFilter]}
360-
onUpdate={(value) => setLogFileFilter(value[0] ?? "all")}
361-
options={[{ value: "all", content: "Все файлы" }, ...logFiles.map((file) => ({ value: file, content: file }))]}
362-
size="m"
363-
/>
364-
<div className="grid grid-cols-2 gap-2">
365-
<input className="w-full h-10" type="number" placeholder="Строка от" value={logLineMin} onChange={(event) => setLogLineMin(event.target.value)} />
366-
<input className="w-full h-10" type="number" placeholder="Строка до" value={logLineMax} onChange={(event) => setLogLineMax(event.target.value)} />
391+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
392+
<FilterDateTimeRange label="Время записи" from={logTimeFrom} to={logTimeTo} onFromChange={setLogTimeFrom} onToChange={setLogTimeTo} />
393+
<FilterNumberRange label="Строка" from={logLineMin} to={logLineMax} onFromChange={setLogLineMin} onToChange={setLogLineMax} />
367394
</div>
368-
<Select
369-
value={[logEntityFilter]}
370-
onUpdate={(value) => setLogEntityFilter(value[0] ?? "all")}
371-
options={[
372-
{ value: "all", content: "Все сущности" },
373-
{ value: "student", content: "Студент" },
374-
{ value: "moodle", content: "Строка Moodle" },
375-
{ value: "camera", content: "Запись камеры" },
376-
]}
377-
size="m"
378-
/>
379-
<TextInput
380-
placeholder="Поиск в сообщениях"
381-
size="l"
382-
value={logSearch}
383-
onUpdate={setLogSearch}
384-
startContent={<Search className="w-3.5 h-3.5 text-muted-foreground" />}
385-
/>
386395
</div>
387396
</div>
388397

frontend/src/pages/UploadHistoryPage.tsx

Lines changed: 50 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useRetryProcessing, useStopProcessing, useUploads } from "../entities/u
1919
import { getUploadStatusLabel } from "../shared/config/ui";
2020
import { api } from "../shared/api/client";
2121
import { dateFilterValue, matchesDateRange, matchesNumberRange, matchesText } from "../shared/lib/clientFilters";
22-
import { DateTimeIsoInput } from "../shared/ui/DateTimeIsoInput";
22+
import { FilterDateTimeRange, FilterFormField, FilterNumberRange } from "../shared/ui/FilterField";
2323

2424
type SortField = "id" | "date" | "author" | "status";
2525
type SortDir = "asc" | "desc";
@@ -174,58 +174,57 @@ export function UploadHistoryPage() {
174174
</button>
175175
)}
176176
</div>
177-
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
178-
<TextInput
179-
placeholder="ID загрузки"
180-
size="l"
181-
value={idFilter}
182-
onUpdate={setIdFilter}
183-
startContent={<Search className="w-3.5 h-3.5 text-muted-foreground" />}
184-
/>
185-
<div className="grid grid-cols-2 gap-2 xl:col-span-2">
186-
<DateTimeIsoInput label="Дата от" value={dateFrom} onUpdate={setDateFrom} />
187-
<DateTimeIsoInput label="Дата до" value={dateTo} onUpdate={setDateTo} />
177+
<div className="space-y-4">
178+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
179+
<FilterFormField label="ID загрузки">
180+
<TextInput
181+
placeholder="Поиск по ID"
182+
size="l"
183+
value={idFilter}
184+
onUpdate={setIdFilter}
185+
startContent={<Search className="w-3.5 h-3.5 text-muted-foreground" />}
186+
/>
187+
</FilterFormField>
188+
<FilterFormField label="Статус">
189+
<Select
190+
value={[statusFilter]}
191+
onUpdate={(v) => setStatusFilter(v[0] ?? "all")}
192+
options={[
193+
{ value: "all", content: "Все статусы" },
194+
{ value: "success", content: "Успешно" },
195+
{ value: "warning", content: "Предупреждения" },
196+
{ value: "error", content: "Ошибка" },
197+
{ value: "processing", content: "Обработка" },
198+
{ value: "cancelled", content: "Остановлено" },
199+
{ value: "stale", content: "Зависло" },
200+
]}
201+
size="l"
202+
width="max"
203+
/>
204+
</FilterFormField>
205+
<FilterFormField label="Автор">
206+
<Select
207+
value={[authorFilter]}
208+
onUpdate={(v) => setAuthorFilter(v[0] ?? "all")}
209+
options={[
210+
{ value: "all", content: "Все авторы" },
211+
...authors.map((a) => ({ value: a, content: a })),
212+
]}
213+
size="l"
214+
width="max"
215+
/>
216+
</FilterFormField>
217+
<FilterFormField label="Типы файлов">
218+
<TextInput placeholder="Например: moodle, camera" size="l" value={fileTypesFilter} onUpdate={setFileTypesFilter} />
219+
</FilterFormField>
188220
</div>
189-
<Select
190-
value={[statusFilter]}
191-
onUpdate={(v) => setStatusFilter(v[0] ?? "all")}
192-
options={[
193-
{ value: "all", content: "Все статусы" },
194-
{ value: "success", content: "Успешно" },
195-
{ value: "warning", content: "Предупреждения" },
196-
{ value: "error", content: "Ошибка" },
197-
{ value: "processing", content: "Обработка" },
198-
{ value: "cancelled", content: "Остановлено" },
199-
{ value: "stale", content: "Зависло" },
200-
]}
201-
size="l"
202-
/>
203-
<Select
204-
value={[authorFilter]}
205-
onUpdate={(v) => setAuthorFilter(v[0] ?? "all")}
206-
options={[
207-
{ value: "all", content: "Все авторы" },
208-
...authors.map((a) => ({ value: a, content: a })),
209-
]}
210-
size="l"
211-
/>
212-
<TextInput
213-
placeholder="Типы файлов"
214-
size="l"
215-
value={fileTypesFilter}
216-
onUpdate={setFileTypesFilter}
217-
/>
218-
<div className="grid grid-cols-2 gap-2">
219-
<input className="w-full h-10" type="number" placeholder="Файлов от" value={filesMin} onChange={(event) => setFilesMin(event.target.value)} />
220-
<input className="w-full h-10" type="number" placeholder="Файлов до" value={filesMax} onChange={(event) => setFilesMax(event.target.value)} />
221+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
222+
<FilterDateTimeRange label="Дата загрузки" from={dateFrom} to={dateTo} onFromChange={setDateFrom} onToChange={setDateTo} />
221223
</div>
222-
<div className="grid grid-cols-2 gap-2">
223-
<input className="w-full h-10" type="number" placeholder="Строк от" value={rowsMin} onChange={(event) => setRowsMin(event.target.value)} />
224-
<input className="w-full h-10" type="number" placeholder="Строк до" value={rowsMax} onChange={(event) => setRowsMax(event.target.value)} />
225-
</div>
226-
<div className="grid grid-cols-2 gap-2">
227-
<input className="w-full h-10" type="number" placeholder="Студентов от" value={studentsMin} onChange={(event) => setStudentsMin(event.target.value)} />
228-
<input className="w-full h-10" type="number" placeholder="Студентов до" value={studentsMax} onChange={(event) => setStudentsMax(event.target.value)} />
224+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
225+
<FilterNumberRange label="Файлов" from={filesMin} to={filesMax} onFromChange={setFilesMin} onToChange={setFilesMax} />
226+
<FilterNumberRange label="Строк" from={rowsMin} to={rowsMax} onFromChange={setRowsMin} onToChange={setRowsMax} />
227+
<FilterNumberRange label="Студентов" from={studentsMin} to={studentsMax} onFromChange={setStudentsMin} onToChange={setStudentsMax} />
229228
</div>
230229
</div>
231230
</div>

0 commit comments

Comments
 (0)