mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-09-07 00:17:24 +08:00
feat: 筛选器支持根据创建时间筛选 (#743)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use bili_sync_entity::rule::Rule;
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
@@ -29,6 +30,8 @@ pub struct VideosRequest {
|
||||
pub query: Option<String>,
|
||||
pub status_filter: Option<StatusFilter>,
|
||||
pub validation_filter: Option<ValidationFilter>,
|
||||
pub created_from: Option<NaiveDateTime>,
|
||||
pub created_to: Option<NaiveDateTime>,
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
@@ -48,6 +51,8 @@ pub struct ResetFilteredVideoStatusRequest {
|
||||
pub query: Option<String>,
|
||||
pub status_filter: Option<StatusFilter>,
|
||||
pub validation_filter: Option<ValidationFilter>,
|
||||
pub created_from: Option<NaiveDateTime>,
|
||||
pub created_to: Option<NaiveDateTime>,
|
||||
#[serde(default)]
|
||||
pub force: bool,
|
||||
}
|
||||
@@ -86,6 +91,8 @@ pub struct UpdateFilteredVideoStatusRequest {
|
||||
pub query: Option<String>,
|
||||
pub status_filter: Option<StatusFilter>,
|
||||
pub validation_filter: Option<ValidationFilter>,
|
||||
pub created_from: Option<NaiveDateTime>,
|
||||
pub created_to: Option<NaiveDateTime>,
|
||||
#[serde(default)]
|
||||
#[validate(nested)]
|
||||
pub video_updates: Vec<StatusUpdate>,
|
||||
|
||||
@@ -5,10 +5,12 @@ use axum::extract::{Extension, Path, Query};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use bili_sync_entity::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use sea_orm::sea_query::{Expr, ExprTrait};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, PaginatorTrait, QueryFilter,
|
||||
QueryOrder, TransactionTrait, TryIntoModel,
|
||||
QueryOrder, Select, TransactionTrait, TryIntoModel,
|
||||
};
|
||||
|
||||
use crate::api::error::InnerApiError;
|
||||
@@ -39,6 +41,26 @@ pub(super) fn router() -> Router {
|
||||
.route("/videos/update-status", post(update_filtered_video_status))
|
||||
}
|
||||
|
||||
fn apply_created_at_filter(
|
||||
mut query: Select<video::Entity>,
|
||||
created_from: Option<NaiveDateTime>,
|
||||
created_to: Option<NaiveDateTime>,
|
||||
) -> Select<video::Entity> {
|
||||
if let Some(created_from) = created_from {
|
||||
query = query.filter(
|
||||
Expr::cust_with_expr("datetime(?, 'localtime')", Expr::col(video::Column::CreatedAt))
|
||||
.gte(created_from.format("%Y-%m-%d %H:%M:%S").to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(created_to) = created_to {
|
||||
query = query.filter(
|
||||
Expr::cust_with_expr("datetime(?, 'localtime')", Expr::col(video::Column::CreatedAt))
|
||||
.lte(created_to.format("%Y-%m-%d %H:%M:%S").to_string()),
|
||||
);
|
||||
}
|
||||
query
|
||||
}
|
||||
|
||||
/// 列出视频的基本信息,支持根据视频来源筛选、名称查找和分页
|
||||
pub async fn get_videos(
|
||||
Extension(db): Extension<DatabaseConnection>,
|
||||
@@ -68,6 +90,7 @@ pub async fn get_videos(
|
||||
if let Some(validation_filter) = params.validation_filter {
|
||||
query = query.filter(validation_filter.to_video_query());
|
||||
}
|
||||
query = apply_created_at_filter(query, params.created_from, params.created_to);
|
||||
let total_count = query.clone().count(&db).await?;
|
||||
let (page, page_size) = if let (Some(page), Some(page_size)) = (params.page, params.page_size) {
|
||||
(page, page_size)
|
||||
@@ -240,6 +263,7 @@ pub async fn reset_filtered_video_status(
|
||||
if let Some(validation_filter) = request.validation_filter {
|
||||
query = query.filter(validation_filter.to_video_query());
|
||||
}
|
||||
query = apply_created_at_filter(query, request.created_from, request.created_to);
|
||||
let all_videos = query.into_partial_model::<SimpleVideoInfo>().all(&db).await?;
|
||||
let all_pages = page::Entity::find()
|
||||
.filter(page::Column::VideoId.is_in(all_videos.iter().map(|v| v.id)))
|
||||
@@ -379,6 +403,7 @@ pub async fn update_filtered_video_status(
|
||||
if let Some(validation_filter) = request.validation_filter {
|
||||
query = query.filter(validation_filter.to_video_query());
|
||||
}
|
||||
query = apply_created_at_filter(query, request.created_from, request.created_to);
|
||||
let mut all_videos = query.into_partial_model::<SimpleVideoInfo>().all(&db).await?;
|
||||
let mut all_pages = page::Entity::find()
|
||||
.filter(page::Column::VideoId.is_in(all_videos.iter().map(|v| v.id)))
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
<script lang="ts">
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
import EraserIcon from '@lucide/svelte/icons/eraser';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import * as Popover from '$lib/components/ui/popover/index.js';
|
||||
|
||||
interface Props {
|
||||
createdFrom: string | null;
|
||||
createdTo: string | null;
|
||||
onChange?: (createdFrom: string | null, createdTo: string | null) => void;
|
||||
}
|
||||
|
||||
let { createdFrom, createdTo, onChange }: Props = $props();
|
||||
|
||||
const id = $props.id();
|
||||
let open = $state(false);
|
||||
let draftCreatedFrom = $state('');
|
||||
let draftCreatedTo = $state('');
|
||||
|
||||
const invalidRange = $derived(
|
||||
Boolean(draftCreatedFrom && draftCreatedTo && draftCreatedFrom > draftCreatedTo)
|
||||
);
|
||||
const displayValue = $derived.by(() => {
|
||||
const from = createdFrom?.slice(0, 10);
|
||||
const to = createdTo?.slice(0, 10);
|
||||
if (from && to) return `${from} – ${to}`;
|
||||
if (from) return `${from} 起`;
|
||||
if (to) return `截至 ${to}`;
|
||||
return '未应用';
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!open) {
|
||||
draftCreatedFrom = createdFrom || '';
|
||||
draftCreatedTo = createdTo || '';
|
||||
}
|
||||
});
|
||||
|
||||
function apply() {
|
||||
if (invalidRange) return;
|
||||
onChange?.(
|
||||
draftCreatedFrom ? normalizeDateTime(draftCreatedFrom) : null,
|
||||
draftCreatedTo ? normalizeDateTime(draftCreatedTo) : null
|
||||
);
|
||||
open = false;
|
||||
}
|
||||
|
||||
function normalizeDateTime(value: string) {
|
||||
return value.length === 16 ? `${value}:00` : value;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
draftCreatedFrom = '';
|
||||
draftCreatedTo = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="inline-flex items-center gap-1">
|
||||
<span
|
||||
class="bg-secondary text-secondary-foreground max-w-52 truncate rounded-lg px-2 py-1 text-xs font-medium"
|
||||
title={displayValue}
|
||||
>
|
||||
{displayValue}
|
||||
</span>
|
||||
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="ghost" size="sm" {...props} class="h-6 w-6 p-0">
|
||||
<ChevronDownIcon class="h-3 w-3" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-88" align="end">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm leading-none font-medium">创建时间</h4>
|
||||
<p class="text-muted-foreground mt-1.5 text-xs">可只设置一侧,时间按服务器时区解释</p>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label for={`${id}-created-from`} class="text-xs">开始时间</Label>
|
||||
<Input
|
||||
id={`${id}-created-from`}
|
||||
type="datetime-local"
|
||||
class="text-xs"
|
||||
bind:value={draftCreatedFrom}
|
||||
max={draftCreatedTo || undefined}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label for={`${id}-created-to`} class="text-xs">结束时间</Label>
|
||||
<Input
|
||||
id={`${id}-created-to`}
|
||||
type="datetime-local"
|
||||
class="text-xs"
|
||||
bind:value={draftCreatedTo}
|
||||
min={draftCreatedFrom || undefined}
|
||||
/>
|
||||
</div>
|
||||
{#if invalidRange}
|
||||
<p class="text-destructive text-xs">结束时间不能早于开始时间</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="text-muted-foreground px-2 text-xs"
|
||||
disabled={!draftCreatedFrom && !draftCreatedTo}
|
||||
onclick={clear}
|
||||
>
|
||||
<EraserIcon class="size-3" />
|
||||
清空选择
|
||||
</Button>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="sm" class="text-xs" onclick={() => (open = false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button size="sm" class="text-xs" disabled={invalidRange} onclick={apply}>应用</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</div>
|
||||
@@ -12,6 +12,8 @@ export interface AppState {
|
||||
} | null;
|
||||
statusFilter: StatusFilterValue | null;
|
||||
validationFilter: ValidationFilterValue | null;
|
||||
createdFrom: string | null;
|
||||
createdTo: string | null;
|
||||
}
|
||||
|
||||
export const appStateStore = writable<AppState>({
|
||||
@@ -19,11 +21,21 @@ export const appStateStore = writable<AppState>({
|
||||
currentPage: 0,
|
||||
videoSource: null,
|
||||
statusFilter: null,
|
||||
validationFilter: 'normal'
|
||||
validationFilter: 'normal',
|
||||
createdFrom: null,
|
||||
createdTo: null
|
||||
});
|
||||
|
||||
export const ToQuery = (state: AppState): string => {
|
||||
const { query, videoSource, currentPage, statusFilter, validationFilter } = state;
|
||||
const {
|
||||
query,
|
||||
videoSource,
|
||||
currentPage,
|
||||
statusFilter,
|
||||
validationFilter,
|
||||
createdFrom,
|
||||
createdTo
|
||||
} = state;
|
||||
const params = new URLSearchParams();
|
||||
if (currentPage > 0) {
|
||||
params.set('page', String(currentPage));
|
||||
@@ -40,6 +52,12 @@ export const ToQuery = (state: AppState): string => {
|
||||
if (validationFilter) {
|
||||
params.set('validation_filter', validationFilter);
|
||||
}
|
||||
if (createdFrom) {
|
||||
params.set('created_from', createdFrom);
|
||||
}
|
||||
if (createdTo) {
|
||||
params.set('created_to', createdTo);
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return queryString ? `videos?${queryString}` : 'videos';
|
||||
};
|
||||
@@ -55,6 +73,8 @@ export const ToFilterParams = (
|
||||
watch_later?: number;
|
||||
status_filter?: Exclude<StatusFilterValue, null>;
|
||||
validation_filter?: Exclude<ValidationFilterValue, null>;
|
||||
created_from?: string;
|
||||
created_to?: string;
|
||||
} => {
|
||||
const params: {
|
||||
query?: string;
|
||||
@@ -64,6 +84,8 @@ export const ToFilterParams = (
|
||||
watch_later?: number;
|
||||
status_filter?: Exclude<StatusFilterValue, null>;
|
||||
validation_filter?: Exclude<ValidationFilterValue, null>;
|
||||
created_from?: string;
|
||||
created_to?: string;
|
||||
} = {};
|
||||
|
||||
if (state.query.trim()) {
|
||||
@@ -80,6 +102,12 @@ export const ToFilterParams = (
|
||||
if (state.validationFilter) {
|
||||
params.validation_filter = state.validationFilter;
|
||||
}
|
||||
if (state.createdFrom) {
|
||||
params.created_from = state.createdFrom;
|
||||
}
|
||||
if (state.createdTo) {
|
||||
params.created_to = state.createdTo;
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
@@ -89,7 +117,9 @@ export const hasActiveFilters = (state: AppState): boolean => {
|
||||
state.query.trim() ||
|
||||
state.videoSource ||
|
||||
state.statusFilter ||
|
||||
state.validationFilter
|
||||
state.validationFilter ||
|
||||
state.createdFrom ||
|
||||
state.createdTo
|
||||
);
|
||||
};
|
||||
|
||||
@@ -121,6 +151,14 @@ export const setValidationFilter = (validationFilter: ValidationFilterValue | nu
|
||||
}));
|
||||
};
|
||||
|
||||
export const setCreatedTimeFilter = (createdFrom: string | null, createdTo: string | null) => {
|
||||
appStateStore.update((state) => ({
|
||||
...state,
|
||||
createdFrom,
|
||||
createdTo
|
||||
}));
|
||||
};
|
||||
|
||||
export const resetCurrentPage = () => {
|
||||
appStateStore.update((state) => ({
|
||||
...state,
|
||||
@@ -133,13 +171,17 @@ export const setAll = (
|
||||
currentPage: number,
|
||||
videoSource: { type: string; id: string } | null,
|
||||
statusFilter: StatusFilterValue | null,
|
||||
validationFilter: ValidationFilterValue | null = 'normal'
|
||||
validationFilter: ValidationFilterValue | null = 'normal',
|
||||
createdFrom: string | null = null,
|
||||
createdTo: string | null = null
|
||||
) => {
|
||||
appStateStore.set({
|
||||
query,
|
||||
currentPage,
|
||||
videoSource,
|
||||
statusFilter,
|
||||
validationFilter
|
||||
validationFilter,
|
||||
createdFrom,
|
||||
createdTo
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface VideosRequest {
|
||||
query?: string;
|
||||
status_filter?: 'failed' | 'succeeded' | 'waiting';
|
||||
validation_filter?: 'skipped' | 'invalid' | 'normal';
|
||||
created_from?: string;
|
||||
created_to?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
@@ -124,6 +126,8 @@ export interface UpdateFilteredVideoStatusRequest {
|
||||
query?: string;
|
||||
status_filter?: 'failed' | 'succeeded' | 'waiting';
|
||||
validation_filter?: 'skipped' | 'invalid' | 'normal';
|
||||
created_from?: string;
|
||||
created_to?: string;
|
||||
video_updates?: StatusUpdate[];
|
||||
page_updates?: StatusUpdate[];
|
||||
}
|
||||
@@ -140,6 +144,8 @@ export interface ResetFilteredVideoStatusRequest {
|
||||
query?: string;
|
||||
status_filter?: 'failed' | 'succeeded' | 'waiting';
|
||||
validation_filter?: 'skipped' | 'invalid' | 'normal';
|
||||
created_from?: string;
|
||||
created_to?: string;
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
resetCurrentPage,
|
||||
setAll,
|
||||
setCurrentPage,
|
||||
setCreatedTimeFilter,
|
||||
setQuery,
|
||||
setStatusFilter,
|
||||
setValidationFilter,
|
||||
@@ -41,6 +42,7 @@
|
||||
import FilteredStatusEditor from '$lib/components/filtered-status-editor.svelte';
|
||||
import StatusFilter from '$lib/components/status-filter.svelte';
|
||||
import ValidationFilter from '$lib/components/validation-filter.svelte';
|
||||
import CreatedTimeFilter from '$lib/components/created-time-filter.svelte';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
|
||||
const pageSize = 20;
|
||||
@@ -91,6 +93,8 @@
|
||||
videoSource,
|
||||
statusFilter,
|
||||
validationFilter,
|
||||
createdFrom: searchParams.get('created_from'),
|
||||
createdTo: searchParams.get('created_to'),
|
||||
pageNum: parseInt(searchParams.get('page') || '0')
|
||||
};
|
||||
}
|
||||
@@ -100,7 +104,9 @@
|
||||
pageNum: number = 0,
|
||||
filter?: { type: string; id: string } | null,
|
||||
statusFilter: StatusFilterValue | null = null,
|
||||
validationFilter: ValidationFilterValue | null = null
|
||||
validationFilter: ValidationFilterValue | null = null,
|
||||
createdFrom: string | null = null,
|
||||
createdTo: string | null = null
|
||||
) {
|
||||
loading = true;
|
||||
try {
|
||||
@@ -120,6 +126,12 @@
|
||||
if (validationFilter) {
|
||||
params.validation_filter = validationFilter;
|
||||
}
|
||||
if (createdFrom) {
|
||||
params.created_from = createdFrom;
|
||||
}
|
||||
if (createdTo) {
|
||||
params.created_to = createdTo;
|
||||
}
|
||||
const result = await api.getVideos(params);
|
||||
videosData = result.data;
|
||||
} catch (error) {
|
||||
@@ -132,16 +144,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadVideos() {
|
||||
const {
|
||||
query,
|
||||
currentPage,
|
||||
videoSource,
|
||||
statusFilter,
|
||||
validationFilter,
|
||||
createdFrom,
|
||||
createdTo
|
||||
} = $appStateStore;
|
||||
await loadVideos(
|
||||
query,
|
||||
currentPage,
|
||||
videoSource,
|
||||
statusFilter,
|
||||
validationFilter,
|
||||
createdFrom,
|
||||
createdTo
|
||||
);
|
||||
}
|
||||
|
||||
async function handlePageChange(pageNum: number) {
|
||||
setCurrentPage(pageNum);
|
||||
goto(`/${ToQuery($appStateStore)}`);
|
||||
}
|
||||
|
||||
async function handleSearchParamsChange(searchParams: URLSearchParams) {
|
||||
const { query, videoSource, pageNum, statusFilter, validationFilter } =
|
||||
const { query, videoSource, pageNum, statusFilter, validationFilter, createdFrom, createdTo } =
|
||||
getApiParams(searchParams);
|
||||
setAll(query, pageNum, videoSource, statusFilter, validationFilter);
|
||||
loadVideos(query, pageNum, videoSource, statusFilter, validationFilter);
|
||||
setAll(query, pageNum, videoSource, statusFilter, validationFilter, createdFrom, createdTo);
|
||||
loadVideos(query, pageNum, videoSource, statusFilter, validationFilter, createdFrom, createdTo);
|
||||
}
|
||||
|
||||
async function handleResetVideo(id: number, forceReset: boolean) {
|
||||
@@ -152,8 +185,7 @@
|
||||
toast.success('重置成功', {
|
||||
description: `视频「${data.video.name}」已重置`
|
||||
});
|
||||
const { query, currentPage, videoSource, statusFilter, validationFilter } = $appStateStore;
|
||||
await loadVideos(query, currentPage, videoSource, statusFilter, validationFilter);
|
||||
await reloadVideos();
|
||||
} else {
|
||||
toast.info('重置无效', {
|
||||
description: `视频「${data.video.name}」没有失败的状态,无需重置`
|
||||
@@ -180,8 +212,7 @@
|
||||
description: `视频「${data.video.name}」已清空重置`
|
||||
});
|
||||
}
|
||||
const { query, currentPage, videoSource, statusFilter, validationFilter } = $appStateStore;
|
||||
await loadVideos(query, currentPage, videoSource, statusFilter, validationFilter);
|
||||
await reloadVideos();
|
||||
} catch (error) {
|
||||
console.error('清空重置失败:', error);
|
||||
toast.error('清空重置失败', {
|
||||
@@ -204,8 +235,7 @@
|
||||
toast.success('重置成功', {
|
||||
description: `已重置 ${data.resetted_videos_count} 个视频和 ${data.resetted_pages_count} 个分页`
|
||||
});
|
||||
const { query, currentPage, videoSource, statusFilter, validationFilter } = $appStateStore;
|
||||
await loadVideos(query, currentPage, videoSource, statusFilter, validationFilter);
|
||||
await reloadVideos();
|
||||
} else {
|
||||
toast.info('没有需要重置的视频');
|
||||
}
|
||||
@@ -235,8 +265,7 @@
|
||||
toast.success('更新成功', {
|
||||
description: `已更新 ${data.updated_videos_count} 个视频和 ${data.updated_pages_count} 个分页`
|
||||
});
|
||||
const { query, currentPage, videoSource, statusFilter, validationFilter } = $appStateStore;
|
||||
await loadVideos(query, currentPage, videoSource, statusFilter, validationFilter);
|
||||
await reloadVideos();
|
||||
} else {
|
||||
toast.info('没有视频被更新');
|
||||
}
|
||||
@@ -302,6 +331,11 @@
|
||||
};
|
||||
parts.push(`有效性:${validationLabels[state.validationFilter]}`);
|
||||
}
|
||||
if (state.createdFrom || state.createdTo) {
|
||||
const createdFrom = state.createdFrom?.replace('T', ' ') || '不限';
|
||||
const createdTo = state.createdTo?.replace('T', ' ') || '不限';
|
||||
parts.push(`创建时间:${createdFrom} 至 ${createdTo}`);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
@@ -370,6 +404,18 @@
|
||||
}}
|
||||
></SearchBar>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-muted-foreground text-xs">创建时间:</span>
|
||||
<CreatedTimeFilter
|
||||
createdFrom={$appStateStore.createdFrom}
|
||||
createdTo={$appStateStore.createdTo}
|
||||
onChange={(createdFrom, createdTo) => {
|
||||
setCreatedTimeFilter(createdFrom, createdTo);
|
||||
resetCurrentPage();
|
||||
goto(`/${ToQuery($appStateStore)}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-muted-foreground text-xs">有效性:</span>
|
||||
<ValidationFilter
|
||||
@@ -410,11 +456,27 @@
|
||||
{filters}
|
||||
selectedLabel={$appStateStore.videoSource}
|
||||
onSelect={(type, id) => {
|
||||
setAll('', 0, { type, id }, $appStateStore.statusFilter, $appStateStore.validationFilter);
|
||||
setAll(
|
||||
'',
|
||||
0,
|
||||
{ type, id },
|
||||
$appStateStore.statusFilter,
|
||||
$appStateStore.validationFilter,
|
||||
$appStateStore.createdFrom,
|
||||
$appStateStore.createdTo
|
||||
);
|
||||
goto(`/${ToQuery($appStateStore)}`);
|
||||
}}
|
||||
onRemove={() => {
|
||||
setAll('', 0, null, $appStateStore.statusFilter, $appStateStore.validationFilter);
|
||||
setAll(
|
||||
'',
|
||||
0,
|
||||
null,
|
||||
$appStateStore.statusFilter,
|
||||
$appStateStore.validationFilter,
|
||||
$appStateStore.createdFrom,
|
||||
$appStateStore.createdTo
|
||||
);
|
||||
goto(`/${ToQuery($appStateStore)}`);
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user