mirror of
https://github.com/tcsenpai/pensieve.git
synced 2025-06-06 19:25:24 +00:00
feat(web): support time filter
This commit is contained in:
parent
d0cfd91f5a
commit
08c51a8259
@ -1,7 +1,7 @@
|
||||
<!-- Modal.svelte -->
|
||||
<script>
|
||||
import { ScrollArea } from "$lib/components/ui/scroll-area";
|
||||
import CopyToClipboard from "$lib/components/copy-to-clipboard.svelte"
|
||||
import CopyToClipboard from "$lib/components/CopyToClipboard.svelte"
|
||||
import OCRTable from './OCRTable.svelte';
|
||||
import { marked } from 'marked';
|
||||
|
||||
|
114
web/src/lib/components/TimeFilter.svelte
Normal file
114
web/src/lib/components/TimeFilter.svelte
Normal file
@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { RangeCalendar } from '$lib/components/ui/range-calendar/index.js';
|
||||
|
||||
import {
|
||||
CalendarDate,
|
||||
DateFormatter,
|
||||
type DateValue,
|
||||
getLocalTimeZone
|
||||
} from '@internationalized/date';
|
||||
|
||||
let now = +new Date();
|
||||
|
||||
const rangeMap = {
|
||||
unlimited: {
|
||||
label: '不限时间',
|
||||
start: null,
|
||||
end: null
|
||||
},
|
||||
today: {
|
||||
label: '今天',
|
||||
start: now - 24 * 60 * 60 * 1000,
|
||||
end: now
|
||||
},
|
||||
week: {
|
||||
label: '最近一周',
|
||||
start: now - 7 * 24 * 60 * 60 * 1000,
|
||||
end: now
|
||||
},
|
||||
month: {
|
||||
label: '最近一个月',
|
||||
start: now - 30 * 24 * 60 * 60 * 1000,
|
||||
end: now
|
||||
},
|
||||
threeMonths: {
|
||||
label: '最近三个月',
|
||||
start: now - 90 * 24 * 60 * 60 * 1000,
|
||||
end: now
|
||||
},
|
||||
custom: {
|
||||
label: '自定义',
|
||||
start: null,
|
||||
end: null
|
||||
}
|
||||
};
|
||||
|
||||
let timeFilter = 'unlimited';
|
||||
export let start: number;
|
||||
export let end: number;
|
||||
|
||||
let customDateRange = {
|
||||
start: null,
|
||||
end: null
|
||||
};
|
||||
|
||||
function updateCustomDateRange(range: { start: DateValue | null; end: DateValue | null }) {
|
||||
if (range.start && range.end) {
|
||||
rangeMap.custom.start = range.start.toDate(getLocalTimeZone()).getTime();
|
||||
rangeMap.custom.end = range.end.toDate(getLocalTimeZone()).getTime();
|
||||
if (timeFilter !== 'custom') {
|
||||
timeFilter = 'custom';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: updateCustomDateRange(customDateRange);
|
||||
|
||||
$: if (timeFilter !== 'custom') {
|
||||
customDateRange = {
|
||||
start: null,
|
||||
end: null
|
||||
};
|
||||
}
|
||||
|
||||
$: displayText =
|
||||
(timeFilter === 'custom' && customDateRange.start && customDateRange.end)
|
||||
? `${customDateRange.start?.toString()} - ${customDateRange.end?.toString()}`
|
||||
: rangeMap[timeFilter].label;
|
||||
$: start = rangeMap[timeFilter].start;
|
||||
$: end = rangeMap[timeFilter].end;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="border p-2 text-xs font-medium focus:outline-none"
|
||||
builders={[builder]}
|
||||
>
|
||||
<span class="truncate">{displayText}</span>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content class="w-56">
|
||||
<DropdownMenu.Label>时间筛选</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.RadioGroup bind:value={timeFilter}>
|
||||
<DropdownMenu.RadioItem value="unlimited">时间不限</DropdownMenu.RadioItem>
|
||||
<DropdownMenu.RadioItem value="today">今天</DropdownMenu.RadioItem>
|
||||
<DropdownMenu.RadioItem value="week">最近一周</DropdownMenu.RadioItem>
|
||||
<DropdownMenu.RadioItem value="month">最近一个月</DropdownMenu.RadioItem>
|
||||
<DropdownMenu.RadioItem value="threeMonths">最近三个月</DropdownMenu.RadioItem>
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger>自定义</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
<RangeCalendar bind:value={customDateRange} />
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
</DropdownMenu.RadioGroup>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
@ -11,15 +11,37 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { RangeCalendar } from "$lib/components/ui/range-calendar/index.js";
|
||||
import * as Popover from "$lib/components/ui/popover/index.js";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
const df = new DateFormatter("en-US", {
|
||||
dateStyle: "medium"
|
||||
});
|
||||
|
||||
let value: DateRange | undefined = {
|
||||
start: new CalendarDate(2022, 1, 20),
|
||||
end: new CalendarDate(2022, 1, 20).add({ days: 20 })
|
||||
};
|
||||
export let startTimestamp: number;
|
||||
export let endTimestamp: number;
|
||||
|
||||
let value: DateRange | undefined;
|
||||
let initialized = false; // Flag to control reactive updates
|
||||
let userSelected = false; // New flag to track user selection
|
||||
|
||||
onMount(() => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth() + 1; // getMonth() returns 0-11
|
||||
const date = now.getDate();
|
||||
value = {
|
||||
start: new CalendarDate(year, month, date).subtract({ days: 30 }),
|
||||
end: new CalendarDate(year, month, date)
|
||||
};
|
||||
console.log(initialized);
|
||||
initialized = true;
|
||||
});
|
||||
|
||||
$: if (initialized && value && value.start && value.end && !userSelected) {
|
||||
userSelected = true; // Set flag when user selects a range
|
||||
startTimestamp = value.start.toDate(getLocalTimeZone()).getTime() / 1000;
|
||||
endTimestamp = value.end.toDate(getLocalTimeZone()).getTime() / 1000;
|
||||
}
|
||||
|
||||
let startValue: DateValue | undefined = undefined;
|
||||
</script>
|
||||
@ -30,13 +52,15 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
class={cn(
|
||||
"w-[300px] justify-start text-left font-normal",
|
||||
!value && "text-muted-foreground"
|
||||
"w-[220px] justify-start text-center font-normal text-xs",
|
||||
(startTimestamp === -1 && endTimestamp === -1) && "text-muted-foreground"
|
||||
)}
|
||||
builders={[builder]}
|
||||
>
|
||||
<Calendar class="mr-2 h-4 w-4" />
|
||||
{#if value && value.start}
|
||||
{#if startTimestamp === -1 && endTimestamp === -1}
|
||||
不限时间
|
||||
{:else if value && value.start}
|
||||
{#if value.end}
|
||||
{df.format(value.start.toDate(getLocalTimeZone()))} - {df.format(
|
||||
value.end.toDate(getLocalTimeZone())
|
@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:checked
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.CheckboxIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.CheckboxIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.Content>
|
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = DropdownMenuPrimitive.ItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.Item>
|
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.LabelProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Label
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.Label>
|
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
|
||||
|
||||
export let value: $$Props["value"] = undefined;
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup {...$$restProps} bind:value>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.RadioGroup>
|
@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Circle from "lucide-svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"];
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{value}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.RadioIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.RadioIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.RadioItem>
|
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SeparatorProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
class={cn("bg-muted -mx-1 my-1 h-px", className)}
|
||||
{...$$restProps}
|
||||
/>
|
@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</span>
|
@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.SubContentEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||
x: -10,
|
||||
y: 0,
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
on:focusout
|
||||
on:pointermove
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.SubContent>
|
@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = DropdownMenuPrimitive.SubTriggerEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
48
web/src/lib/components/ui/dropdown-menu/index.ts
Normal file
48
web/src/lib/components/ui/dropdown-menu/index.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Item from "./dropdown-menu-item.svelte";
|
||||
import Label from "./dropdown-menu-label.svelte";
|
||||
import Content from "./dropdown-menu-content.svelte";
|
||||
import Shortcut from "./dropdown-menu-shortcut.svelte";
|
||||
import RadioItem from "./dropdown-menu-radio-item.svelte";
|
||||
import Separator from "./dropdown-menu-separator.svelte";
|
||||
import RadioGroup from "./dropdown-menu-radio-group.svelte";
|
||||
import SubContent from "./dropdown-menu-sub-content.svelte";
|
||||
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
|
||||
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
|
||||
|
||||
const Sub = DropdownMenuPrimitive.Sub;
|
||||
const Root = DropdownMenuPrimitive.Root;
|
||||
const Trigger = DropdownMenuPrimitive.Trigger;
|
||||
const Group = DropdownMenuPrimitive.Group;
|
||||
|
||||
export {
|
||||
Sub,
|
||||
Root,
|
||||
Item,
|
||||
Label,
|
||||
Group,
|
||||
Trigger,
|
||||
Content,
|
||||
Shortcut,
|
||||
Separator,
|
||||
RadioItem,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
RadioGroup,
|
||||
CheckboxItem,
|
||||
//
|
||||
Root as DropdownMenu,
|
||||
Sub as DropdownMenuSub,
|
||||
Item as DropdownMenuItem,
|
||||
Label as DropdownMenuLabel,
|
||||
Group as DropdownMenuGroup,
|
||||
Content as DropdownMenuContent,
|
||||
Trigger as DropdownMenuTrigger,
|
||||
Shortcut as DropdownMenuShortcut,
|
||||
RadioItem as DropdownMenuRadioItem,
|
||||
Separator as DropdownMenuSeparator,
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
SubContent as DropdownMenuSubContent,
|
||||
SubTrigger as DropdownMenuSubTrigger,
|
||||
CheckboxItem as DropdownMenuCheckboxItem,
|
||||
};
|
34
web/src/lib/components/ui/select/index.ts
Normal file
34
web/src/lib/components/ui/select/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
import Label from "./select-label.svelte";
|
||||
import Item from "./select-item.svelte";
|
||||
import Content from "./select-content.svelte";
|
||||
import Trigger from "./select-trigger.svelte";
|
||||
import Separator from "./select-separator.svelte";
|
||||
|
||||
const Root = SelectPrimitive.Root;
|
||||
const Group = SelectPrimitive.Group;
|
||||
const Input = SelectPrimitive.Input;
|
||||
const Value = SelectPrimitive.Value;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
Input,
|
||||
Label,
|
||||
Item,
|
||||
Value,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
Input as SelectInput,
|
||||
Label as SelectLabel,
|
||||
Item as SelectItem,
|
||||
Value as SelectValue,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
};
|
39
web/src/lib/components/ui/select/select-content.svelte
Normal file
39
web/src/lib/components/ui/select/select-content.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { scale } from "svelte/transition";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.ContentProps;
|
||||
type $$Events = SelectPrimitive.ContentEvents;
|
||||
|
||||
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||
export let inTransition: $$Props["inTransition"] = flyAndScale;
|
||||
export let inTransitionConfig: $$Props["inTransitionConfig"] = undefined;
|
||||
export let outTransition: $$Props["outTransition"] = scale;
|
||||
export let outTransitionConfig: $$Props["outTransitionConfig"] = {
|
||||
start: 0.95,
|
||||
opacity: 0,
|
||||
duration: 50,
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Content
|
||||
{inTransition}
|
||||
{inTransitionConfig}
|
||||
{outTransition}
|
||||
{outTransitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground relative z-50 min-w-[8rem] overflow-hidden rounded-md border shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
>
|
||||
<div class="w-full p-1">
|
||||
<slot />
|
||||
</div>
|
||||
</SelectPrimitive.Content>
|
40
web/src/lib/components/ui/select/select-item.svelte
Normal file
40
web/src/lib/components/ui/select/select-item.svelte
Normal file
@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.ItemProps;
|
||||
type $$Events = SelectPrimitive.ItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"];
|
||||
export let label: $$Props["label"] = undefined;
|
||||
export let disabled: $$Props["disabled"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Item
|
||||
{value}
|
||||
{disabled}
|
||||
{label}
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<slot>
|
||||
{label || value}
|
||||
</slot>
|
||||
</SelectPrimitive.Item>
|
16
web/src/lib/components/ui/select/select-label.svelte
Normal file
16
web/src/lib/components/ui/select/select-label.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.LabelProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Label
|
||||
class={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</SelectPrimitive.Label>
|
11
web/src/lib/components/ui/select/select-separator.svelte
Normal file
11
web/src/lib/components/ui/select/select-separator.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.SeparatorProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} />
|
27
web/src/lib/components/ui/select/select-trigger.svelte
Normal file
27
web/src/lib/components/ui/select/select-trigger.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps;
|
||||
type $$Events = SelectPrimitive.TriggerEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
"border-input bg-background ring-offset-background focus-visible:ring-ring aria-[invalid]:border-destructive data-[placeholder]:[&>span]:text-muted-foreground flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
let:builder
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<slot {builder} />
|
||||
<div>
|
||||
<ChevronDown class="h-4 w-4 opacity-50" />
|
||||
</div>
|
||||
</SelectPrimitive.Trigger>
|
@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import Figure from '$lib/Figure.svelte';
|
||||
import TimeFilter from '$lib/components/TimeFilter.svelte';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { PUBLIC_API_ENDPOINT } from '$env/static/public';
|
||||
|
||||
@ -12,8 +13,9 @@
|
||||
let debounceTimer;
|
||||
let showModal = false;
|
||||
let selectedImage = 0;
|
||||
let datetimeFilterStart = '';
|
||||
let datetimeFilterEnd = '';
|
||||
|
||||
let startTimestamp = -1;
|
||||
let endTimestamp = -1;
|
||||
|
||||
const debounceDelay = 300;
|
||||
const apiEndpoint =
|
||||
@ -22,11 +24,18 @@
|
||||
/**
|
||||
* @param {string} query
|
||||
*/
|
||||
async function searchItems(query) {
|
||||
async function searchItems(query, start, end) {
|
||||
isLoading = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiEndpoint}/search?q=${encodeURIComponent(query)}`);
|
||||
let url = `${apiEndpoint}/search?q=${encodeURIComponent(query)}`;
|
||||
if (start > 0) {
|
||||
url += `&start=${Math.floor(start / 1000)}`;
|
||||
}
|
||||
if (end > 0) {
|
||||
url += `&end=${Math.floor(end / 1000)}`;
|
||||
}
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
@ -41,10 +50,10 @@
|
||||
/**
|
||||
* @param {string} query
|
||||
*/
|
||||
function debounceSearch(query) {
|
||||
function debounceSearch(query, start, end) {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
searchItems(query);
|
||||
searchItems(query, start, end);
|
||||
}, debounceDelay);
|
||||
}
|
||||
|
||||
@ -95,10 +104,14 @@
|
||||
};
|
||||
|
||||
$: if (searchString.trim()) {
|
||||
debounceSearch(searchString);
|
||||
debounceSearch(searchString, startTimestamp, endTimestamp);
|
||||
} else {
|
||||
searchResults = [];
|
||||
}
|
||||
|
||||
$: if ((startTimestamp !== -1 || endTimestamp !== -1) && searchString.trim()) {
|
||||
debounceSearch(searchString, startTimestamp, endTimestamp);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
@ -110,6 +123,7 @@
|
||||
bind:value={searchString}
|
||||
placeholder="Type to search..."
|
||||
/>
|
||||
<TimeFilter bind:start={startTimestamp} bind:end={endTimestamp} />
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto">
|
||||
|
Loading…
x
Reference in New Issue
Block a user