fix: fix
This commit is contained in:
parent
5be5b9f634
commit
81bf27ed28
38
Cargo.lock
generated
38
Cargo.lock
generated
@ -551,7 +551,7 @@ dependencies = [
|
||||
"derive_builder",
|
||||
"diligent-date-parser",
|
||||
"never",
|
||||
"quick-xml",
|
||||
"quick-xml 0.37.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -1922,6 +1922,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
@ -2332,11 +2343,12 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
checksum = "d6215aee357f8c7c989ebb4b8466ca4d7dc93b3957039f2fc3ea2ade8ea5f279"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"derivative",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
@ -4394,7 +4406,7 @@ dependencies = [
|
||||
"futures",
|
||||
"httparse",
|
||||
"network-interface",
|
||||
"quick-xml",
|
||||
"quick-xml 0.37.5",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tokio",
|
||||
@ -5166,7 +5178,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"parking_lot 0.12.4",
|
||||
"percent-encoding",
|
||||
"quick-xml",
|
||||
"quick-xml 0.37.5",
|
||||
"rand 0.9.1",
|
||||
"reqwest",
|
||||
"ring",
|
||||
@ -5219,7 +5231,7 @@ dependencies = [
|
||||
"log",
|
||||
"md-5",
|
||||
"percent-encoding",
|
||||
"quick-xml",
|
||||
"quick-xml 0.37.5",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -6505,6 +6517,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.8"
|
||||
@ -6798,7 +6820,7 @@ dependencies = [
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"polars",
|
||||
"quick-xml",
|
||||
"quick-xml 0.38.0",
|
||||
"quirks_path",
|
||||
"rand 0.9.1",
|
||||
"regex",
|
||||
@ -7233,7 +7255,7 @@ dependencies = [
|
||||
"atom_syndication",
|
||||
"derive_builder",
|
||||
"never",
|
||||
"quick-xml",
|
||||
"quick-xml 0.37.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -109,7 +109,7 @@ sea-orm = { version = "1.1", features = [
|
||||
figment = { version = "0.10", features = ["toml", "json", "env", "yaml"] }
|
||||
sea-orm-migration = { version = "1.1", features = ["runtime-tokio"] }
|
||||
rss = { version = "2", features = ["builders", "with-serde"] }
|
||||
fancy-regex = "0.14"
|
||||
fancy-regex = "0.15"
|
||||
lightningcss = "1.0.0-alpha.66"
|
||||
html-escape = "0.2.13"
|
||||
opendal = { version = "0.53", features = ["default", "services-fs"] }
|
||||
@ -126,6 +126,7 @@ seaography = { version = "1.1", features = [
|
||||
"with-postgres-array",
|
||||
"with-json-as-scalar",
|
||||
"with-custom-as-json",
|
||||
"with-chrono-datetime-utc-as-timestamp",
|
||||
] }
|
||||
tower = { version = "0.5.2", features = ["util"] }
|
||||
tower-http = { version = "0.6", features = [
|
||||
@ -160,7 +161,7 @@ polars = { version = "0.49.1", features = [
|
||||
"lazy",
|
||||
"diagonal_concat",
|
||||
], optional = true }
|
||||
quick-xml = { version = "0.37.5", features = [
|
||||
quick-xml = { version = "0.38", features = [
|
||||
"serialize",
|
||||
"serde-types",
|
||||
"serde",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getFutureMatches } from '@datasert/cronjs-matcher';
|
||||
import { Calendar, Clock, Info, Settings, Zap } from 'lucide-react';
|
||||
import { getFutureMatches } from "@datasert/cronjs-matcher";
|
||||
import { Calendar, Clock, Info, Settings, Zap } from "lucide-react";
|
||||
import {
|
||||
type CSSProperties,
|
||||
type FC,
|
||||
@ -8,109 +8,109 @@ import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
} from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { cn } from '@/presentation/utils';
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { cn } from "@/presentation/utils";
|
||||
import {
|
||||
type CronBuilderProps,
|
||||
CronField,
|
||||
type CronFieldConfig,
|
||||
CronPeriod,
|
||||
type CronPreset,
|
||||
} from './types';
|
||||
} from "./types.js";
|
||||
|
||||
const CRON_PRESETS: CronPreset[] = [
|
||||
{
|
||||
label: 'Every minute',
|
||||
value: '0 * * * * *',
|
||||
description: 'Runs every minute',
|
||||
category: 'common',
|
||||
label: "Every minute",
|
||||
value: "0 * * * * *",
|
||||
description: "Runs every minute",
|
||||
category: "common",
|
||||
},
|
||||
{
|
||||
label: 'Every 5 minutes',
|
||||
value: '0 */5 * * * *',
|
||||
description: 'Runs every 5 minutes',
|
||||
category: 'common',
|
||||
label: "Every 5 minutes",
|
||||
value: "0 */5 * * * *",
|
||||
description: "Runs every 5 minutes",
|
||||
category: "common",
|
||||
},
|
||||
{
|
||||
label: 'Every 15 minutes',
|
||||
value: '0 */15 * * * *',
|
||||
description: 'Runs every 15 minutes',
|
||||
category: 'common',
|
||||
label: "Every 15 minutes",
|
||||
value: "0 */15 * * * *",
|
||||
description: "Runs every 15 minutes",
|
||||
category: "common",
|
||||
},
|
||||
{
|
||||
label: 'Every 30 minutes',
|
||||
value: '0 */30 * * * *',
|
||||
description: 'Runs every 30 minutes',
|
||||
category: 'common',
|
||||
label: "Every 30 minutes",
|
||||
value: "0 */30 * * * *",
|
||||
description: "Runs every 30 minutes",
|
||||
category: "common",
|
||||
},
|
||||
{
|
||||
label: 'Every hour',
|
||||
value: '0 0 * * * *',
|
||||
description: 'Runs at the top of every hour',
|
||||
category: 'common',
|
||||
label: "Every hour",
|
||||
value: "0 0 * * * *",
|
||||
description: "Runs at the top of every hour",
|
||||
category: "common",
|
||||
},
|
||||
{
|
||||
label: 'Every 6 hours',
|
||||
value: '0 0 */6 * * *',
|
||||
description: 'Runs every 6 hours',
|
||||
category: 'common',
|
||||
label: "Every 6 hours",
|
||||
value: "0 0 */6 * * *",
|
||||
description: "Runs every 6 hours",
|
||||
category: "common",
|
||||
},
|
||||
{
|
||||
label: 'Daily at midnight',
|
||||
value: '0 0 0 * * *',
|
||||
description: 'Runs once daily at 00:00',
|
||||
category: 'daily',
|
||||
label: "Daily at midnight",
|
||||
value: "0 0 0 * * *",
|
||||
description: "Runs once daily at 00:00",
|
||||
category: "daily",
|
||||
},
|
||||
{
|
||||
label: 'Daily at 9 AM',
|
||||
value: '0 0 9 * * *',
|
||||
description: 'Runs daily at 9:00 AM',
|
||||
category: 'daily',
|
||||
label: "Daily at 9 AM",
|
||||
value: "0 0 9 * * *",
|
||||
description: "Runs daily at 9:00 AM",
|
||||
category: "daily",
|
||||
},
|
||||
{
|
||||
label: 'Weekdays at 9 AM',
|
||||
value: '0 0 9 * * 1-5',
|
||||
description: 'Runs Monday to Friday at 9:00 AM',
|
||||
category: 'weekly',
|
||||
label: "Weekdays at 9 AM",
|
||||
value: "0 0 9 * * 1-5",
|
||||
description: "Runs Monday to Friday at 9:00 AM",
|
||||
category: "weekly",
|
||||
},
|
||||
{
|
||||
label: 'Every Sunday',
|
||||
value: '0 0 0 * * 0',
|
||||
description: 'Runs every Sunday at midnight',
|
||||
category: 'weekly',
|
||||
label: "Every Sunday",
|
||||
value: "0 0 0 * * 0",
|
||||
description: "Runs every Sunday at midnight",
|
||||
category: "weekly",
|
||||
},
|
||||
{
|
||||
label: 'First day of month',
|
||||
value: '0 0 0 1 * *',
|
||||
description: 'Runs on the 1st day of every month',
|
||||
category: 'monthly',
|
||||
label: "First day of month",
|
||||
value: "0 0 0 1 * *",
|
||||
description: "Runs on the 1st day of every month",
|
||||
category: "monthly",
|
||||
},
|
||||
{
|
||||
label: 'Every year',
|
||||
value: '0 0 0 1 1 *',
|
||||
description: 'Runs on January 1st every year',
|
||||
category: 'yearly',
|
||||
label: "Every year",
|
||||
value: "0 0 0 1 1 *",
|
||||
description: "Runs on January 1st every year",
|
||||
category: "yearly",
|
||||
},
|
||||
];
|
||||
|
||||
@ -119,98 +119,98 @@ const FIELD_CONFIGS: Record<CronField, CronFieldConfig> = {
|
||||
min: 0,
|
||||
max: 59,
|
||||
step: 1,
|
||||
allowSpecial: ['*', '?'],
|
||||
allowSpecial: ["*", "?"],
|
||||
},
|
||||
minutes: {
|
||||
min: 0,
|
||||
max: 59,
|
||||
step: 1,
|
||||
allowSpecial: ['*', '?'],
|
||||
allowSpecial: ["*", "?"],
|
||||
},
|
||||
hours: {
|
||||
min: 0,
|
||||
max: 23,
|
||||
step: 1,
|
||||
allowSpecial: ['*', '?'],
|
||||
allowSpecial: ["*", "?"],
|
||||
},
|
||||
dayOfMonth: {
|
||||
min: 1,
|
||||
max: 31,
|
||||
step: 1,
|
||||
allowSpecial: ['*', '?', 'L', 'W'],
|
||||
allowSpecial: ["*", "?", "L", "W"],
|
||||
options: [
|
||||
{ label: 'Any day', value: '*' },
|
||||
{ label: 'No specific day', value: '?' },
|
||||
{ label: 'Last day', value: 'L' },
|
||||
{ label: 'Weekday', value: 'W' },
|
||||
{ label: "Any day", value: "*" },
|
||||
{ label: "No specific day", value: "?" },
|
||||
{ label: "Last day", value: "L" },
|
||||
{ label: "Weekday", value: "W" },
|
||||
],
|
||||
},
|
||||
month: {
|
||||
min: 1,
|
||||
max: 12,
|
||||
step: 1,
|
||||
allowSpecial: ['*'],
|
||||
allowSpecial: ["*"],
|
||||
options: [
|
||||
{ label: 'January', value: 1 },
|
||||
{ label: 'February', value: 2 },
|
||||
{ label: 'March', value: 3 },
|
||||
{ label: 'April', value: 4 },
|
||||
{ label: 'May', value: 5 },
|
||||
{ label: 'June', value: 6 },
|
||||
{ label: 'July', value: 7 },
|
||||
{ label: 'August', value: 8 },
|
||||
{ label: 'September', value: 9 },
|
||||
{ label: 'October', value: 10 },
|
||||
{ label: 'November', value: 11 },
|
||||
{ label: 'December', value: 12 },
|
||||
{ label: "January", value: 1 },
|
||||
{ label: "February", value: 2 },
|
||||
{ label: "March", value: 3 },
|
||||
{ label: "April", value: 4 },
|
||||
{ label: "May", value: 5 },
|
||||
{ label: "June", value: 6 },
|
||||
{ label: "July", value: 7 },
|
||||
{ label: "August", value: 8 },
|
||||
{ label: "September", value: 9 },
|
||||
{ label: "October", value: 10 },
|
||||
{ label: "November", value: 11 },
|
||||
{ label: "December", value: 12 },
|
||||
],
|
||||
},
|
||||
dayOfWeek: {
|
||||
min: 0,
|
||||
max: 6,
|
||||
step: 1,
|
||||
allowSpecial: ['*', '?'],
|
||||
allowSpecial: ["*", "?"],
|
||||
options: [
|
||||
{ label: 'Sunday', value: 0 },
|
||||
{ label: 'Monday', value: 1 },
|
||||
{ label: 'Tuesday', value: 2 },
|
||||
{ label: 'Wednesday', value: 3 },
|
||||
{ label: 'Thursday', value: 4 },
|
||||
{ label: 'Friday', value: 5 },
|
||||
{ label: 'Saturday', value: 6 },
|
||||
{ label: "Sunday", value: 0 },
|
||||
{ label: "Monday", value: 1 },
|
||||
{ label: "Tuesday", value: 2 },
|
||||
{ label: "Wednesday", value: 3 },
|
||||
{ label: "Thursday", value: 4 },
|
||||
{ label: "Friday", value: 5 },
|
||||
{ label: "Saturday", value: 6 },
|
||||
],
|
||||
},
|
||||
year: {
|
||||
min: 0,
|
||||
max: 9999,
|
||||
step: 1,
|
||||
allowSpecial: ['*', '?'],
|
||||
allowSpecial: ["*", "?"],
|
||||
},
|
||||
};
|
||||
|
||||
const PERIOD_CONFIGS = {
|
||||
minute: {
|
||||
label: CronPeriod.Minute,
|
||||
description: 'Run every minute',
|
||||
template: '0 * * * * *',
|
||||
description: "Run every minute",
|
||||
template: "0 * * * * *",
|
||||
fields: [CronField.Minutes],
|
||||
},
|
||||
hourly: {
|
||||
label: CronPeriod.Hourly,
|
||||
description: 'Run every hour',
|
||||
template: '0 0 * * * *',
|
||||
description: "Run every hour",
|
||||
template: "0 0 * * * *",
|
||||
fields: [CronField.Minutes, CronField.Hours],
|
||||
},
|
||||
daily: {
|
||||
label: CronPeriod.Daily,
|
||||
description: 'Run every day',
|
||||
template: '0 0 0 * * *',
|
||||
description: "Run every day",
|
||||
template: "0 0 0 * * *",
|
||||
fields: [CronField.Seconds, CronField.Minutes, CronField.Hours],
|
||||
},
|
||||
weekly: {
|
||||
label: CronPeriod.Weekly,
|
||||
description: 'Run every week',
|
||||
template: '0 0 0 * * 0',
|
||||
description: "Run every week",
|
||||
template: "0 0 0 * * 0",
|
||||
fields: [
|
||||
CronField.Seconds,
|
||||
CronField.Minutes,
|
||||
@ -220,8 +220,8 @@ const PERIOD_CONFIGS = {
|
||||
},
|
||||
monthly: {
|
||||
label: CronPeriod.Monthly,
|
||||
description: 'Run every month',
|
||||
template: '0 0 0 1 * *',
|
||||
description: "Run every month",
|
||||
template: "0 0 0 1 * *",
|
||||
fields: [
|
||||
CronField.Seconds,
|
||||
CronField.Minutes,
|
||||
@ -231,8 +231,8 @@ const PERIOD_CONFIGS = {
|
||||
},
|
||||
yearly: {
|
||||
label: CronPeriod.Yearly,
|
||||
description: 'Run every year',
|
||||
template: '0 0 0 1 1 *',
|
||||
description: "Run every year",
|
||||
template: "0 0 0 1 1 *",
|
||||
fields: [
|
||||
CronField.Seconds,
|
||||
CronField.Minutes,
|
||||
@ -243,8 +243,8 @@ const PERIOD_CONFIGS = {
|
||||
},
|
||||
custom: {
|
||||
label: CronPeriod.Custom,
|
||||
description: 'Custom expression',
|
||||
template: '0 0 * * * *',
|
||||
description: "Custom expression",
|
||||
template: "0 0 * * * *",
|
||||
fields: [
|
||||
CronField.Seconds,
|
||||
CronField.Minutes,
|
||||
@ -257,8 +257,8 @@ const PERIOD_CONFIGS = {
|
||||
} as const;
|
||||
|
||||
const CronBuilder: FC<CronBuilderProps> = ({
|
||||
timezone = 'UTC',
|
||||
value = '0 0 * * * *',
|
||||
timezone = "UTC",
|
||||
value = "0 0 * * * *",
|
||||
onChange,
|
||||
className,
|
||||
disabled = false,
|
||||
@ -301,7 +301,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
});
|
||||
return matches.map((match) => new Date(match));
|
||||
} catch (error) {
|
||||
console.error('Failed to get future matched runs', error);
|
||||
console.error("Failed to get future matched runs", error);
|
||||
return [];
|
||||
}
|
||||
}, [currentExpression, showPreview, timezone]);
|
||||
@ -327,7 +327,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
|
||||
const handlePeriodChange = useCallback((period: CronPeriod) => {
|
||||
setActiveTab(period);
|
||||
if (period !== 'custom') {
|
||||
if (period !== "custom") {
|
||||
const config = PERIOD_CONFIGS[period];
|
||||
setCronFields(parseCronExpression(config.template));
|
||||
}
|
||||
@ -335,7 +335,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
|
||||
const filteredPresets = useMemo(() => {
|
||||
return presets.filter((preset) => {
|
||||
if (activeTab === 'custom') {
|
||||
if (activeTab === "custom") {
|
||||
return true;
|
||||
}
|
||||
return preset.category === activeTab;
|
||||
@ -343,7 +343,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
}, [presets, activeTab]);
|
||||
|
||||
return (
|
||||
<div className={cn(withCard && 'space-y-6', className)}>
|
||||
<div className={cn(withCard && "space-y-6", className)}>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => handlePeriodChange(v as CronPeriod)}
|
||||
@ -353,11 +353,11 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
className="grid w-(--all-grids-width) grid-cols-7 whitespace-nowrap lg:w-full"
|
||||
style={
|
||||
{
|
||||
'--my-grid-cols': `grid-template-columns: repeat(${displayPeriods.length}, minmax(0, 1fr))`,
|
||||
'--all-grids-width':
|
||||
"--my-grid-cols": `grid-template-columns: repeat(${displayPeriods.length}, minmax(0, 1fr))`,
|
||||
"--all-grids-width":
|
||||
displayPeriods.length > 4
|
||||
? `${displayPeriods.length * 25 - 20}%`
|
||||
: '100%',
|
||||
: "100%",
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
@ -377,10 +377,10 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
<TabsContent
|
||||
key={period}
|
||||
value={period}
|
||||
className={cn(withCard ? 'space-y-4' : 'px-0')}
|
||||
className={cn(withCard ? "space-y-4" : "px-0")}
|
||||
>
|
||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
||||
<CardHeader className={cn('pb-1', !withCard && 'px-0')}>
|
||||
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||
<CardHeader className={cn("pb-1", !withCard && "px-0")}>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Settings className="h-4 w-4" />
|
||||
<span className="capitalize">
|
||||
@ -391,7 +391,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
{PERIOD_CONFIGS[period].description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className={cn('space-y-4', !withCard && 'px-0')}>
|
||||
<CardContent className={cn("space-y-4", !withCard && "px-0")}>
|
||||
<CronFieldEditor
|
||||
period={period}
|
||||
fields={cronFields}
|
||||
@ -402,8 +402,8 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
</Card>
|
||||
|
||||
{showPresets && filteredPresets.length > 0 && (
|
||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
||||
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||
<CardHeader className={cn(!withCard && "px-0")}>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Zap className="h-4 w-4" />
|
||||
Quick Presets
|
||||
@ -412,7 +412,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
Common cron expressions for quick setup
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className={cn(!withCard && 'px-0')}>
|
||||
<CardContent className={cn(!withCard && "px-0")}>
|
||||
<div className="grid gap-3 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{filteredPresets.map((preset, index) => (
|
||||
<Button
|
||||
@ -447,14 +447,14 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
||||
</Tabs>
|
||||
{/* Current Expression & Preview */}
|
||||
{showGeneratedExpression && (
|
||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
||||
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||
<CardHeader className={cn(!withCard && "px-0")}>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Clock className="h-4 w-4" />
|
||||
Generated Expression
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className={cn('space-y-4', !withCard && 'px-0')}>
|
||||
<CardContent className={cn("space-y-4", !withCard && "px-0")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="px-3 py-1 font-mono text-sm">
|
||||
{currentExpression}
|
||||
@ -532,8 +532,8 @@ const CronFieldEditor: FC<CronFieldEditorProps> = ({
|
||||
};
|
||||
|
||||
const CronFieldItemAnyOrSpecificOption = {
|
||||
Any: 'any',
|
||||
Specific: 'specific',
|
||||
Any: "any",
|
||||
Specific: "specific",
|
||||
} as const;
|
||||
|
||||
type CronFieldItemAnyOrSpecificOption =
|
||||
@ -548,11 +548,11 @@ interface CronFieldItemEditorProps {
|
||||
}
|
||||
|
||||
function encodeCronFieldItem(value: string): string {
|
||||
if (value === '') {
|
||||
return '<meta:empty>';
|
||||
if (value === "") {
|
||||
return "<meta:empty>";
|
||||
}
|
||||
|
||||
if (value.includes(' ')) {
|
||||
if (value.includes(" ")) {
|
||||
return `<meta:contains-space:${encodeURIComponent(value)}>`;
|
||||
}
|
||||
|
||||
@ -560,15 +560,15 @@ function encodeCronFieldItem(value: string): string {
|
||||
}
|
||||
|
||||
function decodeCronFieldItem(value: string): string {
|
||||
if (value.startsWith('<meta:contains')) {
|
||||
if (value.startsWith("<meta:contains")) {
|
||||
return decodeURIComponent(
|
||||
// biome-ignore lint/performance/useTopLevelRegex: false
|
||||
value.replace(/^<meta:contains-space:([^>]+)>$/, '$1')
|
||||
value.replace(/^<meta:contains-space:([^>]+)>$/, "$1")
|
||||
);
|
||||
}
|
||||
|
||||
if (value === '<meta:empty>') {
|
||||
return '';
|
||||
if (value === "<meta:empty>") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -582,7 +582,7 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
||||
|
||||
const [anyOrSpecificOption, _setAnyOrSpecificOption] =
|
||||
useState<CronFieldItemAnyOrSpecificOption>(() =>
|
||||
innerValue === '*'
|
||||
innerValue === "*"
|
||||
? CronFieldItemAnyOrSpecificOption.Any
|
||||
: CronFieldItemAnyOrSpecificOption.Specific
|
||||
);
|
||||
@ -607,9 +607,9 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
||||
(v: CronFieldItemAnyOrSpecificOption) => {
|
||||
_setAnyOrSpecificOption(v);
|
||||
if (v === CronFieldItemAnyOrSpecificOption.Any) {
|
||||
handleChange('*');
|
||||
handleChange("*");
|
||||
} else if (v === CronFieldItemAnyOrSpecificOption.Specific) {
|
||||
handleChange('0');
|
||||
handleChange("0");
|
||||
}
|
||||
},
|
||||
[handleChange]
|
||||
@ -618,10 +618,10 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium text-sm capitalize">
|
||||
{field.replace(/([A-Z])/g, ' $1').toLowerCase()}
|
||||
{field.replace(/([A-Z])/g, " $1").toLowerCase()}
|
||||
</Label>
|
||||
|
||||
{(field === 'month' || field === 'dayOfWeek') && (
|
||||
{(field === "month" || field === "dayOfWeek") && (
|
||||
<Select
|
||||
value={innerValue}
|
||||
onValueChange={handleChange}
|
||||
@ -640,7 +640,7 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{field === 'dayOfMonth' && (
|
||||
{field === "dayOfMonth" && (
|
||||
<div className="space-y-2">
|
||||
<Select
|
||||
value={innerValue}
|
||||
@ -666,9 +666,9 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
||||
</div>
|
||||
)}
|
||||
{!(
|
||||
field === 'month' ||
|
||||
field === 'dayOfWeek' ||
|
||||
field === 'dayOfMonth'
|
||||
field === "month" ||
|
||||
field === "dayOfWeek" ||
|
||||
field === "dayOfMonth"
|
||||
) && (
|
||||
<div className="space-y-2">
|
||||
<ToggleGroup
|
||||
@ -722,21 +722,21 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
||||
);
|
||||
|
||||
function parseCronExpression(expression: string): Record<CronField, string> {
|
||||
const parts = expression.split(' ');
|
||||
const parts = expression.split(" ");
|
||||
|
||||
// Ensure we have 6 parts, pad with defaults if needed
|
||||
while (parts.length < 6) {
|
||||
parts.push('*');
|
||||
parts.push("*");
|
||||
}
|
||||
|
||||
return {
|
||||
seconds: parts[0] || '0',
|
||||
minutes: parts[1] || '*',
|
||||
hours: parts[2] || '*',
|
||||
dayOfMonth: parts[3] || '*',
|
||||
month: parts[4] || '*',
|
||||
dayOfWeek: parts[5] || '*',
|
||||
year: parts[6] || '*',
|
||||
seconds: parts[0] || "0",
|
||||
minutes: parts[1] || "*",
|
||||
hours: parts[2] || "*",
|
||||
dayOfMonth: parts[3] || "*",
|
||||
month: parts[4] || "*",
|
||||
dayOfWeek: parts[5] || "*",
|
||||
year: parts[6] || "*",
|
||||
};
|
||||
}
|
||||
|
@ -1,67 +1,67 @@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Code2, Play, Settings, Type } from "lucide-react";
|
||||
import { type FC, useCallback, useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Code2, Play, Settings, Type } from 'lucide-react';
|
||||
import { type FC, useCallback, useState } from 'react';
|
||||
import { CronBuilder } from './cron-builder.js';
|
||||
import { CronDisplay } from './cron-display.js';
|
||||
import { CronInput } from './cron-input.js';
|
||||
import { Cron } from './cron.js';
|
||||
} from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Cron } from "./cron.jsx";
|
||||
import { CronBuilder } from "./cron-builder.jsx";
|
||||
import { CronDisplay } from "./cron-display.jsx";
|
||||
import { CronInput } from "./cron-input.jsx";
|
||||
|
||||
const CronExample: FC = () => {
|
||||
const [inputValue, setInputValue] = useState('0 30 9 * * 1-5');
|
||||
const [builderValue, setBuilderValue] = useState('0 0 */6 * * *');
|
||||
const [fullValue, setFullValue] = useState('0 */15 * * * *');
|
||||
const [displayValue] = useState('0 0 0 * * 0');
|
||||
const [inputValue, setInputValue] = useState("0 30 9 * * 1-5");
|
||||
const [builderValue, setBuilderValue] = useState("0 0 */6 * * *");
|
||||
const [fullValue, setFullValue] = useState("0 */15 * * * *");
|
||||
const [displayValue] = useState("0 0 0 * * 0");
|
||||
|
||||
const examples = [
|
||||
{
|
||||
label: 'Every minute',
|
||||
expression: '0 * * * * *',
|
||||
description: 'Runs at the start of every minute',
|
||||
label: "Every minute",
|
||||
expression: "0 * * * * *",
|
||||
description: "Runs at the start of every minute",
|
||||
},
|
||||
{
|
||||
label: 'Every 5 minutes',
|
||||
expression: '0 */5 * * * *',
|
||||
description: 'Runs every 5 minutes',
|
||||
label: "Every 5 minutes",
|
||||
expression: "0 */5 * * * *",
|
||||
description: "Runs every 5 minutes",
|
||||
},
|
||||
{
|
||||
label: 'Every hour',
|
||||
expression: '0 0 * * * *',
|
||||
description: 'Runs at the start of every hour',
|
||||
label: "Every hour",
|
||||
expression: "0 0 * * * *",
|
||||
description: "Runs at the start of every hour",
|
||||
},
|
||||
{
|
||||
label: 'Daily at 9 AM',
|
||||
expression: '0 0 9 * * *',
|
||||
description: 'Runs every day at 9:00 AM',
|
||||
label: "Daily at 9 AM",
|
||||
expression: "0 0 9 * * *",
|
||||
description: "Runs every day at 9:00 AM",
|
||||
},
|
||||
{
|
||||
label: 'Weekdays at 9:30 AM',
|
||||
expression: '0 30 9 * * 1-5',
|
||||
description: 'Runs Monday through Friday at 9:30 AM',
|
||||
label: "Weekdays at 9:30 AM",
|
||||
expression: "0 30 9 * * 1-5",
|
||||
description: "Runs Monday through Friday at 9:30 AM",
|
||||
},
|
||||
{
|
||||
label: 'Every Sunday',
|
||||
expression: '0 0 0 * * 0',
|
||||
description: 'Runs every Sunday at midnight',
|
||||
label: "Every Sunday",
|
||||
expression: "0 0 0 * * 0",
|
||||
description: "Runs every Sunday at midnight",
|
||||
},
|
||||
{
|
||||
label: 'First day of month',
|
||||
expression: '0 0 0 1 * *',
|
||||
description: 'Runs on the 1st day of every month',
|
||||
label: "First day of month",
|
||||
expression: "0 0 0 1 * *",
|
||||
description: "Runs on the 1st day of every month",
|
||||
},
|
||||
{
|
||||
label: 'Every quarter',
|
||||
expression: '0 0 0 1 */3 *',
|
||||
description: 'Runs on the 1st day of every quarter',
|
||||
label: "Every quarter",
|
||||
expression: "0 0 0 1 */3 *",
|
||||
description: "Runs on the 1st day of every quarter",
|
||||
},
|
||||
];
|
||||
|
||||
@ -69,7 +69,7 @@ const CronExample: FC = () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(expression);
|
||||
} catch (error) {
|
||||
console.warn('Failed to copy to clipboard:', error);
|
||||
console.warn("Failed to copy to clipboard:", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -165,7 +165,7 @@ const CronExample: FC = () => {
|
||||
<div className="rounded bg-muted p-4">
|
||||
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{fullValue || 'No expression set'}
|
||||
{fullValue || "No expression set"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
@ -196,7 +196,7 @@ const CronExample: FC = () => {
|
||||
<div className="rounded bg-muted p-4">
|
||||
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{inputValue || 'No expression set'}
|
||||
{inputValue || "No expression set"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
@ -244,7 +244,7 @@ const CronExample: FC = () => {
|
||||
<div className="rounded bg-muted p-4">
|
||||
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{builderValue || 'No expression set'}
|
||||
{builderValue || "No expression set"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
@ -1,4 +1,4 @@
|
||||
import { parse } from '@datasert/cronjs-parser';
|
||||
import { parse } from "@datasert/cronjs-parser";
|
||||
import {
|
||||
AlertCircle,
|
||||
Bolt,
|
||||
@ -7,45 +7,45 @@ import {
|
||||
Copy,
|
||||
Settings,
|
||||
Type,
|
||||
} from 'lucide-react';
|
||||
import { type FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
} from "lucide-react";
|
||||
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { cn } from '@/presentation/utils';
|
||||
import { CronBuilder } from './cron-builder';
|
||||
import { CronDisplay } from './cron-display';
|
||||
import { CronInput } from './cron-input';
|
||||
} from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { cn } from "@/presentation/utils";
|
||||
import { CronBuilder } from "./cron-builder.js";
|
||||
import { CronDisplay } from "./cron-display.js";
|
||||
import { CronInput } from "./cron-input.js";
|
||||
import {
|
||||
CronMode,
|
||||
type CronPrimitiveMode,
|
||||
type CronProps,
|
||||
type CronValidationResult,
|
||||
} from './types';
|
||||
} from "./types.js";
|
||||
|
||||
const PLACEHOLDER = '0 0 * * * *';
|
||||
const PLACEHOLDER = "0 0 * * * *";
|
||||
|
||||
const Cron: FC<CronProps> = ({
|
||||
value = '',
|
||||
value = "",
|
||||
onChange,
|
||||
activeMode = 'input',
|
||||
activeMode = "input",
|
||||
onActiveModeChange,
|
||||
onValidate,
|
||||
className,
|
||||
mode = 'both',
|
||||
mode = "both",
|
||||
disabled = false,
|
||||
placeholder = PLACEHOLDER,
|
||||
showPreview = true,
|
||||
showDescription = true,
|
||||
timezone = 'UTC',
|
||||
timezone = "UTC",
|
||||
error,
|
||||
children,
|
||||
showHelp = true,
|
||||
@ -57,7 +57,7 @@ const Cron: FC<CronProps> = ({
|
||||
isFirstSibling = false,
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: false
|
||||
}) => {
|
||||
const [internalValue, setInternalValue] = useState(value || '');
|
||||
const [internalValue, setInternalValue] = useState(value || "");
|
||||
const [internalActiveMode, setInternalActiveMode] =
|
||||
useState<CronPrimitiveMode>(
|
||||
mode === CronMode.Both ? activeMode : (mode as CronPrimitiveMode)
|
||||
@ -66,7 +66,7 @@ const Cron: FC<CronProps> = ({
|
||||
|
||||
const validationResult = useMemo((): CronValidationResult => {
|
||||
if (!internalValue.trim()) {
|
||||
return { isValid: false, error: 'Expression is required', isEmpty: true };
|
||||
return { isValid: false, error: "Expression is required", isEmpty: true };
|
||||
}
|
||||
|
||||
try {
|
||||
@ -78,13 +78,13 @@ const Cron: FC<CronProps> = ({
|
||||
error:
|
||||
parseError instanceof Error
|
||||
? parseError.message
|
||||
: 'Invalid cron expression',
|
||||
: "Invalid cron expression",
|
||||
};
|
||||
}
|
||||
}, [internalValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(value || '');
|
||||
setInternalValue(value || "");
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -92,7 +92,7 @@ const Cron: FC<CronProps> = ({
|
||||
}, [validationResult.isValid, onValidate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === 'both') {
|
||||
if (mode === "both") {
|
||||
setInternalActiveMode(activeMode);
|
||||
}
|
||||
}, [activeMode, mode]);
|
||||
@ -123,16 +123,16 @@ const Cron: FC<CronProps> = ({
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (e) {
|
||||
console.warn('Failed to copy to clipboard:', e);
|
||||
console.warn("Failed to copy to clipboard:", e);
|
||||
}
|
||||
}, [internalValue]);
|
||||
|
||||
const hasError =
|
||||
!!error || !!(!validationResult.isValid && internalValue.trim());
|
||||
|
||||
if (mode === 'input') {
|
||||
if (mode === "input") {
|
||||
return (
|
||||
<div className={cn(withCard && 'space-y-4', className)}>
|
||||
<div className={cn(withCard && "space-y-4", className)}>
|
||||
<CronInput
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
@ -161,9 +161,9 @@ const Cron: FC<CronProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === 'builder') {
|
||||
if (mode === "builder") {
|
||||
return (
|
||||
<div className={cn(withCard && 'space-y-4', className)}>
|
||||
<div className={cn(withCard && "space-y-4", className)}>
|
||||
<CronBuilder
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
@ -184,14 +184,14 @@ const Cron: FC<CronProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(withCard && 'space-y-6', className)}>
|
||||
<div className={cn(withCard && "space-y-6", className)}>
|
||||
<Card
|
||||
className={cn(
|
||||
!withCard && 'border-none shadow-none',
|
||||
!withCard && isFirstSibling && 'pt-0'
|
||||
!withCard && "border-none shadow-none",
|
||||
!withCard && isFirstSibling && "pt-0"
|
||||
)}
|
||||
>
|
||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
||||
<CardHeader className={cn(!withCard && "px-0")}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
@ -207,7 +207,7 @@ const Cron: FC<CronProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={
|
||||
validationResult.isValid ? 'secondary' : 'destructive'
|
||||
validationResult.isValid ? "secondary" : "destructive"
|
||||
}
|
||||
className="font-mono text-sm"
|
||||
>
|
||||
@ -238,11 +238,11 @@ const Cron: FC<CronProps> = ({
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className={cn(!withCard && 'px-0')}>
|
||||
<CardContent className={cn(!withCard && "px-0")}>
|
||||
<Tabs
|
||||
value={internalActiveMode}
|
||||
onValueChange={(v) =>
|
||||
handleActiveModeChange(v as 'input' | 'builder')
|
||||
handleActiveModeChange(v as "input" | "builder")
|
||||
}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
@ -314,14 +314,14 @@ const Cron: FC<CronProps> = ({
|
||||
{showHelp && (
|
||||
<>
|
||||
{!withCard && <Separator />}
|
||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
||||
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||
<CardHeader className={cn(!withCard && "px-0")}>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Code2 className="h-4 w-4" />
|
||||
Cron Expression Format
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className={cn(!withCard && 'px-0')}>
|
||||
<CardContent className={cn(!withCard && "px-0")}>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-6 gap-2 text-center text-sm">
|
||||
<div className="space-y-1">
|
@ -1,8 +1,8 @@
|
||||
export { Cron } from './cron';
|
||||
export { CronBuilder } from './cron-builder';
|
||||
export { CronDisplay } from './cron-display';
|
||||
export { CronExample } from './cron-example';
|
||||
export { CronInput } from './cron-input';
|
||||
export { Cron } from "./cron.js";
|
||||
export { CronBuilder } from "./cron-builder.js";
|
||||
export { CronDisplay } from "./cron-display.js";
|
||||
export { CronExample } from "./cron-example.js";
|
||||
export { CronInput } from "./cron-input.js";
|
||||
|
||||
export {
|
||||
type CronBuilderProps,
|
||||
@ -17,4 +17,4 @@ export {
|
||||
type CronProps,
|
||||
type CronValidationResult,
|
||||
type PeriodConfig,
|
||||
} from './types';
|
||||
} from "./types.js";
|
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,12 @@ import { DOCUMENT } from '../platform/injection';
|
||||
export class IntlService {
|
||||
document = inject(DOCUMENT);
|
||||
|
||||
get Intl(): typeof Intl {
|
||||
return this.document.defaultView?.Intl as typeof Intl;
|
||||
}
|
||||
|
||||
get timezone() {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
return this.Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
|
||||
formatTimestamp(timestamp: number, options?: Intl.DateTimeFormatOptions) {
|
||||
@ -20,7 +24,7 @@ export class IntlService {
|
||||
...options,
|
||||
};
|
||||
|
||||
return new Intl.DateTimeFormat(
|
||||
return new this.Intl.DateTimeFormat(
|
||||
this.document.defaultView?.navigator.language,
|
||||
{
|
||||
...defaultOptions,
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { Cron } from '@/components/domains/cron';
|
||||
import { CronMode } from '@/components/domains/cron/types';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
@ -9,6 +12,8 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Cron } from '@/components/ui/cron';
|
||||
import { CronMode } from '@/components/ui/cron/types';
|
||||
import {
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
@ -26,11 +31,6 @@ import {
|
||||
SubscriberTaskTypeEnum,
|
||||
} from '@/infra/graphql/gql/graphql';
|
||||
import { IntlService } from '@/infra/intl/intl.service';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const SUBSCRIPTION_TASK_CRON_PRESETS = [
|
||||
{
|
||||
@ -162,59 +162,53 @@ export const SubscriptionCronCreationView = memo(
|
||||
const loading = loadingInsert;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
defaultValue={CRON_TABS[0].tab}
|
||||
className="flex min-h-0 flex-1 flex-col"
|
||||
>
|
||||
<div className="flex w-full shrink-0 overflow-x-auto">
|
||||
<TabsList className="flex items-center justify-center whitespace-nowrap">
|
||||
{CRON_TABS.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.tab}
|
||||
value={tab.tab}
|
||||
className="w-fit px-4"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</div>
|
||||
{CRON_TABS.map((tab) => (
|
||||
<TabsContent
|
||||
key={tab.tab}
|
||||
value={tab.tab}
|
||||
className="flex-1 space-y-2"
|
||||
asChild
|
||||
>
|
||||
<SubscriptionCronForm
|
||||
tab={tab}
|
||||
onComplete={(payload) => {
|
||||
insertCron({
|
||||
variables: {
|
||||
data: {
|
||||
cronExpr: payload.cronExpr,
|
||||
cronTimezone: intlService.timezone,
|
||||
subscriberTaskCron: {
|
||||
subscriptionId,
|
||||
taskType: tab.tab,
|
||||
},
|
||||
<Tabs
|
||||
defaultValue={CRON_TABS[0].tab}
|
||||
className="flex min-h-0 flex-1 flex-col"
|
||||
>
|
||||
<div className="flex w-full shrink-0 overflow-x-auto">
|
||||
<TabsList className="flex items-center justify-center whitespace-nowrap">
|
||||
{CRON_TABS.map((tab) => (
|
||||
<TabsTrigger key={tab.tab} value={tab.tab} className="w-fit px-4">
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</div>
|
||||
{CRON_TABS.map((tab) => (
|
||||
<TabsContent
|
||||
key={tab.tab}
|
||||
value={tab.tab}
|
||||
className="flex-1 space-y-2"
|
||||
asChild
|
||||
>
|
||||
<SubscriptionCronForm
|
||||
tab={tab}
|
||||
onComplete={(payload) => {
|
||||
insertCron({
|
||||
variables: {
|
||||
data: {
|
||||
cronExpr: payload.cronExpr,
|
||||
cronTimezone: intlService.timezone,
|
||||
subscriberTaskCron: {
|
||||
subscriptionId,
|
||||
taskType: tab.tab,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
timezone={intlService.timezone}
|
||||
/>
|
||||
</TabsContent>
|
||||
))}
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex flex-row items-center justify-center gap-2">
|
||||
<Spinner variant="circle-filled" size="16" />
|
||||
<span>Creating cron...</span>
|
||||
</div>
|
||||
)}
|
||||
</Tabs>
|
||||
</>
|
||||
},
|
||||
});
|
||||
}}
|
||||
timezone={intlService.timezone}
|
||||
/>
|
||||
</TabsContent>
|
||||
))}
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex flex-row items-center justify-center gap-2">
|
||||
<Spinner variant="circle-filled" size="16" />
|
||||
<span>Creating cron...</span>
|
||||
</div>
|
||||
)}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -3,8 +3,6 @@ import { createFileRoute } from '@tanstack/react-router';
|
||||
import { format } from 'date-fns';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { CronDisplay } from '@/components/domains/cron';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@ -15,17 +13,20 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { ContainerHeader } from '@/components/ui/container-header';
|
||||
import { CronDisplay } from '@/components/ui/cron';
|
||||
import { DetailCardSkeleton } from '@/components/ui/detail-card-skeleton';
|
||||
import { DetailEmptyView } from '@/components/ui/detail-empty-view';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { QueryErrorView } from '@/components/ui/query-error-view';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { GET_CRONS } from '@/domains/recorder/schema/cron';
|
||||
import { useInject } from '@/infra/di/inject';
|
||||
import {
|
||||
CronStatusEnum,
|
||||
type GetCronsQuery,
|
||||
type GetCronsQueryVariables,
|
||||
} from '@/infra/graphql/gql/graphql';
|
||||
import { IntlService } from '@/infra/intl/intl.service';
|
||||
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
||||
import { getStatusBadge } from './-status-badge';
|
||||
|
||||
@ -38,6 +39,7 @@ export const Route = createFileRoute('/_app/tasks/cron/detail/$id')({
|
||||
|
||||
function CronDetailRouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const intlService = useInject(IntlService);
|
||||
|
||||
const { data, loading, error, refetch } = useQuery<
|
||||
GetCronsQuery,
|
||||
@ -115,7 +117,7 @@ function CronDetailRouteComponent() {
|
||||
{/* Basic Information */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium text-sm">Cron ID</Label>
|
||||
<Label className="font-medium text-sm">ID</Label>
|
||||
<div className="rounded-md bg-muted p-3">
|
||||
<code className="text-sm">{cron.id}</code>
|
||||
</div>
|
||||
@ -129,7 +131,7 @@ function CronDetailRouteComponent() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium text-sm">Retry count</Label>
|
||||
<Label className="font-medium text-sm">Attemps</Label>
|
||||
<div className="rounded-md bg-muted p-3">
|
||||
<span className="text-sm">
|
||||
{cron.attempts} / {cron.maxAttempts}
|
||||
@ -199,7 +201,10 @@ function CronDetailRouteComponent() {
|
||||
<Label className="font-medium text-sm">Created at</Label>
|
||||
<div className="rounded-md bg-muted p-3">
|
||||
<span className="text-sm">
|
||||
{format(new Date(cron.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
||||
{intlService.formatTimestamp(
|
||||
cron.createdAt,
|
||||
'yyyy-MM-dd HH:mm:ss'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -41,9 +41,13 @@
|
||||
"noBannedTypes": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": {
|
||||
"fix": "none",
|
||||
"level": "error"
|
||||
},
|
||||
"noUnusedImports": {
|
||||
"fix": "none",
|
||||
"level": "warn"
|
||||
"level": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user