feat: 사이트 등록/수정 페이지 form 작업

This commit is contained in:
2025-03-13 17:42:22 +09:00
parent 64a615258d
commit f541687c00
10 changed files with 616 additions and 227 deletions
+6 -1
View File
@@ -11,6 +11,9 @@
"dependencies": {
"@common/assets": "workspace:*",
"@common/components": "workspace:*",
"@hookform/resolvers": "^4.1.3",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^5.67.1",
"@tanstack/react-table": "^8.21.2",
"axios": "^1.8.1",
@@ -19,7 +22,9 @@
"js-cookie": "^3.0.5",
"next": "15.1.7",
"react-chartjs-2": "^5.3.0",
"tailwindcss-animate": "^1.0.7"
"react-hook-form": "^7.54.2",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
+12 -3
View File
@@ -1,17 +1,18 @@
import { client } from "@/shared/api/client";
import { ENDPOINT } from "@/shared/config/api";
import type {
ISiteQuery,
ISiteInfo,
SiteService,
ISiteDetail,
ISiteParams,
ISiteData,
} from "../model/site-interface";
export class SiteApiService implements SiteService {
public async siteInfo(query: ISiteQuery): Promise<ISiteInfo> {
public async siteInfo(params: ISiteParams): Promise<ISiteInfo> {
const response = await client.get<ISiteInfo>(
`${ENDPOINT.CMS_SERVICE.SITES}`,
{ params: query },
{ params },
);
return response.data;
}
@@ -22,6 +23,14 @@ export class SiteApiService implements SiteService {
);
return response.data;
}
public async modifySite(siteId: string, data: ISiteData): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`,
data,
);
return response.data;
}
}
export const SiteApi = new SiteApiService();
+1
View File
@@ -1 +1,2 @@
export { SiteApi } from "./api/site-service";
export * from "./model/site-interface";
@@ -1,4 +1,4 @@
export interface ISiteQuery {
export interface ISiteParams {
siteNm: string;
siteKndCd: string;
useYn: string;
@@ -47,20 +47,25 @@ export interface ISiteInfo {
empty: boolean;
}
export interface ISiteDetail {
siteId: string;
export interface ISiteData {
siteNm: string;
siteExpln: string;
siteKndCd: string;
siteKindNm: string;
siteAddr: string;
faxNumber: string;
faxNo: string;
telNo: string;
lwndCn: string;
basicSiteYn: string;
bscSiteYn: string;
bkmkIcon: string;
useYn: string;
siteSkn: string;
}
export interface ISiteDetail extends ISiteData {
siteId: string;
siteKindNm: string;
basicSiteYn: string;
faxNumber: string;
regDt: string;
rgTr: string;
mdfcnDt: string;
@@ -70,6 +75,7 @@ export interface ISiteDetail {
}
export interface SiteService {
siteInfo(query: ISiteQuery): Promise<ISiteInfo>;
siteInfo(query: ISiteParams): Promise<ISiteInfo>;
siteDetail(siteId: string): Promise<ISiteDetail>;
modifySite(siteId: string, data: ISiteData): Promise<void>;
}
@@ -0,0 +1,17 @@
import type { ISiteData } from "@/entities/site";
const siteFormSchema: ISiteData = {
siteNm: "",
siteExpln: "",
siteKndCd: "",
siteAddr: "",
faxNo: "",
telNo: "",
lwndCn: "",
bscSiteYn: "",
bkmkIcon: "",
useYn: "",
siteSkn: "",
};
export { siteFormSchema };
+350 -211
View File
@@ -2,8 +2,8 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { SiteApi } from "@/entities/site";
import { ENDPOINT } from "@/shared/config/api";
import { useForm } from "react-hook-form";
import { useQuery } from "@tanstack/react-query";
import {
Button,
Input,
@@ -16,29 +16,49 @@ import {
TableHead,
TableRow,
} from "@common/components";
import { useQuery } from "@tanstack/react-query";
import { SiteApi, type ISiteDetail } from "@/entities/site";
import { CodeSelect } from "@/entities/code";
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
import { ENDPOINT } from "@/shared/config/api";
import { SitePageLayout } from "./layout";
import { IPManagementDialog } from "./ip-management-dialog";
import {
DomainManagementDialog,
type Domain,
} from "./domain-management-dialog";
import type { ISiteDetail } from "@/entities/site/model/site-interface";
import { siteFormSchema } from "../model/form-schema";
type CreateFormData = typeof siteFormSchema & {
siteId: string;
};
type ModifyFormData = typeof siteFormSchema;
type FormData = CreateFormData | ModifyFormData;
type ViewState = Partial<ISiteDetail>;
function RegisterPage({ siteId }: { siteId: string }) {
const isModifyMode = !!siteId;
const router = useRouter();
const { data: siteDetail } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId],
queryFn: () => SiteApi.siteDetail(siteId),
select: (data) => {
const newData = {
...data,
faxNo: data.faxNumber,
bscSiteYn: data.basicSiteYn,
};
handleViewStateChange("siteKndCd")(data.siteKndCd);
handleViewStateChange("siteSkn")(data.siteSkn);
return data;
return newData;
},
enabled: !!siteId,
enabled: isModifyMode,
});
const form = useForm<FormData>({
defaultValues: isModifyMode
? siteFormSchema
: { ...siteFormSchema, siteId: "" },
});
const [viewState, setViewState] = useState<ViewState>({});
@@ -54,213 +74,332 @@ function RegisterPage({ siteId }: { siteId: string }) {
);
};
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<SitePageLayout>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">사이트 등록/수정</h3>
</div>
<div className="card">
<Table variant="secondary">
<colgroup>
<col width="20%" />
<col width="80%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead>사이트 코드</TableHead>
<TableCell className="border">
<Input
className="w-[12rem] !text-[1.3rem]"
defaultValue={siteDetail?.siteId}
disabled={!!siteId}
onChange={(e) =>
handleViewStateChange("siteId")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 구분</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<CodeSelect
upCd="SITEKNDCD"
defaultValue={siteDetail?.siteKndCd}
onValueChange={handleViewStateChange("siteKndCd")}
/>
{siteId && (
<IPManagementDialog
triggerDisabled={viewState.siteKndCd === "O"}
<Form {...form}>
<SitePageLayout>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
사이트 등록/수정
</h3>
</div>
<div className="card">
<Table variant="secondary">
<colgroup>
<col width="20%" />
<col width="80%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead>사이트 코드</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteId"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="w-[12rem] !text-[1.3rem]"
defaultValue={siteDetail?.siteId}
disabled={isModifyMode}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
)}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 디자인</TableHead>
<TableCell className="border">
<CodeSelect
upCd="SITESKINCD"
defaultValue={siteDetail?.siteSkn}
onValueChange={handleViewStateChange("siteSkn")}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트명</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="사이트명을 입력하십시오."
defaultValue={siteDetail?.siteNm}
onChange={(e) =>
handleViewStateChange("siteNm")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 설명</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
defaultValue={siteDetail?.siteExpln}
onChange={(e) =>
handleViewStateChange("siteExpln")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow className={`${!siteId ? "hidden" : ""}`}>
<TableHead>사이트 도메인</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<div className="bg-form h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input px-3 py-2 text-[1.3rem] opacity-50">
{representativeDomain}
</div>
<DomainManagementDialog handleSave={handleDomainSave} />
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 주소를 입력하십시오."
defaultValue={siteDetail?.siteAddr}
onChange={(e) =>
handleViewStateChange("siteAddr")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 전화번호</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 전화번호를 입력하십시오."
defaultValue={siteDetail?.telNo}
onChange={(e) =>
handleViewStateChange("telNo")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 팩스번호</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 팩스번호를 입력하십시오."
defaultValue={siteDetail?.faxNumber}
onChange={(e) =>
handleViewStateChange("faxNumber")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 내용</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
defaultValue={siteDetail?.lwndCn}
onChange={(e) =>
handleViewStateChange("lwndCn")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>북마크 아이콘</TableHead>
<TableCell className="border">
<Input
className="!text-[1.3rem]"
placeholder="아이콘 파일 경로 및 파일명을 입력하여 주십시오."
defaultValue={siteDetail?.bkmkIcon}
onChange={(e) =>
handleViewStateChange("bkmkIcon")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>기본 사이트 여부</TableHead>
<TableCell className="border">
<RadioGroup
name="basicSiteYn"
defaultValue={siteDetail?.basicSiteYn}
onValueChange={handleViewStateChange("basicSiteYn")}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="basicSiteY" />
<Label htmlFor="basicSiteY">예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="basicSiteN" />
<Label htmlFor="basicSiteN">아니오</Label>
</div>
</RadioGroup>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사용 여부</TableHead>
<TableCell className="border">
<RadioGroup
name="useYn"
defaultValue={siteDetail?.useYn}
onValueChange={handleViewStateChange("useYn")}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="useY" />
<Label htmlFor="useY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="useN" />
<Label htmlFor="useN">사용안함</Label>
</div>
</RadioGroup>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg">저장</Button>
<Button size="lg" color="white" onClick={() => router.back()}>
취소
</Button>
<Button size="lg" color="red">
삭제
</Button>
</div>
</SitePageLayout>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 구분</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<FormField
control={form.control}
name="siteKndCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
upCd="SITEKNDCD"
defaultValue={siteDetail?.siteKndCd}
onValueChange={(value) => {
handleViewStateChange("siteKndCd")(value);
field.onChange(value);
}}
/>
</FormControl>
</FormItem>
)}
/>
{isModifyMode && (
<IPManagementDialog
triggerDisabled={viewState.siteKndCd === "O"}
/>
)}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 디자인</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteSkn"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
upCd="SITESKINCD"
defaultValue={siteDetail?.siteSkn}
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트명을 입력하십시오."
defaultValue={siteDetail?.siteNm}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
defaultValue={siteDetail?.siteExpln}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow className={`${!isModifyMode ? "hidden" : ""}`}>
<TableHead>사이트 도메인</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<div className="h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{representativeDomain}
</div>
<DomainManagementDialog handleSave={handleDomainSave} />
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteAddr"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 주소를 입력하십시오."
defaultValue={siteDetail?.siteAddr}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 전화번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="telNo"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 전화번호를 입력하십시오."
defaultValue={siteDetail?.telNo}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 팩스번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="faxNo"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 팩스번호를 입력하십시오."
defaultValue={siteDetail?.faxNo}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 내용</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lwndCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
defaultValue={siteDetail?.lwndCn}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>북마크 아이콘</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bkmkIcon"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="아이콘 파일 경로 및 파일명을 입력하여 주십시오."
defaultValue={siteDetail?.bkmkIcon}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>기본 사이트 여부</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bscSiteYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="bscSiteYn"
defaultValue={siteDetail?.bscSiteYn}
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="bscSiteY" />
<Label htmlFor="bscSiteY">예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="bscSiteN" />
<Label htmlFor="bscSiteN">아니오</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사용 여부</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="useYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="useYn"
defaultValue={siteDetail?.useYn}
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="useY" />
<Label htmlFor="useY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="useN" />
<Label htmlFor="useN">사용안함</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg" type="submit">
저장
</Button>
<Button size="lg" color="white" onClick={() => router.back()}>
취소
</Button>
<Button size="lg" color="red">
삭제
</Button>
</div>
</form>
</SitePageLayout>
</Form>
);
}
+2 -3
View File
@@ -7,8 +7,7 @@ import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { Button, Input, DataTable } from "@common/components";
import { SiteApi } from "@/entities/site/api/site-service";
import type { ISite, ISiteQuery } from "@/entities/site/model/site-interface";
import { SiteApi, type ISite, type ISiteParams } from "@/entities/site";
import { ENDPOINT } from "@/shared/config/api";
import { columns } from "../model/table-columns";
import { SitePageLayout } from "./layout";
@@ -21,7 +20,7 @@ function SitePage() {
enabled: false,
});
const [siteQuery, setSiteQuery] = useState<ISiteQuery>({
const [siteQuery, setSiteQuery] = useState<ISiteParams>({
siteNm: "",
siteKndCd: "",
useYn: "",
+177
View File
@@ -0,0 +1,177 @@
"use client";
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
FormProvider,
useFormContext,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form";
import { cn, Label } from "@common/components";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? "") : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
"text-[1.3rem] font-medium leading-none text-label peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef<
+37 -1
View File
@@ -42,6 +42,15 @@ importers:
'@common/components':
specifier: workspace:*
version: link:../../packages/common/components
'@hookform/resolvers':
specifier: ^4.1.3
version: 4.1.3(react-hook-form@7.54.2)
'@radix-ui/react-label':
specifier: ^2.1.2
version: 2.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-slot':
specifier: ^1.1.2
version: 1.1.2(@types/react@18.3.18)(react@18.3.1)
'@tanstack/react-query':
specifier: ^5.67.1
version: 5.67.1(react@18.3.1)
@@ -66,9 +75,15 @@ importers:
react-chartjs-2:
specifier: ^5.3.0
version: 5.3.0(chart.js@4.4.8)(react@18.3.1)
react-hook-form:
specifier: ^7.54.2
version: 7.54.2(react@18.3.1)
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.17)
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@eslint/eslintrc':
specifier: ^3
@@ -1122,6 +1137,15 @@ packages:
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
dev: false
/@hookform/resolvers@4.1.3(react-hook-form@7.54.2):
resolution: {integrity: sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ==}
peerDependencies:
react-hook-form: ^7.0.0
dependencies:
'@standard-schema/utils': 0.3.0
react-hook-form: 7.54.2(react@18.3.1)
dev: false
/@humanfs/core@0.19.1:
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -2530,6 +2554,10 @@ packages:
engines: {node: '>=18'}
dev: true
/@standard-schema/utils@0.3.0:
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
dev: false
/@storybook/addon-actions@8.6.0(storybook@8.6.0):
resolution: {integrity: sha512-lI03stcI28wN8trjp7zx0PzN1NRxSpU/551slzEhSM0tghcLxcfLZR/wh4Jp0T68Obj7z//YXPttZ9PBFajs6w==}
peerDependencies:
@@ -6469,6 +6497,15 @@ packages:
react: 18.3.1
scheduler: 0.23.2
/react-hook-form@7.54.2(react@18.3.1):
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^18.3.1
dependencies:
react: 18.3.1
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -7894,4 +7931,3 @@ packages:
/zod@3.24.2:
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
dev: true