Fix and update (#139)

* refactor(ui): 统一组件引用类型为ComponentRef

更新所有UI组件中的forwardRef类型,从ElementRef改为ComponentRef以保持一致性
迁移postcss配置至mjs格式并更新依赖版本

* refactor: 优化表单类型定义和验证逻辑

移除自定义的 asOptionalField 工具函数,直接使用 Zod 的 optional() 方法,并明确定义表单数据类型。

* style: 更新UI主题配置和样式变量

将主题风格从default切换为new-york,并重构CSS变量使用OKLCH色彩空间。同时添加tailwindcss-animate插件支持。

* style: 统一页面头部按钮组样式

优化多个页面头部按钮组的布局样式,增加响应式设计和flex-wrap支持

* fix(server): 修复对话框交互问题并优化SWR配置

修复对话框关闭逻辑并阻止外部交互,同时禁用SWR的自动重新验证功能以提升性能。

* feat: 添加日历组件及账单相关国际化

实现基于 react-day-picker 的日历组件,并添加账单管理相关的多语言支持

* style(components): 统一按钮样式并格式化代码

为删除和禁用按钮添加text-white类名,同时调整ServerCard组件中的代码缩进格式。

* perf(build): 优化Vite打包配置与代码分割策略

调整Vite构建配置,改进第三方依赖的分组逻辑并添加UUID支持到安装命令组件

* fix: 修正页面标题翻译不一致问题

将CronPage和ServicePage的标题从"Server"分别改为"Task"和"Service",并优化NotificationGroupPage的按钮组布局。

* fix(auth): 改进登录错误处理和国际化支持

优化登录错误提示,添加多语言支持并移除控制台错误日志。同时修复头部组件透明度样式问题。

* feat: 添加服务器操作下拉菜单

为服务器卡片添加统一的下拉菜单操作入口,整合终端、配置和安装命令功能。

* feat[alert-rule]: 优化告警规则组件性能

重构告警规则组件代码结构,提升渲染效率并减少内存占用。

* docs(i18n): 新增翻译字段

为界面添加"Add"、"Delete"、"AdvancedJSON"和"Save"等关键操作的翻译字段,支持中英文双语显示。

* perf(vite): 优化分包策略以提升构建性能

重构 manualChunks 逻辑,按功能类别分组依赖项,并增加大型库的独立分包规则。

* style: 统一危险操作按钮的文字颜色

在所有确认操作的弹窗按钮中添加白色文字样式,保持视觉一致性。

* fix(components): 调整下拉菜单对齐方式

根据菜单项状态动态设置下拉菜单的对齐方向和起始位置。

* fix(types): 修复在线用户API分页类型

添加ModelOnlineUserApi接口类型,包含分页信息,并移除index.ts中重复的类型定义。

* chore: auto-fix linting and formatting issues

* feat(locales): 添加无过期相关翻译项

为英文和中文翻译文件添加"NoExpiry"、"SetNoExpiry"等无过期相关字段的翻译。

fix(components): 移除重复的图标按钮选项

从IconButton组件中删除重复的"more"图标选项。

* feat(ServerCard): 优化日期选择器并添加下拉提示

为日期选择器添加下拉布局和年份范围限制,并在公共笔记区域增加下拉项生效提示文本。

* chore: auto-fix linting and formatting issues

* style: 优化多个组件的UI交互细节

统一按钮悬停样式并简化国际化文本调用,移除冗余的单位显示和空值判断逻辑。

* refactor(ServerCard): 移除网络路由相关代码

删除 ServerCard 组件中与 plan.networkRoute 相关的字段验证和错误显示逻辑。

* chore: auto-fix linting and formatting issues

* feat(ui): 添加Switch组件并改进服务器表单交互

- 新增Radix UI Switch组件依赖及实现
- 将IPv4/IPv6输入改为开关控件,优化用户体验
- 添加"按量付费"选项和新的翻译字段
- 改进网络路由和备注输入的占位提示
- 修复暗黑模式下的按钮背景色

* style(components): 为禁止按钮添加白色文本样式

* chore: auto-fix linting and formatting issues

* fix(ServerCard): 修复日期选择器样式和滚动问题

调整日期选择器的宽度和高度限制,添加滚动容器以解决内容溢出问题

* refactor(server-config): 简化复选框checked属性的布尔转换

使用!!操作符简化controllerField.value的布尔值转换,使代码更简洁

* feat(国际化): 添加告警规则和搜索框的国际化支持

为告警规则组件添加多语言支持,包括服务器监控选项、忽略提示和示例文本。同时将搜索框的占位文本替换为国际化字段。

* chore: auto-fix linting and formatting issues

* fix(switch): 修正 Switch 组件 ref 类型定义错误

---------

Co-authored-by: Guccen <171530509+Chillln@users.noreply.github.com>
This commit is contained in:
Chillln
2025-10-02 14:23:11 +08:00
committed by GitHub
parent 875750d74e
commit bb288c554f
61 changed files with 2214 additions and 446 deletions
+694 -18
View File
@@ -1,5 +1,6 @@
import { updateServer } from "@/api/server"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Checkbox } from "@/components/ui/checkbox"
import {
Dialog,
@@ -21,7 +22,16 @@ import {
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { ScrollArea } from "@/components/ui/scroll-area"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import { IconButton } from "@/components/xui/icon-button"
import { conv } from "@/lib/utils"
@@ -49,7 +59,7 @@ const serverFormSchema = z.object({
enable_ddns: asOptionalField(z.boolean()),
ddns_profiles: asOptionalField(z.array(z.number())),
ddns_profiles_raw: asOptionalField(z.string()),
override_ddns_domains: asOptionalField(z.record(z.coerce.number().int(), z.array(z.string()))),
override_ddns_domains: asOptionalField(z.record(z.string(), z.array(z.string()))),
override_ddns_domains_raw: asOptionalField(
z.string().refine(
(val) => {
@@ -69,8 +79,8 @@ const serverFormSchema = z.object({
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
const { t } = useTranslation()
const form = useForm<z.infer<typeof serverFormSchema>>({
resolver: zodResolver(serverFormSchema),
const form = useForm({
resolver: zodResolver(serverFormSchema) as any,
defaultValues: {
...data,
ddns_profiles_raw: data.ddns_profiles ? conv.arrToStr(data.ddns_profiles) : undefined,
@@ -85,7 +95,131 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
const [open, setOpen] = useState(false)
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
type PublicNote = {
billingDataMod: {
startDate: string
endDate: string
autoRenewal: string
cycle: string
amount: string
}
planDataMod: {
bandwidth: string
trafficVol: string
trafficType: string
IPv4: string
IPv6: string
networkRoute: string
extra: string
}
}
const defaultPublicNote: PublicNote = {
billingDataMod: {
startDate: "",
endDate: "",
autoRenewal: "",
cycle: "",
amount: "",
},
planDataMod: {
bandwidth: "",
trafficVol: "",
trafficType: "",
IPv4: "0",
IPv6: "0",
networkRoute: "",
extra: "",
},
}
const parsePublicNote = (s?: string): PublicNote => {
if (!s) return defaultPublicNote
try {
const obj = JSON.parse(s)
return {
billingDataMod: {
startDate: obj?.billingDataMod?.startDate ?? "",
endDate: obj?.billingDataMod?.endDate ?? "",
autoRenewal: obj?.billingDataMod?.autoRenewal ?? "",
cycle: obj?.billingDataMod?.cycle ?? "",
amount: obj?.billingDataMod?.amount ?? "",
},
planDataMod: {
bandwidth: obj?.planDataMod?.bandwidth ?? "",
trafficVol: obj?.planDataMod?.trafficVol ?? "",
trafficType: obj?.planDataMod?.trafficType ?? "",
IPv4: obj?.planDataMod?.IPv4 === "1" ? "1" : "0",
IPv6: obj?.planDataMod?.IPv6 === "1" ? "1" : "0",
networkRoute: obj?.planDataMod?.networkRoute ?? "",
extra: obj?.planDataMod?.extra ?? "",
},
}
} catch {
return defaultPublicNote
}
}
const [publicNoteObj, setPublicNoteObj] = useState<PublicNote>(
parsePublicNote(data?.public_note),
)
const [publicNoteErrors, setPublicNoteErrors] = useState<
Partial<
Record<
| "billing.startDate"
| "billing.endDate"
| "billing.autoRenewal"
| "billing.cycle"
| "billing.amount"
| "plan.bandwidth"
| "plan.trafficVol"
| "plan.trafficType"
| "plan.IPv4"
| "plan.IPv6"
| "plan.extra",
string
>
>
>({})
const isValidISOLike = (v: string) => {
if (!v) return true
// special marker for "no expiry"
if (v === "0000-00-00T23:59:59+08:00") return true
const d = new Date(v)
return !isNaN(d.getTime())
}
const validatePublicNote = (pn: PublicNote) => {
const errs: Partial<Record<string, string>> = {}
if (pn.billingDataMod.startDate && !isValidISOLike(pn.billingDataMod.startDate)) {
errs["billing.startDate"] = t("Validation.InvalidDate")
}
if (pn.billingDataMod.endDate && !isValidISOLike(pn.billingDataMod.endDate)) {
errs["billing.endDate"] = t("Validation.InvalidDate")
}
if (pn.billingDataMod.autoRenewal && !/^(0|1)$/.test(pn.billingDataMod.autoRenewal)) {
errs["billing.autoRenewal"] = t("Validation.MustBe0Or1")
}
if (pn.billingDataMod.cycle && !/^(Day|Week|Month|Year)$/i.test(pn.billingDataMod.cycle)) {
errs["billing.cycle"] = t("Validation.MustBeDayWeekMonthYear")
}
// amount 允许任意非空字符串或空
if (pn.planDataMod.trafficType && !/^(1|2)$/.test(pn.planDataMod.trafficType)) {
errs["plan.trafficType"] = t("Validation.MustBe1Or2")
}
if (!/^(0|1)$/.test(pn.planDataMod.IPv4)) {
errs["plan.IPv4"] = t("Validation.MustBe0Or1")
}
if (!/^(0|1)$/.test(pn.planDataMod.IPv6)) {
errs["plan.IPv6"] = t("Validation.MustBe0Or1")
}
return { errors: errs, valid: Object.keys(errs).length === 0 }
}
const onSubmit = async (values: any) => {
try {
values.ddns_profiles = values.ddns_profiles_raw
? conv.strToArr(values.ddns_profiles_raw).map(Number)
@@ -93,6 +227,36 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
values.override_ddns_domains = values.override_ddns_domains_raw
? JSON.parse(values.override_ddns_domains_raw)
: undefined
// validate structured fields
const { errors, valid } = validatePublicNote(publicNoteObj)
if (!valid) {
setPublicNoteErrors(errors)
toast(t("Error"), { description: t("Validation.InvalidForm") })
return
}
setPublicNoteErrors({})
// normalize datetime-local to ISO string if provided
const normalizeISO = (v: string) => {
if (!v) return v
// keep special "no expiry" value as-is
if (v === "0000-00-00T23:59:59+08:00") return v
const date = new Date(v)
return isNaN(date.getTime()) ? v : date.toISOString()
}
const pnNormalized: PublicNote = {
billingDataMod: {
...publicNoteObj.billingDataMod,
startDate: normalizeISO(publicNoteObj.billingDataMod.startDate),
endDate: normalizeISO(publicNoteObj.billingDataMod.endDate),
// keep others as-is
},
planDataMod: { ...publicNoteObj.planDataMod },
}
// serialize structured public note back to JSON string
values.public_note = JSON.stringify(pnNormalized)
await updateServer(data!.id!, values)
} catch (e) {
console.error(e)
@@ -111,7 +275,11 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
<DialogTrigger asChild>
<IconButton variant="outline" icon="edit" />
</DialogTrigger>
<DialogContent className="sm:max-w-xl">
<DialogContent
className="sm:max-w-xl"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
<div className="items-center mx-1">
<DialogHeader>
@@ -236,19 +404,527 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="public_note"
render={({ field }) => (
<FormItem>
<FormLabel>{t("Public") + t("Note")}</FormLabel>
<FormControl>
<Textarea className="resize-y" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Structured Public Note fields */}
<div className="space-y-3">
<div className="space-y-1">
<FormLabel>{t("Public") + t("Note")}</FormLabel>
<p className="text-xs text-muted-foreground">
{t("PublicNote.DropdownHint")}
</p>
</div>
<div className="rounded-md border p-3 space-y-3">
<div className="text-sm font-medium opacity-80">
{t("PublicNote.Billing")}
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.StartDate")}
</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left font-normal"
>
{publicNoteObj.billingDataMod.startDate
? new Date(
publicNoteObj.billingDataMod.startDate,
).toLocaleDateString()
: "YYYY-MM-DD"}
</Button>
</PopoverTrigger>
<PopoverContent
className="p-0 w-[300px] max-h-[60dvh] overflow-hidden"
align="start"
>
<div className="max-h-[500px] overflow-y-auto">
<Calendar
className="w-full min-h-[320px]"
mode="single"
captionLayout="dropdown"
startMonth={new Date(2000, 0)}
endMonth={new Date(2050, 11)}
selected={
publicNoteObj.billingDataMod
.startDate
? new Date(
publicNoteObj.billingDataMod.startDate,
)
: undefined
}
onSelect={(d) => {
if (!d) return
setPublicNoteObj((prev) => {
const prevDateStr =
prev.billingDataMod
.startDate
if (prevDateStr) {
const pd = new Date(
prevDateStr,
)
// 仅在有效日期时复制时分秒
if (
!isNaN(pd.getTime())
) {
d.setHours(
pd.getHours(),
pd.getMinutes(),
pd.getSeconds(),
0,
)
}
}
return {
...prev,
billingDataMod: {
...prev.billingDataMod,
startDate:
d.toISOString(),
},
}
})
}}
autoFocus
/>
</div>
</PopoverContent>
</Popover>
{publicNoteErrors["billing.startDate"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["billing.startDate"]}
</p>
)}
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<Label className="text-xs">
{t("PublicNote.EndDate")}
</Label>
<Button
type="button"
variant="outline"
className="text-xs px-2 py-0 h-auto bg-gray-200 dark:bg-gray-700"
onClick={() =>
setPublicNoteObj((prev) => ({
...prev,
billingDataMod: {
...prev.billingDataMod,
endDate:
prev.billingDataMod
.endDate ===
"0000-00-00T23:59:59+08:00"
? ""
: "0000-00-00T23:59:59+08:00",
},
}))
}
>
{publicNoteObj.billingDataMod.endDate ===
"0000-00-00T23:59:59+08:00"
? t("PublicNote.CancelNoExpiry")
: t("PublicNote.SetNoExpiry")}
</Button>
</div>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left font-normal"
>
{publicNoteObj.billingDataMod.endDate
? publicNoteObj.billingDataMod
.endDate ===
"0000-00-00T23:59:59+08:00"
? t("PublicNote.NoExpiry")
: new Date(
publicNoteObj.billingDataMod.endDate,
).toLocaleDateString()
: "YYYY-MM-DD"}
</Button>
</PopoverTrigger>
<PopoverContent
className="p-0 w-[300px] max-h-[60dvh] overflow-hidden"
align="start"
>
<div className="max-h-[500px] overflow-y-auto">
<Calendar
className="w-full min-h-[320px]"
mode="single"
captionLayout="dropdown"
startMonth={new Date(2000, 0)}
endMonth={new Date(2050, 11)}
selected={
publicNoteObj.billingDataMod
.endDate &&
publicNoteObj.billingDataMod
.endDate !==
"0000-00-00T23:59:59+08:00"
? new Date(
publicNoteObj.billingDataMod.endDate,
)
: undefined
}
onSelect={(d) => {
if (!d) return
setPublicNoteObj((prev) => {
const prevDateStr =
prev.billingDataMod
.endDate
if (prevDateStr) {
const pd = new Date(
prevDateStr,
)
// 仅在有效日期时复制时分秒(特殊“不过期”值不会影响)
if (
!isNaN(pd.getTime())
) {
d.setHours(
pd.getHours(),
pd.getMinutes(),
pd.getSeconds(),
0,
)
}
}
return {
...prev,
billingDataMod: {
...prev.billingDataMod,
endDate:
d.toISOString(),
},
}
})
}}
autoFocus
/>
</div>
</PopoverContent>
</Popover>
{publicNoteErrors["billing.endDate"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["billing.endDate"]}
</p>
)}
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.AutoRenewal")}
</Label>
<Select
onValueChange={(val) =>
setPublicNoteObj((prev) => ({
...prev,
billingDataMod: {
...prev.billingDataMod,
autoRenewal: val,
},
}))
}
defaultValue={
publicNoteObj.billingDataMod.autoRenewal ||
"0"
}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">
{t("PublicNote.Enabled")}
</SelectItem>
<SelectItem value="0">
{t("PublicNote.Disabled")}
</SelectItem>
</SelectContent>
</Select>
{publicNoteErrors["billing.autoRenewal"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["billing.autoRenewal"]}
</p>
)}
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.Cycle")}
</Label>
<Select
onValueChange={(val) =>
setPublicNoteObj((prev) => ({
...prev,
billingDataMod: {
...prev.billingDataMod,
cycle: val,
},
}))
}
defaultValue={
publicNoteObj.billingDataMod.cycle ||
"Month"
}
>
<SelectTrigger>
<SelectValue placeholder="Select cycle" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Day">
{t("PublicNote.Day")}
</SelectItem>
<SelectItem value="Week">
{t("PublicNote.Week")}
</SelectItem>
<SelectItem value="Month">
{t("PublicNote.Month")}
</SelectItem>
<SelectItem value="Year">
{t("PublicNote.Year")}
</SelectItem>
</SelectContent>
</Select>
{publicNoteErrors["billing.cycle"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["billing.cycle"]}
</p>
)}
</div>
<div className="space-y-1 sm:col-span-2">
<div className="flex items-center gap-2">
<Label className="text-xs">
{t("PublicNote.Amount")}
</Label>
<Button
type="button"
variant="outline"
className="text-xs px-2 py-0 h-auto bg-gray-200 dark:bg-gray-700"
onClick={() =>
setPublicNoteObj((prev) => ({
...prev,
billingDataMod: {
...prev.billingDataMod,
amount: "0",
},
}))
}
>
{t("PublicNote.Free")}
</Button>
<Button
type="button"
variant="outline"
className="text-xs px-2 py-0 h-auto bg-gray-200 dark:bg-gray-700"
onClick={() =>
setPublicNoteObj((prev) => ({
...prev,
billingDataMod: {
...prev.billingDataMod,
amount: "-1",
},
}))
}
>
{t("PublicNote.PayAsYouGo")}
</Button>
</div>
<Input
placeholder="200EUR"
value={publicNoteObj.billingDataMod.amount}
onChange={(e) =>
setPublicNoteObj((prev) => ({
...prev,
billingDataMod: {
...prev.billingDataMod,
amount: e.target.value,
},
}))
}
/>
</div>
</div>
</div>
<div className="rounded-md border p-3 space-y-3">
<div className="text-sm font-medium opacity-80">
{t("PublicNote.Plan")}
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.Bandwidth")}
</Label>
<Input
placeholder="30Mbps"
value={publicNoteObj.planDataMod.bandwidth}
onChange={(e) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
bandwidth: e.target.value,
},
}))
}
/>
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.TrafficVolume")}
</Label>
<Input
placeholder="1TB/Month"
value={publicNoteObj.planDataMod.trafficVol}
onChange={(e) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
trafficVol: e.target.value,
},
}))
}
/>
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.TrafficType")}
</Label>
<Select
onValueChange={(val) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
trafficType: val,
},
}))
}
defaultValue={
publicNoteObj.planDataMod.trafficType || "2"
}
>
<SelectTrigger>
<SelectValue placeholder="Select type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">
{t("PublicNote.Inbound")}
</SelectItem>
<SelectItem value="2">
{t("PublicNote.Both")}
</SelectItem>
</SelectContent>
</Select>
{publicNoteErrors["plan.trafficType"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["plan.trafficType"]}
</p>
)}
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.IPv4")}
</Label>
<div className="flex items-center gap-2 mt-2">
<span className="text-xs">
{t("PublicNote.None")}
</span>
<Switch
checked={
publicNoteObj.planDataMod.IPv4 === "1"
}
onCheckedChange={(checked) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
IPv4: checked ? "1" : "0",
},
}))
}
/>
<span className="text-xs">
{t("PublicNote.Has")}
</span>
</div>
{publicNoteErrors["plan.IPv4"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["plan.IPv4"]}
</p>
)}
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.IPv6")}
</Label>
<div className="flex items-center gap-2 mt-2">
<span className="text-xs">
{t("PublicNote.None")}
</span>
<Switch
checked={
publicNoteObj.planDataMod.IPv6 === "1"
}
onCheckedChange={(checked) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
IPv6: checked ? "1" : "0",
},
}))
}
/>
<span className="text-xs">
{t("PublicNote.Has")}
</span>
</div>
{publicNoteErrors["plan.IPv6"] && (
<p className="text-xs text-destructive mt-1">
{publicNoteErrors["plan.IPv6"]}
</p>
)}
</div>
<div className="space-y-1">
<Label className="text-xs">
{t("PublicNote.NetworkRoute")}
</Label>
<Input
placeholder={t("PublicNote.CommaSeparated")}
value={publicNoteObj.planDataMod.networkRoute}
onChange={(e) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
networkRoute: e.target.value,
},
}))
}
/>
</div>
<div className="space-y-1 sm:col-span-2">
<Label className="text-xs">
{t("PublicNote.Extra")}
</Label>
<Input
placeholder={t("PublicNote.CommaSeparated")}
value={publicNoteObj.planDataMod.extra}
onChange={(e) =>
setPublicNoteObj((prev) => ({
...prev,
planDataMod: {
...prev.planDataMod,
extra: e.target.value,
},
}))
}
/>
</div>
</div>
</div>
</div>
<DialogFooter className="justify-end">
<DialogClose asChild>
<Button type="button" className="my-2" variant="secondary">