feat: refine api key permissions and admin routes
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react';
|
||||
import { DayPicker, type ChevronProps, type DayPickerProps } from 'react-day-picker';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export function Calendar(props: DayPickerProps) {
|
||||
const { className, classNames, showOutsideDays = true, ...rest } = props;
|
||||
return (
|
||||
<DayPicker
|
||||
className={cn('shCalendar', className)}
|
||||
classNames={{
|
||||
root: 'shCalendarRoot',
|
||||
months: 'shCalendarMonths',
|
||||
month: 'shCalendarMonth',
|
||||
month_caption: 'shCalendarCaption',
|
||||
caption_label: 'shCalendarCaptionLabel',
|
||||
nav: 'shCalendarNav',
|
||||
button_previous: 'shCalendarNavButton',
|
||||
button_next: 'shCalendarNavButton',
|
||||
chevron: 'shCalendarChevron',
|
||||
month_grid: 'shCalendarGrid',
|
||||
weekdays: 'shCalendarWeekdays',
|
||||
weekday: 'shCalendarWeekday',
|
||||
weeks: 'shCalendarWeeks',
|
||||
week: 'shCalendarWeek',
|
||||
day: 'shCalendarDay',
|
||||
day_button: 'shCalendarDayButton',
|
||||
outside: 'shCalendarDayOutside',
|
||||
disabled: 'shCalendarDayDisabled',
|
||||
selected: 'shCalendarDaySelected',
|
||||
today: 'shCalendarDayToday',
|
||||
hidden: 'shCalendarDayHidden',
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
Chevron: CalendarChevron,
|
||||
...props.components,
|
||||
}}
|
||||
showOutsideDays={showOutsideDays}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CalendarChevron(props: ChevronProps) {
|
||||
const Icon = props.orientation === 'left'
|
||||
? ChevronLeft
|
||||
: props.orientation === 'right'
|
||||
? ChevronRight
|
||||
: props.orientation === 'up'
|
||||
? ChevronUp
|
||||
: ChevronDown;
|
||||
return <Icon className={cn('shCalendarChevronIcon', props.className)} size={props.size ?? 16} />;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
import { CalendarIcon, X } from 'lucide-react';
|
||||
import { Button } from './button';
|
||||
import { Calendar } from './calendar';
|
||||
import { Input } from './input';
|
||||
import { Label } from './label';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './popover';
|
||||
|
||||
export function DateTimePicker(props: {
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const selectedDate = useMemo(() => parseLocalDateTime(props.value), [props.value]);
|
||||
const timeValue = selectedDate ? formatTimeValue(selectedDate) : '';
|
||||
|
||||
function updateDate(nextDate: Date | undefined) {
|
||||
if (!nextDate) return;
|
||||
const current = selectedDate ?? new Date();
|
||||
const next = new Date(nextDate);
|
||||
next.setHours(current.getHours(), current.getMinutes(), 0, 0);
|
||||
props.onChange(formatLocalDateTime(next));
|
||||
}
|
||||
|
||||
function updateTime(value: string) {
|
||||
const [hours, minutes] = value.split(':').map((item) => Number(item));
|
||||
if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return;
|
||||
const next = selectedDate ? new Date(selectedDate) : new Date();
|
||||
next.setHours(hours, minutes, 0, 0);
|
||||
props.onChange(formatLocalDateTime(next));
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className="dateTimePickerTrigger"
|
||||
data-empty={!selectedDate}
|
||||
disabled={props.disabled}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<CalendarIcon size={15} />
|
||||
{selectedDate ? format(selectedDate, 'yyyy-MM-dd HH:mm') : <span>{props.placeholder ?? '选择日期时间'}</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="dateTimePickerPopover">
|
||||
<Calendar mode="single" selected={selectedDate} onSelect={updateDate} />
|
||||
<div className="dateTimePickerFooter">
|
||||
<Label>
|
||||
时间
|
||||
<Input type="time" value={timeValue} onChange={(event) => updateTime(event.target.value)} />
|
||||
</Label>
|
||||
<Button type="button" variant="ghost" size="icon" title="清除有效期" disabled={!props.value} onClick={() => props.onChange('')}>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function parseLocalDateTime(value: string) {
|
||||
if (!value) return undefined;
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? undefined : date;
|
||||
}
|
||||
|
||||
function formatLocalDateTime(date: Date) {
|
||||
const year = date.getFullYear();
|
||||
const month = pad(date.getMonth() + 1);
|
||||
const day = pad(date.getDate());
|
||||
const hours = pad(date.getHours());
|
||||
const minutes = pad(date.getMinutes());
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function formatTimeValue(date: Date) {
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
function pad(value: number) {
|
||||
return String(value).padStart(2, '0');
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
export * from './badge';
|
||||
export * from './button';
|
||||
export * from './calendar';
|
||||
export * from './card';
|
||||
export * from './checkbox';
|
||||
export * from './confirm-dialog';
|
||||
export * from './date-time-picker';
|
||||
export * from './dialog';
|
||||
export * from './form-item';
|
||||
export * from './input';
|
||||
export * from './label';
|
||||
export * from './message';
|
||||
export * from './popover';
|
||||
export * from './select';
|
||||
export * from './separator';
|
||||
export * from './table';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export const Popover = PopoverPrimitive.Root;
|
||||
export const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
export const PopoverAnchor = PopoverPrimitive.Anchor;
|
||||
|
||||
export const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ align = 'center', className, sideOffset = 6, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
align={align}
|
||||
className={cn('shPopoverContent', className)}
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
Reference in New Issue
Block a user