feat(data-grid): improve filter condition actions
This commit is contained in:
parent
64011dff4c
commit
b65962d46f
|
|
@ -1,15 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from "vue";
|
||||
import { Eye, EyeOff, Plus, Search, Trash2 } from "@lucide/vue";
|
||||
import { Eye, EyeOff, Plus, Search, Trash2, X } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { filterModeNeedsValue, filterModeUsesList, filterModeUsesRange } from "@/lib/dataGrid/dataGridColumnFilter";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
import type { DataGridContextFilterMode } from "@/lib/dataGrid/dataGridSql";
|
||||
import type { DataGridStructuredFilterRule } from "@/composables/useDataGridFilterBuilder";
|
||||
|
||||
const { t } = useI18n();
|
||||
const VALUE_SHORTCUT_HINT_STORAGE_KEY = "dbx-filter-builder-value-shortcut-hint-days";
|
||||
const VALUE_SHORTCUT_HINT_MAX_DAYS = 3;
|
||||
const VALUE_SHORTCUT_HINT_MAX_PER_DAY = 2;
|
||||
type ValueShortcutHintDay = { date: string; count: number };
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
rules: DataGridStructuredFilterRule[];
|
||||
|
|
@ -38,12 +43,43 @@ const pendingValueFocus = new Set<string>();
|
|||
const pendingKeyboardAddFocus = new Set<string>();
|
||||
const openColumnSelectIds = ref(new Set<string>());
|
||||
const activeColumnIndexes = ref<Record<string, number>>({});
|
||||
const focusedValueRuleId = ref<string>();
|
||||
const valueShortcutHintRuleId = ref<string>();
|
||||
const valueShortcutHintShownDays = ref(readValueShortcutHintShownDays());
|
||||
let ruleIdsBeforeKeyboardAdd: Set<string> | undefined;
|
||||
|
||||
function usesExpandedLayout(mode: DataGridContextFilterMode) {
|
||||
return filterModeUsesList(mode) || filterModeUsesRange(mode);
|
||||
}
|
||||
|
||||
function currentLocalDateKey() {
|
||||
const now = new Date();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(now.getDate()).padStart(2, "0");
|
||||
return `${now.getFullYear()}-${month}-${day}`;
|
||||
}
|
||||
|
||||
function readValueShortcutHintShownDays(): ValueShortcutHintDay[] {
|
||||
try {
|
||||
const parsed = JSON.parse(safeLocalStorageGet(VALUE_SHORTCUT_HINT_STORAGE_KEY) ?? "[]");
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed
|
||||
.map((item): ValueShortcutHintDay | undefined => {
|
||||
if (!item || typeof item !== "object") return undefined;
|
||||
const date = (item as { date?: unknown }).date;
|
||||
const count = (item as { count?: unknown }).count;
|
||||
return typeof date === "string" && typeof count === "number" && Number.isFinite(count) && count > 0 ? { date, count } : undefined;
|
||||
})
|
||||
.filter((item): item is ValueShortcutHintDay => !!item);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShowValueShortcutHint(rule: DataGridStructuredFilterRule, index: number) {
|
||||
return focusedValueRuleId.value === rule.id && valueShortcutHintRuleId.value === rule.id && index > 0 && filterModeNeedsValue(rule.mode) && !filterModeUsesList(rule.mode);
|
||||
}
|
||||
|
||||
function updateRuleColumn(rule: DataGridStructuredFilterRule, value: unknown, focusValue = true) {
|
||||
if (focusValue && filterModeNeedsValue(rule.mode)) pendingValueFocus.add(rule.id);
|
||||
emit("updateRule", rule.id, { columnName: String(value) });
|
||||
|
|
@ -194,13 +230,32 @@ function handleValueEditorKeydown(event: KeyboardEvent) {
|
|||
event.stopPropagation();
|
||||
if (!event.repeat) addRuleAndOpenColumnSelect();
|
||||
}
|
||||
|
||||
function focusValueRule(id: string, index: number, mode: DataGridContextFilterMode) {
|
||||
focusedValueRuleId.value = id;
|
||||
valueShortcutHintRuleId.value = undefined;
|
||||
if (index === 0 || !filterModeNeedsValue(mode) || filterModeUsesList(mode)) return;
|
||||
const today = currentLocalDateKey();
|
||||
const existingIndex = valueShortcutHintShownDays.value.findIndex((item) => item.date === today);
|
||||
if (existingIndex >= 0 && valueShortcutHintShownDays.value[existingIndex].count >= VALUE_SHORTCUT_HINT_MAX_PER_DAY) return;
|
||||
if (existingIndex < 0 && valueShortcutHintShownDays.value.length >= VALUE_SHORTCUT_HINT_MAX_DAYS) return;
|
||||
const nextShownDays = existingIndex >= 0 ? valueShortcutHintShownDays.value.map((item, itemIndex) => (itemIndex === existingIndex ? { ...item, count: item.count + 1 } : item)) : [...valueShortcutHintShownDays.value, { date: today, count: 1 }];
|
||||
valueShortcutHintShownDays.value = nextShownDays;
|
||||
valueShortcutHintRuleId.value = id;
|
||||
safeLocalStorageSet(VALUE_SHORTCUT_HINT_STORAGE_KEY, JSON.stringify(nextShownDays));
|
||||
}
|
||||
|
||||
function blurValueRule(id: string) {
|
||||
if (focusedValueRuleId.value === id) focusedValueRuleId.value = undefined;
|
||||
if (valueShortcutHintRuleId.value === id) valueShortcutHintRuleId.value = undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<div v-if="props.showHeader !== false" class="flex items-center justify-between gap-3">
|
||||
<div class="text-xs font-medium text-foreground">{{ t("grid.filter") }}</div>
|
||||
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" :disabled="props.disabled || !props.columns.length" @click="emit('add')"> <Plus class="mr-1 h-3.5 w-3.5" />{{ t("grid.filterBuilderAddRule") }} </Button>
|
||||
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="emit('clear')"> <Trash2 class="mr-1 h-3.5 w-3.5" />{{ t("grid.clearFilter") }} </Button>
|
||||
</div>
|
||||
|
||||
<div v-if="props.rules.length" class="space-y-2">
|
||||
|
|
@ -256,9 +311,20 @@ function handleValueEditorKeydown(event: KeyboardEvent) {
|
|||
:disabled="rule.disabled"
|
||||
:placeholder="t('grid.filterBuilderRangeStart')"
|
||||
@update:model-value="(value) => emit('updateRule', rule.id, { rawValue: String(value ?? '') })"
|
||||
@focus="focusValueRule(rule.id, index, rule.mode)"
|
||||
@blur="blurValueRule(rule.id)"
|
||||
@keydown="handleValueEditorKeydown"
|
||||
/>
|
||||
<Input
|
||||
:model-value="rule.rawEndValue"
|
||||
class="h-8 text-xs"
|
||||
:disabled="rule.disabled"
|
||||
:placeholder="t('grid.filterBuilderRangeEnd')"
|
||||
@update:model-value="(value) => emit('updateRule', rule.id, { rawEndValue: String(value ?? '') })"
|
||||
@focus="focusValueRule(rule.id, index, rule.mode)"
|
||||
@blur="blurValueRule(rule.id)"
|
||||
@keydown="handleValueEditorKeydown"
|
||||
/>
|
||||
<Input :model-value="rule.rawEndValue" class="h-8 text-xs" :disabled="rule.disabled" :placeholder="t('grid.filterBuilderRangeEnd')" @update:model-value="(value) => emit('updateRule', rule.id, { rawEndValue: String(value ?? '') })" @keydown="handleValueEditorKeydown" />
|
||||
</div>
|
||||
<textarea
|
||||
v-else-if="filterModeUsesList(rule.mode)"
|
||||
|
|
@ -280,19 +346,24 @@ function handleValueEditorKeydown(event: KeyboardEvent) {
|
|||
:disabled="rule.disabled"
|
||||
:placeholder="t('grid.filterBuilderValue')"
|
||||
@update:model-value="(value) => emit('updateRule', rule.id, { rawValue: String(value ?? '') })"
|
||||
@focus="focusValueRule(rule.id, index, rule.mode)"
|
||||
@blur="blurValueRule(rule.id)"
|
||||
@keydown="handleValueEditorKeydown"
|
||||
/>
|
||||
<div v-else class="flex h-8 items-center rounded-md border border-dashed px-2 text-xs text-muted-foreground">{{ t("grid.filterBuilderNoValue") }}</div>
|
||||
<div class="flex items-center gap-1" :class="usesExpandedLayout(rule.mode) ? 'col-start-3 row-start-1 row-span-2' : ''">
|
||||
<div v-if="shouldShowValueShortcutHint(rule, index)" class="text-[11px] leading-none text-muted-foreground" :class="usesExpandedLayout(rule.mode) ? 'col-span-2 -mt-1' : 'col-start-3 row-start-2 -mt-1'">
|
||||
{{ t("grid.filterBuilderValueShortcutHint") }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1" :class="usesExpandedLayout(rule.mode) ? 'col-start-3 row-start-1 row-span-2' : 'col-start-4 row-start-1'">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="emit('updateRule', rule.id, { disabled: !rule.disabled })"><EyeOff v-if="rule.disabled" class="h-3.5 w-3.5" /><Eye v-else class="h-3.5 w-3.5" /></Button>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" :disabled="props.rules.length === 1" @click="emit('remove', rule.id)"><Trash2 class="h-3.5 w-3.5" /></Button>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" :disabled="props.rules.length === 1" @click="emit('remove', rule.id)"><X class="h-3.5 w-3.5" /></Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="rounded-md border border-dashed px-3 py-4 text-center text-xs text-muted-foreground">{{ t("grid.filterBuilderEmpty") }}</div>
|
||||
<div v-if="props.showFooter !== false" class="flex justify-between gap-2 pt-1">
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" @click="emit('clear')">{{ t("grid.clearFilter") }}</Button>
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" :disabled="props.disabled || !props.columns.length" @click="emit('add')"><Plus class="mr-1 h-3.5 w-3.5" />{{ t("grid.filterBuilderAddRule") }}</Button>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" @click="emit('reset')">{{ t("grid.resetFilterBuilder") }}</Button
|
||||
><Button size="sm" class="h-8 px-3 text-xs" :disabled="props.disabled" @click="emit('apply')">{{ t("grid.applyFilter") }}</Button>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onUnmounted, ref } from "vue";
|
||||
import { Filter, Plus, X } from "@lucide/vue";
|
||||
import { Filter, Trash2 } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
|
|
@ -134,7 +134,7 @@ onUnmounted(onResizeEnd);
|
|||
<PopoverContent align="start" class="w-[480px] max-w-[calc(100vw-24px)] gap-3 p-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="text-xs font-medium text-foreground">{{ t("grid.filter") }}</div>
|
||||
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="emit('addRule')"><Plus class="mr-1 h-3.5 w-3.5" />{{ t("grid.filterBuilderAddRule") }}</Button>
|
||||
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="emit('clearFilters')"><Trash2 class="mr-1 h-3.5 w-3.5" />{{ t("grid.clearFilter") }}</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="hasLocalColumnFilters" class="space-y-2 rounded-md border border-primary/20 bg-primary/5 px-2.5 py-2">
|
||||
|
|
|
|||
|
|
@ -114,8 +114,14 @@ function detail(patch: Partial<DataGridCellDetail> = {}): DataGridCellDetail {
|
|||
};
|
||||
}
|
||||
|
||||
function localDateKey() {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.removeItem("dbx-filter-builder-value-shortcut-hint-days");
|
||||
});
|
||||
|
||||
describe("DataGridSearchBar", () => {
|
||||
|
|
@ -461,29 +467,108 @@ describe("DataGridFilterBuilder", () => {
|
|||
expect(columnSelects[1].props.open).toBe(true);
|
||||
});
|
||||
|
||||
it("adds a rule instead of applying when shift-enter is pressed in a value editor", () => {
|
||||
it("shows the value editor shortcut hint from the second rule twice per day for up to three days and adds a rule on shift-enter", async () => {
|
||||
const onAdd = vi.fn();
|
||||
const onApply = vi.fn();
|
||||
const mounted = mountComponent(DataGridFilterBuilder, {
|
||||
rules: [{ id: "r1", columnName: "id", mode: "equals", rawValue: "1", rawEndValue: "", conjunction: "AND" }],
|
||||
columns: ["id"],
|
||||
filteredColumns: ["id"],
|
||||
modeOptions: [{ value: "equals", labelKey: "equals" }],
|
||||
columnSearch: "",
|
||||
onAdd,
|
||||
onApply,
|
||||
});
|
||||
const valueEditor = findOne(mounted.root, (node) => node.props["data-filter-value-editor"] === "");
|
||||
const mountFilterBuilder = () =>
|
||||
mountComponent(DataGridFilterBuilder, {
|
||||
rules: [
|
||||
{ id: "r1", columnName: "id", mode: "equals", rawValue: "1", rawEndValue: "", conjunction: "AND" },
|
||||
{ id: "r2", columnName: "name", mode: "equals", rawValue: "n", rawEndValue: "", conjunction: "AND" },
|
||||
],
|
||||
columns: ["id"],
|
||||
filteredColumns: ["id"],
|
||||
modeOptions: [{ value: "equals", labelKey: "equals" }],
|
||||
columnSearch: "",
|
||||
onAdd,
|
||||
onApply,
|
||||
});
|
||||
const mounted = mountFilterBuilder();
|
||||
const valueEditors = findAll(mounted.root, (node) => node.props["data-filter-value-editor"] === "");
|
||||
const valueEditor = valueEditors[0];
|
||||
const secondValueEditor = valueEditors[1];
|
||||
|
||||
const shiftEnter = dispatch(valueEditor, "keydown", { key: "Enter", shiftKey: true, repeat: false });
|
||||
expect(hostText(mounted.root)).not.toContain("grid.filterBuilderValueShortcutHint");
|
||||
dispatch(valueEditor, "focus");
|
||||
await nextTick();
|
||||
expect(hostText(mounted.root)).not.toContain("grid.filterBuilderValueShortcutHint");
|
||||
|
||||
dispatch(secondValueEditor, "focus");
|
||||
await nextTick();
|
||||
expect(hostText(mounted.root)).toContain("grid.filterBuilderValueShortcutHint");
|
||||
dispatch(secondValueEditor, "blur");
|
||||
await nextTick();
|
||||
expect(hostText(mounted.root)).not.toContain("grid.filterBuilderValueShortcutHint");
|
||||
expect(JSON.parse(localStorage.getItem("dbx-filter-builder-value-shortcut-hint-days") ?? "[]")).toEqual([{ date: localDateKey(), count: 1 }]);
|
||||
|
||||
dispatch(secondValueEditor, "focus");
|
||||
await nextTick();
|
||||
expect(hostText(mounted.root)).toContain("grid.filterBuilderValueShortcutHint");
|
||||
expect(JSON.parse(localStorage.getItem("dbx-filter-builder-value-shortcut-hint-days") ?? "[]")).toEqual([{ date: localDateKey(), count: 2 }]);
|
||||
dispatch(secondValueEditor, "blur");
|
||||
dispatch(secondValueEditor, "focus");
|
||||
await nextTick();
|
||||
expect(hostText(mounted.root)).not.toContain("grid.filterBuilderValueShortcutHint");
|
||||
|
||||
localStorage.setItem(
|
||||
"dbx-filter-builder-value-shortcut-hint-days",
|
||||
JSON.stringify([
|
||||
{ date: "2026-01-01", count: 2 },
|
||||
{ date: "2026-01-02", count: 2 },
|
||||
]),
|
||||
);
|
||||
const thirdDayMounted = mountFilterBuilder();
|
||||
const thirdDaySecondValueEditor = findAll(thirdDayMounted.root, (node) => node.props["data-filter-value-editor"] === "")[1];
|
||||
dispatch(thirdDaySecondValueEditor, "focus");
|
||||
await nextTick();
|
||||
expect(hostText(thirdDayMounted.root)).toContain("grid.filterBuilderValueShortcutHint");
|
||||
expect(JSON.parse(localStorage.getItem("dbx-filter-builder-value-shortcut-hint-days") ?? "[]")).toHaveLength(3);
|
||||
|
||||
localStorage.setItem(
|
||||
"dbx-filter-builder-value-shortcut-hint-days",
|
||||
JSON.stringify([
|
||||
{ date: "2026-01-01", count: 2 },
|
||||
{ date: "2026-01-02", count: 2 },
|
||||
{ date: "2026-01-03", count: 2 },
|
||||
]),
|
||||
);
|
||||
const exhaustedMounted = mountFilterBuilder();
|
||||
const exhaustedSecondValueEditor = findAll(exhaustedMounted.root, (node) => node.props["data-filter-value-editor"] === "")[1];
|
||||
dispatch(exhaustedSecondValueEditor, "focus");
|
||||
await nextTick();
|
||||
expect(hostText(exhaustedMounted.root)).not.toContain("grid.filterBuilderValueShortcutHint");
|
||||
|
||||
const shiftEnter = dispatch(secondValueEditor, "keydown", { key: "Enter", shiftKey: true, repeat: false });
|
||||
expect(shiftEnter.defaultPrevented).toBe(true);
|
||||
expect(shiftEnter.propagationStopped).toBe(true);
|
||||
expect(onAdd).toHaveBeenCalledOnce();
|
||||
expect(onApply).not.toHaveBeenCalled();
|
||||
|
||||
dispatch(valueEditor, "keydown", { key: "Enter", shiftKey: false });
|
||||
dispatch(secondValueEditor, "keydown", { key: "Enter", shiftKey: false });
|
||||
expect(onApply).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not show the value editor shortcut hint for list value editors", async () => {
|
||||
const mounted = mountComponent(DataGridFilterBuilder, {
|
||||
rules: [
|
||||
{ id: "r1", columnName: "id", mode: "equals", rawValue: "1", rawEndValue: "", conjunction: "AND" },
|
||||
{ id: "r2", columnName: "name", mode: "in", rawValue: "n", rawEndValue: "", conjunction: "AND" },
|
||||
],
|
||||
columns: ["id"],
|
||||
filteredColumns: ["id"],
|
||||
modeOptions: [
|
||||
{ value: "equals", labelKey: "equals" },
|
||||
{ value: "in", labelKey: "in" },
|
||||
],
|
||||
columnSearch: "",
|
||||
});
|
||||
dispatch(
|
||||
findOne(mounted.root, (node) => node.type === "textarea"),
|
||||
"focus",
|
||||
);
|
||||
await nextTick();
|
||||
expect(hostText(mounted.root)).not.toContain("grid.filterBuilderValueShortcutHint");
|
||||
});
|
||||
});
|
||||
|
||||
describe("DataGridQueryControls", () => {
|
||||
|
|
@ -518,6 +603,7 @@ describe("DataGridQueryControls", () => {
|
|||
});
|
||||
|
||||
it("keeps filter actions available in the popover", () => {
|
||||
const addRule = vi.fn();
|
||||
const clearFilters = vi.fn();
|
||||
const applyFilters = vi.fn();
|
||||
const resetFilters = vi.fn();
|
||||
|
|
@ -543,6 +629,7 @@ describe("DataGridQueryControls", () => {
|
|||
applyWhere: vi.fn(),
|
||||
applyOrderBy: vi.fn(),
|
||||
clearOrderBy: vi.fn(),
|
||||
onAddRule: addRule,
|
||||
onClearFilters: clearFilters,
|
||||
onApplyFilters: applyFilters,
|
||||
onResetFilters: resetFilters,
|
||||
|
|
@ -552,6 +639,10 @@ describe("DataGridQueryControls", () => {
|
|||
findOne(mounted.root, (node) => node.type === "button" && hostText(node) === "grid.clearFilter"),
|
||||
"click",
|
||||
);
|
||||
dispatch(
|
||||
findOne(mounted.root, (node) => node.type === "button" && hostText(node) === "grid.filterBuilderAddRule"),
|
||||
"click",
|
||||
);
|
||||
dispatch(
|
||||
findOne(mounted.root, (node) => node.type === "button" && hostText(node) === "grid.resetFilterBuilder"),
|
||||
"click",
|
||||
|
|
@ -566,6 +657,7 @@ describe("DataGridQueryControls", () => {
|
|||
const whereButtons = findAll(whereControl!, (node) => node.type === "button");
|
||||
dispatch(whereButtons[whereButtons.length - 1], "click");
|
||||
|
||||
expect(addRule).toHaveBeenCalledOnce();
|
||||
expect(clearFilters).toHaveBeenCalledTimes(2);
|
||||
expect(resetFilters).toHaveBeenCalledOnce();
|
||||
expect(applyFilters).toHaveBeenCalledOnce();
|
||||
|
|
|
|||
|
|
@ -1155,6 +1155,7 @@ export default {
|
|||
filterBuilderSearchColumns: "Search columns...",
|
||||
filterBuilderNoMatchingColumns: "No matching columns",
|
||||
filterBuilderValue: "Value",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter can add a rule",
|
||||
filterBuilderValues: "Values (comma or newline separated)",
|
||||
filterBuilderRangeStart: "Start value",
|
||||
filterBuilderRangeEnd: "End value",
|
||||
|
|
|
|||
|
|
@ -1051,6 +1051,7 @@ export default withEnglishFallback({
|
|||
filterBuilderAddRule: "Agregar regla",
|
||||
filterBuilderColumn: "Columna",
|
||||
filterBuilderValue: "Valor",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter puede agregar una regla",
|
||||
filterBuilderValues: "Valores (separados por comas o saltos de línea)",
|
||||
filterBuilderRangeStart: "Valor inicial",
|
||||
filterBuilderRangeEnd: "Valor final",
|
||||
|
|
|
|||
|
|
@ -1049,6 +1049,7 @@ export default withEnglishFallback({
|
|||
filterBuilderAddRule: "Aggiungi regola",
|
||||
filterBuilderColumn: "Colonna",
|
||||
filterBuilderValue: "Valore",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter può aggiungere una regola",
|
||||
filterBuilderValues: "Valori (separati da virgole o nuove righe)",
|
||||
filterBuilderRangeStart: "Valore iniziale",
|
||||
filterBuilderRangeEnd: "Valore finale",
|
||||
|
|
|
|||
|
|
@ -1067,6 +1067,7 @@ export default withEnglishFallback({
|
|||
filterBuilderAddRule: "ルールを追加",
|
||||
filterBuilderColumn: "列",
|
||||
filterBuilderValue: "値",
|
||||
filterBuilderValueShortcutHint: "Shift+Enterで条件を追加できます",
|
||||
filterBuilderValues: "値(カンマまたは改行で区切る)",
|
||||
filterBuilderRangeStart: "開始値",
|
||||
filterBuilderRangeEnd: "終了値",
|
||||
|
|
|
|||
|
|
@ -1138,6 +1138,7 @@ export default withEnglishFallback({
|
|||
filterBuilderSearchColumns: "컬럼 검색...",
|
||||
filterBuilderNoMatchingColumns: "일치하는 컬럼이 없습니다",
|
||||
filterBuilderValue: "값",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter로 조건을 추가할 수 있습니다",
|
||||
filterBuilderValues: "값 (쉼표 또는 줄바꿈으로 구분)",
|
||||
filterBuilderRangeStart: "시작 값",
|
||||
filterBuilderRangeEnd: "끝 값",
|
||||
|
|
|
|||
|
|
@ -1051,6 +1051,7 @@ export default withEnglishFallback({
|
|||
filterBuilderAddRule: "Adicionar regra",
|
||||
filterBuilderColumn: "Coluna",
|
||||
filterBuilderValue: "Valor",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter pode adicionar uma regra",
|
||||
filterBuilderValues: "Valores (separados por vírgulas ou quebras de linha)",
|
||||
filterBuilderRangeStart: "Valor inicial",
|
||||
filterBuilderRangeEnd: "Valor final",
|
||||
|
|
|
|||
|
|
@ -1156,6 +1156,7 @@ export default withEnglishFallback({
|
|||
filterBuilderSearchColumns: "搜索字段...",
|
||||
filterBuilderNoMatchingColumns: "没有匹配的字段",
|
||||
filterBuilderValue: "值",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter 可新增条件",
|
||||
filterBuilderValues: "多个值(用逗号或换行分隔)",
|
||||
filterBuilderRangeStart: "起始值",
|
||||
filterBuilderRangeEnd: "结束值",
|
||||
|
|
|
|||
|
|
@ -1050,6 +1050,7 @@ export default withEnglishFallback({
|
|||
filterBuilderAddRule: "新增條件",
|
||||
filterBuilderColumn: "欄位",
|
||||
filterBuilderValue: "值",
|
||||
filterBuilderValueShortcutHint: "Shift+Enter 可新增條件",
|
||||
filterBuilderValues: "多個值(以逗號或換行分隔)",
|
||||
filterBuilderRangeStart: "起始值",
|
||||
filterBuilderRangeEnd: "結束值",
|
||||
|
|
|
|||
Loading…
Reference in New Issue