iipbs_cs #9

Merged
chanii8 merged 141 commits from iipbs_cs into main 2025-03-17 18:51:48 +09:00
92 changed files with 8417 additions and 15 deletions
+9 -2
View File
@@ -1,8 +1,12 @@
# turbo
.turbo
.turbo/
# dependencies
node_modules
node_modules/
# build
.next/
dist/
# build
.next
@@ -10,3 +14,6 @@ dist
# misc
.DS_Store
# env
.env*
+3 -3
View File
@@ -6,11 +6,11 @@ import { useRouter } from "next/navigation";
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 { ENDPOINT } from "@/shared/config";
import { Button, DataTable, Input } from "@common/components";
import { ENDPOINT } from "@/shared/config/api";
import { SiteApi } from "../api/site-service";
import { type ISite, type ISiteParams } from "../model/site-interface";
import { columns } from "../model/table-columns";
import { type ISite, type ISiteParams } from "../model/site-interface";
import { SitePageLayout } from "./layout";
function SitePage() {
+41
View File
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+70
View File
@@ -0,0 +1,70 @@
@import "../../../packages/common/components/src/base.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+21
View File
@@ -0,0 +1,21 @@
import { BaseLayout } from "@common/components";
import "./globals.css";
import { StoreProvider } from "./store";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<StoreProvider>
<BaseLayout>
{children}
</BaseLayout>
</StoreProvider>
</body>
</html>
);
}
+84
View File
@@ -0,0 +1,84 @@
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Form,
FormField,
FormUi,
ScrollArea,
SelectBox,
} from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { CoRegForm } from "@/components/ui/matDmd/coRegForm/component";
import toast, { Toaster } from "react-hot-toast";
import { ToastFormUi } from "@/components/toastFromUi";
const formSchema = z.object({
ctpv: z.string(),
sgg: z.string(),
coNm: z.string().min(1).max(20),
bznmNo: z.string().min(1).min(9).max(12),
addr: z.string().min(1).min(6).max(20),
telNo: z.string().min(8).max(12),
});
export default function MyForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
coNm:'',
bznmNo:'',
addr:'',
telNo:'',
},
});
function onSubmit(values: z.infer<typeof formSchema>) {
try {
toast.dismiss();
ToastFormUi("수요신청 화면으로 이동하시겠습니까?", "수요처 등록 완료", "./dmdReg", "이동")
} catch (error) {
console.error("Form submission error", error);
toast.error("수요처 등록이 실패하였습니다.");
}
}
return (
<>
<div className="grid h-40 grid-cols-3 place-content-center">
<h1 className=" col-start-2 col-span-1 text-4xl underline">
수요처 정보입력
</h1>
</div>
<div className="grid grid-cols-5 gap-4 overflow-hidden">
<div className="col-span-1 "/>
<div className="col-span-3 ">
<div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 max-w-3xl mx-auto"
>
<CoRegForm>{form}</CoRegForm>
<Button type="submit">수요처등록</Button>
</form>
</Form>
</div>
</div>
<div className="col-span-1 " >
<Toaster position="top-center" />
</div>
</div>
</>
);
}
+106
View File
@@ -0,0 +1,106 @@
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Form } from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { DmdRegForm } from "@/components/ui/matDmd/dmdRegform/component";
import toast, { Toaster } from "react-hot-toast";
import { useRouter } from "next/navigation";
const formSchema = z.object({
dmdSel: z.string().min(1),
dmdTmng: z.coerce.date(),
dmdWq: z.string().min(1).optional(),
useUsgCd: z.string(),
wntUntprc: z.string().min(1),
grssClfg: z.string().min(1),
tn: z.string().min(1).optional(),
combResdch: z.string().min(1).optional(),
tp: z.string().min(1).optional(),
trbt: z.string().min(1).optional(),
ph: z.string().min(1).optional(),
bod: z.string().min(1).optional(),
clIonQnt: z.string().min(1).optional(),
smll: z.string().min(1).optional(),
cdtnv: z.string().min(1).optional(),
crmty: z.string().min(1).optional(),
rmrk: z.string().min(1).optional(),
sply1: z.string().min(1).optional(),
sply2: z.string().min(1).optional(),
sply3: z.string().min(1).optional(),
password: z.string().min(1),
});
export default function MyForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
dmdSel:'',
dmdTmng: new Date(),
dmdWq: "",
useUsgCd: "",
wntUntprc: "",
grssClfg: "",
tn: "",
combResdch: "",
tp: "",
trbt: "",
ph: "",
bod: "",
clIonQnt: "",
smll: "",
cdtnv: "",
crmty: "",
rmrk: "",
sply1: "",
sply2: "",
sply3: "",
password: "",
},
});
const router = useRouter();
async function onSubmit(values: z.infer<typeof formSchema>) {
try {
toast.success("✅ 수요신청이 완료되었습니다.");
await new Promise((resolve) => setTimeout(resolve, 3000));
router.push("../../")
} catch (error) {
toast.error("수요신청을 다시입력해주시길 바랍니다.");
}
}
return (
<>
<div className="grid h-40 grid-cols-3 place-content-center">
<h1 className=" col-start-2 col-span-1 text-4xl underline">
수요 신청정보입력
</h1>
</div>
<div className="grid grid-cols-5 gap-4 overflow-hidden">
<div className="col-span-1 " />
<div className="col-span-3 max-h-[78vh] overflow-y-scroll ">
<div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 max-w-3xl mx-auto"
>
<DmdRegForm> {form}</DmdRegForm>
<Button type="submit"> 수요신청</Button>
</form>
</Form>
</div>
</div>
<div className="col-span-1 ">
<Toaster position="top-center" />
</div>
</div>
</>
);
}
+26
View File
@@ -0,0 +1,26 @@
import Link from 'next/link';
import { Button } from '@common/components/ui';
export default function Home() {
return (
<>
<div className='flex'>
<Button className='place-self-auto'>
<Link href="reuseFaciltyManage/reuseFacilityInsert/publicReuseFaciltyInsert"> 하수재이용 시설 등록</Link>
</Button>
<Button className='place-self-auto'>
<Link href="reuseFaciltyManage/reuseFacilityInsert/rainReuseFaciltyInsert"> 빗물재이용 시설 등록</Link>
</Button>
<Button className='place-self-auto'>
<Link href="reuseFaciltyManage/reuseFacilityInsert/rswtReuseFacilityInsert"> 중수도시설 등록</Link>
</Button>
<Button className='place-self-auto'>
<Link href="matMng/matDmd/coReg">수요처 등록</Link>
</Button>
</div>
</>
);
}
@@ -0,0 +1,52 @@
'use client'
import { ScrollArea, SearchDiv, SelectBox, UiTable } from '@common/business_components';
import { Button } from '@common/components';
import { PublicReuseFaciltyFormInsert } from '../../../../components/ui/publicReuseInsertForm';
import { useWrStore } from '../../../store';
export default function PublicReuseFaciltyInsert() {
const {pubFac} = useWrStore((state) => state);
const { pubFacHeadList, publicReuseFacilityData} = pubFac;
const searchDiv = '하수재이용시설 '
const selectBox = [{ text: '서울하수처리시설', val: "1_hasu" }, { text: '서울1하수처리시설', val: "2_hasu" }, { text: '서울2하수처리시설', val: "3_hasu" }]
const headName = () => {
return (
<h1 className="pl-3 text-xl">○공공 하수처리시설 등록</h1>
)
}
const onSelectValue = (val: string) => {
console.log(val)
}
return (
<>
<div className="grid grid-cols-2 gap-4 h-full">
<div className='col-span-4'>
<h1 className="pl-3 text-xl" > 하수재이용시설 등록</h1>
<div className="m-8">
<SearchDiv>
<div className='w-4/5 grid grid-cols-12 gap-1 mx-4'>
<SelectBox className='col-span-4' label={searchDiv} selectArray={selectBox} onSelectValue={onSelectValue} />
</div>
<div className='w-1/5 flex items-center justify-end'>
<Button className='mr-4 rounded-lg'>조회</Button>
<Button className='mr-4 rounded-lg'>신규추가</Button>
</div>
</SearchDiv>
</div>
</div>
<div className="col-span-1 pl-11">
<UiTable tableData={publicReuseFacilityData} headlist={pubFacHeadList} pageSize={100} total={10} headName={headName()} children={undefined}/>
</div>
<div className="col-span-1 pr-6 max-h-full overflow-y-scroll">
<ScrollArea>
<PublicReuseFaciltyFormInsert />
{/* <ScrollBar orientation="vertical"></ScrollBar> */}
</ScrollArea>
</div>
</div>
</>
)
}
@@ -0,0 +1,67 @@
'use client'
import { useWrStore } from "@/app/store";
import type { TableUpperProps } from "@/app/store/rainReuseFacilityInsert"
import { RainReuseFaciltyInsertForm } from "@/components/ui/rainReuseInsertForm";
import { ScrollArea, SearchDiv, SearchInput, UiTable } from "@common/business_components";
import { Button, TableHead, TableRow } from "@common/components";
export const headMakeColSpan = (upHeadList: TableUpperProps[]) => {
return (<>
<TableRow>{
upHeadList.map((head: TableUpperProps) =>
(!head.upSequnce ? <TableHead rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead key={head.id} rowSpan={1} colSpan={3}>{'중축 개축 증축 '}</TableHead> : null))}
</TableRow>
<TableRow>
{upHeadList.map((head: TableUpperProps) =>
(head.upName === 'upChangeRe' ? <TableHead key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
</>)
}
export default function Home() {
const { rain } = useWrStore((state) => state);
const nodeList = headMakeColSpan(rain.upHeadList);
const headName = () => {
return (
<h1 className="pl-3 text-xl">○건축물 목록</h1>
)
}
return (
<>
<div className="grid grid-cols-2 gap-4 ">
<div className='col-span-2'>
<h1 className="pl-3 text-2xl" >빗물이용시설 등록 </h1>
<div className="m-8">
<SearchDiv >
<div className='w-4/5 grid grid-cols-12 gap-1 mx-4'>
<SearchInput className='col-span-3 my-7' label="건물명" />
</div>
<div className='w-1/5 flex items-center justify-end'>
<Button className='mr-4 rounded-lg'>조회</Button>
<Button className='mr-4 rounded-lg'>저장</Button>
<Button className='mr-4 rounded-lg'>신규등록</Button>
</div>
</SearchDiv>
</div>
</div>
<div className="colsapn-1 pl-11">
<UiTable tableData={rain.rainOfObject} headlist={rain.upHeadList} pageSize={100} total={10} headName={headName()}>
{nodeList}
</UiTable>
</div>
<div className="colsapn-1 pr-11 max-h-full ovreflow-y-scroll">
<ScrollArea>
<RainReuseFaciltyInsertForm></RainReuseFaciltyInsertForm>
</ScrollArea>
</div>
</div>
</>
);
}
@@ -0,0 +1,69 @@
'use client'
import { useWrStore } from "@/app/store";
import type { TableUpperProps } from "@/app/store/rainReuseFacilityInsert";
import { RswtrReuseFaciltyInsertForm } from "@/components/ui/rswtrReuseInsetForm";
import { ScrollArea, SearchDiv, SearchInput, UiTable } from "@common/business_components";
import { Button, TableHead, TableRow } from "@common/components";
const headMakeColSpan = (upHeadList: TableUpperProps[]) => {
return (<>
<TableRow>{
upHeadList.map((head: TableUpperProps) =>
(!head.upSequnce ? <TableHead rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead key={head.id} rowSpan={1} colSpan={3}>{'중축 개축 증축 '}</TableHead> : null))}
</TableRow>
<TableRow>
{upHeadList.map((head: TableUpperProps) =>
(head.upName === 'upChangeRe' ? <TableHead key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
</>)
}
export default function Home() {
const {rswt} = useWrStore((state) => state);
const nodeList = headMakeColSpan(rswt.upHeadList);
const total = 100;
const headName = () => {
return (
<h1 className="pl-3 text-xl">○건축물 목록</h1>
)
}
return (
<>
<div className="grid grid-cols-2 gap-4 ">
<div className='col-span-2'>
<h1 className="pl-3 text-2xl" >중수도시설 등록 </h1>
<div className="m-8">
<SearchDiv >
<div className='w-4/5 grid grid-cols-6 gap-1 mx-4'>
<SearchInput className='col-span-1 my-7 text-2xl' label="건물명:" />
</div>
<div className='w-1/5 flex items-center justify-end'>
<Button className='mr-4 rounded-lg'>조회</Button>
<Button className='mr-4 rounded-lg'>저장</Button>
<Button className='mr-4 rounded-lg'>신규등록</Button>
</div>
</SearchDiv>
</div>
</div>
<div className="colsapn-1 pl-11">
<UiTable tableData={rswt.rswtOfObject} headlist={rswt.upHeadList} pageSize={100} total={10} headName={headName()}>
{nodeList}
</UiTable>
</div>
<div className="colsapn-1 pr-11 max-h-full ovreflow-y-scroll">
<ScrollArea>
<RswtrReuseFaciltyInsertForm></RswtrReuseFaciltyInsertForm>
</ScrollArea>
</div>
</div>
</>
);
}
+41
View File
@@ -0,0 +1,41 @@
'use client'
import { type ReactNode, createContext, useContext, useRef } from 'react'
import { useStore } from 'zustand'
import { type WrStore, StoreZus } from './store'
export type StoreApi = ReturnType<typeof StoreZus>
export const StoreContext = createContext<StoreApi | undefined>(
undefined,
)
export interface StoreProviderProps {
children: ReactNode
}
export const StoreProvider = ({
children,
}: StoreProviderProps) => {
const storeRef = useRef<StoreApi>(null)
if (!storeRef.current) {
storeRef.current = StoreZus()
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
)
}
export const useWrStore = <T,>(
selector: (store: WrStore) => T,
): T => {
const storeContext = useContext(StoreContext)
if (!storeContext) {
throw new Error(`useStore must be used within StoreProvider`)
}
return useStore(storeContext, selector)
}
+55
View File
@@ -0,0 +1,55 @@
import type { selectArray } from "../rainReuseFacilityInsert";
const selectSido = [
{ text: '서울', val: "1" },
{ text: "인천", val: "2" },
{ text: "부산", val: "3" },
];
const selectSgg = [
{ text: '종로구', val: "1" },
{ text: "성북구", val: "2" },
{ text: "강남구", val: "3" },
];
const selectUsg = [
{ text: '민간', val: "1" },
{ text: "공공", val: "2" },
];
const selectYN= [
{ text: 'Y', val: "1" },
{ text: 'N', val: "2" },
];
export type matDmdState ={ dmd:{
isInit: boolean;
selectSido:selectArray[];
selectSgg:selectArray[];
selectUsg:selectArray[];
selectYN:selectArray[];
}}
type matDmdActions = {
decrementList: () => void
}
export type matDmdType = matDmdState & matDmdActions;
export const matDmdStateExport:matDmdState= {dmd:{
isInit:false,
selectSido:selectSido,
selectSgg: selectSgg,
selectUsg: selectUsg,
selectYN:selectYN,
}}
export const matDmdActionsExport:(set:any)=>matDmdActions=(set: any) => {
return {
decrementList:()=>set(
(state:matDmdState) => {
return (
{rain:{
}})}
)
}
}
@@ -0,0 +1,178 @@
import type { TableUpperProps } from "../rainReuseFacilityInsert";
export type TableProps = {
id: string;
title: string;
}
export type selectLabel={
text:string,
val:string
}
type publicReuseFacilityData = {
sido: string,
sigungo: string,
facilityName: string,
location: string,
locationgubun: string,
facilityCapacity: string,
yn?: string,
authoDay?: string,
area?: string,
reuse?: string,
register?: string
}
type pubMngFacData = {
mng: string,
subCo: string,
startDay: string,
endDay: string,
}
const selectLabel:selectLabel[]=[
{text:'test',val:'1'}
]
const pubFacHeadList:TableProps[] = [
{ id: "sido", title: "시도"},
{ id: "sigungo", title: "시군구"},
{ id: "facilityName", title: "시설명"},
{ id: "location", title: "위치"},
{ id: "locationgubun", title: "지역구분"},
{ id: "facilityCapacity", title: "시설용량"},
{ id: "yn", title: '재이용여부' },
{ id: "authoDay", title: '등록여부'}];
const pubMngUpHeadList:TableUpperProps[] = [
{ id: "mng", title: "운영방식"},
{ id: "subCo", title: "대행업체"},
{ id: "startDay", title: "시작일", upName:'upChangeRe' , upSequnce:1 },
{ id: "endDay", title: "종료일", upName:'upChangeRe' , upSequnce:2 },
];
const date = new Date()
const pubMngFacData:pubMngFacData[] = [
{ mng:"자체운영", subCo:"-",startDay: '2024-11-11' ,endDay:'2024-11-20'},
{ mng:"자체운영", subCo:"한국수자원공사",startDay: '2022-11-11' ,endDay:'2024-09-13'},
{ mng:"자체운영", subCo:"스타벅스",startDay: '2023-11-11' ,endDay:'2024-02-19'},
];
const publicReuseFacilityData:publicReuseFacilityData[] = [
{
sido: "서울",
sigungo: "강남구",
facilityName: "난지",
location: "경기도 고양시 덕양구 대차로 4가",
locationgubun: "500",
facilityCapacity: "800,000",
reuse: "Y",
register: "Y"
},
{
sido: "서울",
sigungo: "강남구",
facilityName: "난지",
location: "경기도 고양시 덕양구 대차로 4가",
locationgubun: "500",
facilityCapacity: "800,000",
reuse: "Y",
register: "Y"
},
{
sido: "서울",
sigungo: "강남구",
facilityName: "난지",
location: "경기도 고양시 덕양구 대차로 4가",
locationgubun: "500",
facilityCapacity: "800,000",
yn: "Y",
authoDay: "2012",
area:"123"
},
]
const erctUswtrCd:selectLabel[]=[
{text:'처리장연계',val:'1_erct'},
{text:'자체/연계',val:'2_erct'},
{text:'자체처리',val:'3_erct'},
]
const cyclCd :selectLabel[]= [
{ text: "1차", val: "1_cyc" },
{ text: "2차", val: "2_cyc" },
];
const seCd :selectLabel[] = [
{ text: "신규", val: "1_Se" },
{ text: "진행", val: "2_Se" },
];
const buseCd :selectLabel[]= [
{ text: "재정", val: "1_bus" },
{ text: "만루", val: "2_bus" },
];
const rprcsCd :selectLabel[] = [
{ text: "물리적처리", val: "1_rprc" },
{ text: "생물학적처리", val: "2_rprc" },
];
const buseMet :selectLabel[] = [
{ text: "재정", val: "1_busemet" },
{ text: "민간투자", val: "2_busemet" },
{ text: "자체", val: "3_busemet" },
];
const rprcLoCd :selectLabel[] = [
{ text: "내부", val: "1_rprc" },
{ text: "외부", val: "2_rprc" },
];
export type pubFacState = {pubFac:{
isInit: boolean;
pubFacHeadList: TableProps[];
pubMngUpHeadList:TableUpperProps[]
publicReuseFacilityData: publicReuseFacilityData[];
selectLabel:selectLabel[];
pubMngFacData:pubMngFacData[];
erctUswtrCd:selectLabel[];
cyclCd:selectLabel[];
seCd:selectLabel[];
buseCd :selectLabel[]
rprcsCd :selectLabel[]
buseMet :selectLabel[]
rprcLoCd:selectLabel[]
}}
type pubFacActions = {
decrementList: () => void
}
export type pubFacType = pubFacState & pubFacActions;
export const pubFacStateExport:pubFacState= {pubFac:{
isInit:false,
pubFacHeadList: pubFacHeadList,
pubMngUpHeadList:pubMngUpHeadList,
publicReuseFacilityData: publicReuseFacilityData,
selectLabel:selectLabel,
pubMngFacData:pubMngFacData,
erctUswtrCd:erctUswtrCd,
cyclCd:cyclCd,
seCd:seCd,
buseCd:buseCd,
rprcsCd:rprcsCd,
buseMet:buseMet,
rprcLoCd:rprcLoCd
}}
export const pubFacActionsExport:(set:any)=>pubFacActions=(set: any) => {
return {
decrementList:()=>set(
(state:pubFacState) => {
return (
{pubFac:{
pubFacheadList: state.pubFac.pubFacHeadList,
publicReuseFacilityUper:
state.pubFac.publicReuseFacilityData.slice(0, state.pubFac.publicReuseFacilityData.length - 1)
}})}
)
}
}
@@ -0,0 +1,134 @@
export type TableUpperProps = {
id: string;
title: string;
upName?: string;
upSequnce?: number;
}
export type rainFacOfType = {
sido: string,
sigungo: string,
facilityName: string,
location: string,
locationgubun: string,
firstConstructionPermission:string,
facilityCapacity: string,
buildingArea:string,
yn?: string,
permissionDay:string;
authoDay?: string,
area?: string,
reuse?: string,
register?: string,
legalTargetYn:string,
}
export const upHeadList:TableUpperProps[] = [
{ id: "sido", title: "시도"},
{ id: "sigungo", title: "시군구"},
{ id: "facilityName", title: "시설명"},
{ id: "location", title: "위치"},
{ id: "locationgubun", title: "지역구분"},
{ id: "facilityCapacity", title: "시설용량"},
{ id: "yn", title: '여부', upName:'upChangeRe' , upSequnce:1 },
{ id: "authoDay", title: '허가일',upName:'upChangeRe', upSequnce:2 },
{ id: "area", title: '면적' ,upName:'upChangeRe',upSequnce:3 }];
const rainOfObject:rainFacOfType[] = [
{
sido: "서울",
sigungo: "종로구",
facilityName: "그랑서울",
location: "종로 33",
locationgubun: "업무시설",
firstConstructionPermission : '2024-10-10',
buildingArea: "50",
facilityCapacity: "800,000",
reuse: "Y",
permissionDay:'2024-10-10',
register: "Y",
legalTargetYn:"Y"
},
{
sido: "서울",
sigungo: "종로구",
facilityName: "그랑서울",
location: "종로 33",
locationgubun: "업무시설",
firstConstructionPermission : '2024-10-10',
buildingArea: "50",
facilityCapacity: "800,000",
reuse: "Y",
permissionDay:'2024-10-10',
register: "Y",
legalTargetYn:"Y"
},
{
sido: "서울",
sigungo: "종로구",
facilityName: "그랑서울",
location: "종로 33",
locationgubun: "업무시설",
firstConstructionPermission : '2024-10-10',
buildingArea: "50",
facilityCapacity: "800,000",
reuse: "Y",
permissionDay:'2024-10-10',
register: "Y",
legalTargetYn:"Y"
},
]
export type selectArray ={
text:string,
val:string
}
export const selectClass ="w-full h-full text-2xl";
const selectHomeWater = [
{ text: '집수면', val: "1" },
{ text: "종류", val: "2" },
];
const selectYN= [
{ text: 'Y', val: "1" },
{ text: 'N', val: "2" },
];
export type rainFacState ={ rain:{
isInit: boolean;
upHeadList: TableUpperProps[];
rainOfObject: rainFacOfType[];
selectHomeWater:selectArray[];
selectYN:selectArray[];
}}
type rainFacSActions = {
decrementList: () => void
}
export type rainFacType = rainFacState & rainFacSActions;
export const rainFacStateExport:rainFacState= {rain:{
isInit:false,
upHeadList: upHeadList,
rainOfObject: rainOfObject,
selectHomeWater:selectHomeWater,
selectYN:selectYN
}}
export const rainFacActionsExport:(set:any)=>rainFacSActions=(set: any) => {
return {
decrementList:()=>set(
(state:rainFacState) => {
return (
{rain:{
upHeadList: state.rain.upHeadList,
rainOfObject:
state.rain.rainOfObject.slice(0, state.rain.rainOfObject.length - 1)
}})}
)
}
}
+121
View File
@@ -0,0 +1,121 @@
# Zustand store
## Zustand 등록방법
**-- index.tsx --**
### context 등록
기본적으로 createContext를 통해 react에 context를 생성해서 react에서 사용할 수 있도록 해야함.
``` js
//StoreZus는 내부적으로 사용하기 위한 store파일에서 따로 생성함.
export type StoreApi = ReturnType<typeof StoreZus>
//react에서 사용하기 위한 context 생성.
export const StoreContext = createContext<StoreApi | undefined>(
undefined,
)
```
### component로 작성
등록된 context를 component화 시키기 위한 작업
```js
//외부에서 호출시 <StoreProvider> 형태로 호출.
//app이나 home에서 전체적인 사용이 되도록 작성해야함.
export const StoreProvider = ({
children,
}: StoreProviderProps) => {
const storeRef = useRef<StoreApi>(null)
if (!storeRef.current) {
//StoreZus는 store.tsx를 봐야함.
storeRef.current = StoreZus()
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
)
}
```
### store 등록
react에서 사용하는 store에 현재 context 등록
```js
export const useWrStore = <T,>(
selector: (store: WrStore) => T,
): T => {
const storeContext = useContext(StoreContext)
if (!storeContext) {
throw new Error(`useStore must be used within StoreProvider`)
}
return useStore(storeContext, selector)
}
```
## Zustand 사용방법
**-- store.tsx, 폴더별 index.tsx --**
### 기본 store 생성
index.tsx의 context에 사용되는 StoreZus를 추가하여 사용됨.
외부에서 생성된 state, actions를 사용 가능한 구조
```js
import { createStore } from 'zustand/vanilla';
import { tableActionsExport, tableStateExport, type tableState, type tableType } from './publicReuseFaciltyInsert/index';//외부 파일 위치.
export type WrStore = tableType;//typescript이므로 사용되는 스크립트를 type1 & type2 형식으로 넣어야함.
//index.tsx에서 사용하는 store 생성.
export const StoreZus = (initState: tableState = tableStateExport) => {
return createStore<WrStore>()((set) => {
return {
//아래에서 사용하는 state & actions은 외부 폴더에서 가져다가 사용.
...initState,//외부에 있는 기본 초기값 state
...tableActionsExport(set)//외부에 있는 actions
}
})
}
```
### 외부 스토어 작성 방법
외부 스토어는 export를 어떻게 할건지만 정하면됨.
사용하는 store.tsx에서 import를 받기만 하면 사용가능.
#### 타입을 외부로 빼는 방법
```js
export type tableState = {
isInit: boolean;
upHeadList: TableUpperProps[];
publicReuseFacilityUper: PublicReuseFacility[];
}
type tableActions = {
decrementList: () => void
}
export type tableType = tableState & tableActions;
```
#### 초기값 state를 외부로 빼는 방법
```js
export const tableStateExport:tableState= {
isInit:false,
upHeadList: upHeadList,
publicReuseFacilityUper: publicReuseFacilityUper,
}
```
#### actions를 외부로 빼는 방법
```js
//tableActionsExport를 import 받아서 실행하여 return 된 값을 사용
export const tableActionsExport:(set:any)=>tableActions=
//set을 통해 react store에 등록
(set: any) => {
return {
//decrementList Object 형태로 전달
decrementList:
//set 함수를 실행해서 react store에 등록
()=>set(
// store에 등록되는 함수 구조를 가지고 있음.
(state:tableState) => {
//actions 실질적인 로직 작성하는 곳.
return (
{
upHeadList: state.upHeadList,
publicReuseFacilityUper:
state.publicReuseFacilityUper.slice(0, state.publicReuseFacilityUper.length - 1)
})}
)
}
}
```
@@ -0,0 +1,94 @@
import { upHeadList, type rainFacOfType, type selectArray, type TableUpperProps } from "../rainReuseFacilityInsert";
const rswtOfObject:rainFacOfType[] = [
{
sido: "서울",
sigungo: "종로구",
facilityName: "그랑서울",
location: "종로 33",
locationgubun: "업무시설",
firstConstructionPermission : '2024-10-10',
buildingArea: "50",
facilityCapacity: "800,000",
reuse: "Y",
permissionDay:'2024-10-10',
register: "Y",
legalTargetYn:"Y"
},
{
sido: "서울",
sigungo: "종로구",
facilityName: "그랑서울",
location: "종로 33",
locationgubun: "업무시설",
firstConstructionPermission : '2024-10-10',
buildingArea: "50",
facilityCapacity: "800,000",
reuse: "Y",
permissionDay:'2024-10-10',
register: "Y",
legalTargetYn:"Y"
},
{
sido: "서울",
sigungo: "종로구",
facilityName: "그랑서울",
location: "종로 33",
locationgubun: "업무시설",
firstConstructionPermission : '2024-10-10',
buildingArea: "50",
facilityCapacity: "800,000",
reuse: "Y",
permissionDay:'2024-10-10',
register: "Y",
legalTargetYn:"Y"
},
]
const selectHomeWater = [
{ text: '집수면', val: "1" },
{ text: "종류", val: "2" },
];
const selectYN= [
{ text: 'Y', val: "1" },
{ text: 'N', val: "2" },
];
export type rswtFacState ={ rswt:{
isInit: boolean;
upHeadList: TableUpperProps[];
rswtOfObject: rainFacOfType[];
selectHomeWater:selectArray[];
selectYN:selectArray[];
}}
type rswtFacSActions = {
decrementList: () => void
}
export type rswtFacType = rswtFacState & rswtFacSActions;
export const rswtFacStateExport:rswtFacState= {rswt:{
isInit:false,
upHeadList: upHeadList,
rswtOfObject: rswtOfObject,
selectHomeWater:selectHomeWater,
selectYN:selectYN
}}
export const rswtFacActionsExport:(set:any)=>rswtFacSActions=(set: any) => {
return {
decrementList:()=>set(
(state:rswtFacState) => {
return (
{rain:{
upHeadList: state.rswt.upHeadList,
rainOfObject:
state.rswt.rswtOfObject.slice(0, state.rswt.rswtOfObject.length - 1)
}})}
)
}
}
+25
View File
@@ -0,0 +1,25 @@
import { createStore } from 'zustand/vanilla';
import { pubFacActionsExport, pubFacStateExport, type pubFacState } from './publicReuseFaciltyInsert/index';
import { rainFacActionsExport, rainFacStateExport, type rainFacState, type rainFacType } from './rainReuseFacilityInsert';
import { rswtFacActionsExport, rswtFacStateExport, type rswtFacState, type rswtFacType } from './rswtrReuseFaciltyInsert';
import { matDmdActionsExport, matDmdStateExport, type matDmdState, type matDmdType } from './matDmd';
export type WrStore = pubFacState & rainFacType &rswtFacType &matDmdType;
export const StoreZus = (initState: rainFacState& pubFacState & rswtFacState &matDmdState= {
...pubFacStateExport,
...rainFacStateExport,
...rswtFacStateExport ,
...matDmdStateExport
}) => {
return createStore<WrStore>()((set) => {
return {
...initState,
...pubFacActionsExport(set),
...rainFacActionsExport(set),
...rswtFacActionsExport(set),
...matDmdActionsExport(set)
}
})
}
//store 등록하는 부분
@@ -0,0 +1,9 @@
.formInsideTitle{
background-color: gray;
text-align: center;
}
.smallTitle{
}
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
+56
View File
@@ -0,0 +1,56 @@
import { Button } from "@common/components/ui";
import Link from "next/link";
import { useRouter } from "next/router";
import toast from "react-hot-toast";
export function ToastFormUi(body:string,title:string,url:string,buttonName:string){
//const router = useRouter();
return(
toast.custom((t) => (
<div
className={`${
t.visible ? 'animate-enter' : 'animate-leave'
} w-4/12 h-40 bg-white shadow-lg rounded-lg pointer-events-auto flex ring-1 ring-black ring-opacity-5`}
>
<div className="w-full flex-1 py-10">
<div className="flex items-start">
<div className="flex-shrink-0 pt-0.5">
</div>
<div className="ml-3 flex-1">
<p className="text-xl font-medium underline">
{title}
</p>
<p className="mt-1 text-2xl">
{body}
</p>
</div>
</div>
</div>
<div className="flex py-20">
<Button
size='sm'
onClick={() => toast.dismiss(t.id)}
>
<Link href={url}> {buttonName}</Link>
</Button>
</div>
<div className="flex pl-4 pr-4 py-20">
<Button
onClick={() => toast.dismiss(t.id)}
size='sm'
>
닫기
</Button>
</div>
</div>
))
)
}
+54
View File
@@ -0,0 +1,54 @@
import { FileUpload, FormControl, FormField, FormItem, FormLabel, FormMessage, SelectBox } from "@common/business_components";
import { DatePicker } from "@common/components";
import { Input } from "@common/components";
export type typeOfForm ={
labelName: string;
type: string;
placeholder:any;
formName: string;
selectValue:selectValue|undefined;
}
type onSelectValue = (val:string) => void;
export type selectValue ={
label:string,
value:{ text: string; val: string }[],
fun:onSelectValue
}
export function IteraterFrom(formData:typeOfForm[],form:any , formClass:string ){
const formItem =(data:typeOfForm,field:any)=>{
let tag
if(data.type==='select'){
tag= (data.selectValue ? <SelectBox className='' label={data.selectValue.label} selectArray={data.selectValue.value} onSelectValue={data.selectValue.fun} >{...form}</SelectBox> : null)
}else if(data.type==='date'){
tag =(<DatePicker mode="single" {...field} />)
}else{
tag=(<Input className ='w-64'placeholder= {data.placeholder} type="" {...field} />)
}
return tag;
}
return (
<>
{formData.map((data: typeOfForm, index) => (
<FormField key={`${index}+key`}
control={form.control}
name={data.formName}
render={({ field }) => (
<FormItem className={formClass} >
<FormLabel>{data.labelName}</FormLabel>
<FormControl>
{formItem(data,field)}
</FormControl>
<FormMessage />
</FormItem>
)}
/>
))}
</>
);
};
@@ -0,0 +1,91 @@
import { useWrStore } from "@/app/store";
import { selectClass } from "@/app/store/rainReuseFacilityInsert";
import { DatePicker, FormField, FormItem, FormLabel, FormUi, SelectBox } from "@common/business_components";
import { Button, Input } from "@common/components";
import { useState } from "react";
export function CoRegForm(form:any){
const { dmd } = useWrStore((state) => state);
return(
<>
<FormField
control={form.control}
name="ctpv"
render={({ field }) => (
<FormUi label="시도">
<SelectBox selectClass={selectClass} label={""} selectArray={dmd.selectSido} onSelectValue={field.onChange} className={""} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="sgg"
render={({ field }) => (
<FormUi label="시군구">
<SelectBox selectClass={selectClass} label={""} selectArray={dmd.selectSgg} onSelectValue={field.onChange} className={""} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="coNm"
render={({ field }) => (
<FormUi label="회사명">
<Input placeholder="" type="text" {...field} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="bznmNo"
render={({ field }) => (
<FormUi label="사업자번호">
<Input
placeholder="a0123456789"
type="text"
{...field} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="addr"
render={({ field }) => (
<FormUi label="주소">
<Input
placeholder="성북로24길 16"
type="text"
{...field} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="telNo"
render={({ field }) => (
<FormUi label="전화번호">
<Input
placeholder="01033332222"
type="text"
{...field} />
</FormUi>
)}
/>
</>
)
}
@@ -0,0 +1,316 @@
import { useWrStore } from "@/app/store";
import { selectClass } from "@/app/store/rainReuseFacilityInsert";
import { DatePicker, FormField, FormItem, FormLabel, FormUi, SelectBox } from "@common/business_components";
import { Button, Input } from "@common/components";
import { useState } from "react";
export function DmdRegForm(form: any) {
const { dmd } = useWrStore((state) => state);
const [dmdDate, setDmdDate] = useState(new Date())
const onChangeDate = (val: Date|undefined) => {
if(val)setDmdDate(val)
}
return (
<>
<FormField
control={form.control}
name="dmdSel"
render={({ field }) => (
<FormUi label="수요처명">
<Input
placeholder="수요처명 검색"
type="text"
{...field} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="dmdTmng"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>요구시기</FormLabel>
<DatePicker mode={"single"} field={{ value: dmdDate, onChange: onChangeDate }} />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dmdWq"
render={({ field }) => (
<FormUi label="요구수질">
<Input
placeholder="55.22"
type="text"
{...field} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="useUsgCd"
render={({ field }) => (
<FormUi label="사용용도">
<SelectBox selectClass={selectClass} label={""} selectArray={dmd.selectUsg} onSelectValue={field.onChange} className={""} />
</FormUi>
)}
/>
<FormField
control={form.control}
name="wntUntprc"
render={({ field }) => (
<FormUi label="희망단가">
<Input
placeholder="1000000000000000"
type="text"
{...field} />
</FormUi>
)}
/>
<div className="grid grid-cols-4">
<h1 className=" col-span-2 pl-5 text-2xl underline">
요구수질
</h1>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="grssClfg"
render={({ field }) => (
<FormUi label="총대장균군">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="tn"
render={({ field }) => (
<FormUi label="총질소">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="combResdch"
render={({ field }) => (
<FormUi label="결합잔류염소">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="tp"
render={({ field }) => (
<FormUi label="총인">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="trbt"
render={({ field }) => (
<FormUi label="탁도">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="ph"
render={({ field }) => (
<FormUi label="수소이온농도">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="bod"
render={({ field }) => (
<FormUi label="생물학적 산소요구량">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="clIonQnt"
render={({ field }) => (
<FormUi label="염화물">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="smll"
render={({ field }) => (
<FormUi label="냄새">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="cdtnv"
render={({ field }) => (
<FormUi label="전기전도도">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="crmty"
render={({ field }) => (
<FormUi label="색도">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="rmrk"
render={({ field }) => (
<FormUi label="비고">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-4">
<h1 className=" col-span-1 pl-2 text-2xl underline">
공급후보지 선택
</h1>
<div className=" col-start-3 col-span-1 ">
<Button >
후보지 조회
</Button>
</div>
<div className=" col-start-4 col-span-1 ">
<Button >
유사사례조회
</Button>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-4">
<FormField
control={form.control}
name="sply1"
render={({ field }) => (
<FormUi label="후보지_1">
<Input placeholder="후보지1" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="sply2"
render={({ field }) => (
<FormUi label="후보지_2">
<Input placeholder="후보지2" type="text" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="sply3"
render={({ field }) => (
<FormUi label="후보지_3">
<Input placeholder="후보지3" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-4">
<h1 className=" col-span-2 pl-2 text-2xl underline">
수요신청 비밀번호
</h1>
<div className="col-start-3 col-span-2">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormUi label="">
<Input placeholder="10" type="text" {...field} />
</FormUi>
)}
/>
</div>
</div>
</>
);
}
@@ -0,0 +1,497 @@
"use client";
import { SelectBox } from "@common/business_components";
import { DatePicker, FileUpload, FormUi, UiTable } from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button, Input, TableHead, TableRow } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useWrStore } from "../../app/store/index";
import { selectClass, type TableUpperProps } from "@/app/store/rainReuseFacilityInsert";
const formSchema = z.object({
username: z.string().min(2).max(50),
facilityName: z.string().min(2).max(50),
});
const headMakeColSpan = (upHeadList: TableUpperProps[]) => {
return (<>
<TableRow>{
upHeadList.map((head: TableUpperProps) =>
(!head.upSequnce ? <TableHead rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead key={head.id} rowSpan={1} colSpan={2}>{'운영대행기간'}</TableHead> : null))}
</TableRow>
<TableRow>
{upHeadList.map((head: TableUpperProps) =>
(head.upName === 'upChangeRe' ? <TableHead key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
</>)
}
export function PublicReuseFaciltyFormInsert() {
const { pubFac } = useWrStore((state) => state);
const { pubMngUpHeadList, pubMngFacData,erctUswtrCd,cyclCd,seCd,buseCd,rprcsCd,buseMet,rprcLoCd} = pubFac;
const nodeList = headMakeColSpan(pubMngUpHeadList);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
});
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values);
}
const onSelectValue=(val:string)=>{
console.log(val)
}
const [instlAprvDate, setInstlAprvDate] = useState(new Date())
const [bizCmcnDate, setBizCmcnDate] = useState(new Date())
const [files, setFiles] = useState(null);
return (
<div className="pl-3">
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex justify-between items-center mx-3 mb-3">
<div className="col-span-2 flex items-top">
<h1 className="pl-3 pr-3 text-xl">○ 재이용시설 운영정보</h1>
<FormField
control={form.control}
name="facilityName"
render={({ field }) => (
<FormUi label="">
<Input readOnly className="h-10 text-center text-xl" placeholder="난지" type="" {...field} />
</FormUi>
)}
/>
</div>
<Button className=" rounded-lg" type="submit">
신규등록
</Button>
</div>
<div className="grid grid-cols-12 gap-4 my-4">
<div className="col-span-6">
<FormField
control={form.control}
name="name_0211213336"
render={({ }) => (
<FormUi label="계통도">
<FileUpload files={files} setFiles={setFiles} fileTypes="PDF"></FileUpload>
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="name_0211213336"
render={({ }) => (
<FormUi label="평면도">
<FileUpload files={files} setFiles={setFiles} fileTypes="PDF"></FileUpload>
</FormUi>
)}
/>
</div>
</div>
<div className="flex justify-between items-center mx-3 mb-3">
<h1 className="pl-3 text-xl">○ 재이용시설 운영정보</h1>
<Button className=" rounded-lg" >추가등록</Button>
</div>
<div>
<UiTable
tableData={pubMngFacData}
headlist={pubMngUpHeadList}
pageSize={100}
total={-1}
headName={""}
>{nodeList}</UiTable>
</div>
<div>
<h1 className="pl-3 text-xl mb-3">○ 재이용수 공급 가능량</h1>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-3">
<FormField
control={form.control}
name="name_0943143371"
render={({ field }) => (
<FormUi label="시설용량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="name_2007089217"
render={({ field }) => (
<FormUi label="하수처리량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="name_1624679275"
render={({ field }) => (
<FormUi label="재이용량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="name_1624679277"
render={({ field }) => (
<FormUi label="공급여유량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
</div>
</form>
</Form>
</div>
{/* 차수별 등록 현황 폼 */}
<div className="justify-between">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 mx-auto w-full mt-7">
<div className="flex justify-between items-center mx-3 mb-3">
<h1 className=" text-xl">
○ 차수별 하수처리수 재이용 시설 현황
</h1>
<div className="flex items-center gap-2 mx-3">
<h1 className=" text-xl">
등록차수
</h1>
<FormField
control={form.control}
name="name_1486273033"
render={({ field }) => (
<FormUi label=''>
<SelectBox label='' selectArray={cyclCd} className={""} selectClass={selectClass} onSelectValue={onSelectValue } {...field}></SelectBox>
</FormUi>
)}
/>
<Button className="rounded-lg">
신규등록
</Button>
</div>
</div>
<div>
<h1 className="pl-3 text-xl">♦︎하수처리수 재이용 시설 현황</h1>
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 ">
사업개요{" "}
</h1>
<h1 className="pl-4 text-xl my-0">1. 현황</h1>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-3">
<FormField
control={form.control}
name="name_8928357542"
render={({ field }) => (
<FormUi label="구분">
<SelectBox selectArray={seCd} className={""} label={""} selectClass={selectClass} onSelectValue={onSelectValue} />
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="name_9484942814"
render={({ field }) => (
<FormUi label="사업방식">
<SelectBox selectArray={buseMet} className={""} label={""} selectClass={selectClass} onSelectValue={onSelectValue}/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="name_2450741594"
render={({ field }) => (
<FormUi label="설치승인인가일">
<DatePicker
selectClass={'rounded-lg w-full bg-white'} field={{
value: instlAprvDate,
onChange: (date: Date | undefined): void => {
if (date) setInstlAprvDate(date);
}
}} mode="single" {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="name_2450741594"
render={({ field }) => (
<FormUi label="사업준공일">
<DatePicker
selectClass={'rounded-lg w-full'}
field={{
value: bizCmcnDate,
onChange: function (date: Date | undefined): void {
if (date) setBizCmcnDate(date);
}
}} mode="single" {...field} />
</FormUi>
)}
/>
</div>
</div>
<h1 className="pl-4 text-xl">2. 사업비</h1>
<div className="grid grid-cols-10 gap-4">
<div className="col-span-2">
<FormField
control={form.control}
name="name_7678668236"
render={({ field }) => (
<FormUi label="국고">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="name_7913744238"
render={({ field }) => (
<FormUi label="지방비">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="name_1368081408"
render={({ field }) => (
<FormUi label="민간투자비">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="name_1368081408"
render={({ field }) => (
<FormUi label="원인자부담금">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="name_1368081408"
render={({ field }) => (
<FormUi label="기타">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="w-64">
<FormField
control={form.control}
name="name_1368081408"
render={({ field }) => (
<FormUi label="합계">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 ">
재처리시설{" "}
</h1>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-4">
<FormField
control={form.control}
name="name_7678668236"
render={({ field }) => (
<FormUi label="시설용량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_7913744238"
render={({ field }) => (
<FormUi label="주처리공법">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_1368081408"
render={({ field }) => (
<FormUi label="위치(주소)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-4 ">
<FormField
control={form.control}
name="name_5139286452"
render={({ field }) => (
<FormUi label="재처리방식">
<SelectBox selectArray={rprcsCd} className={""} label={""} selectClass={selectClass} onSelectValue={onSelectValue }/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_7233630277"
render={({ field }) => (
<FormUi label="위치(처리장 내외)">
<SelectBox selectArray={rprcLoCd} className={""} label={""} selectClass={selectClass} onSelectValue={onSelectValue}/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_0973765174"
render={({ field }) => (
<FormUi label="메인공급펌프 용량">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 ">
농축수관리{" "}
</h1>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-4">
<FormField
control={form.control}
name="name_7678668236"
render={({ field }) => (
<FormUi label="눙축수 발생량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_7913744238"
render={({ field }) => (
<FormUi label="눙축수연계지점">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_1368081408"
render={({ field }) => (
<FormUi label="시설용량(m³/일)">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-4 ">
<FormField
control={form.control}
name="name_5139286452"
render={({ field }) => (
<FormUi label="눙축수처리방식">
<SelectBox selectArray={erctUswtrCd} className={""} label={""} selectClass={selectClass} onSelectValue={onSelectValue}/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="name_7233630277"
render={({ field }) => (
<FormUi label="눙축수처리공법">
<Input placeholder="" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
</form>
</Form>
</div>
</div>
);
}
@@ -0,0 +1,240 @@
"use client";
import {
Form,
FormField,
SelectBox,
} from "@common/business_components/ui";
import { Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { FormUi } from "@common/business_components";
import { useWrStore } from "@/app/store";
import { selectClass } from "@/app/store/rainReuseFacilityInsert";
const formSchema = z.object({
username: z.string().min(2).max(50),
facilityName: z.string().min(2).max(50),
});
export function RainReuseFaciltyInsertForm() {
const { rain } = useWrStore((state) => state);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
});
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values);
}
const onSelectValue=(val:string)=>{
console.log(val)
}
return (
<div className="">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="flex justify-between items-center mx-3"> */}
<div>
<div>
<h1 className="pl-3 text-xl ">○ 빗물이용시설 운영정보</h1>
</div>
</div>
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 mt-4 ">
기본정보
</h1>
<div className="grid grid-cols-4 gap-2 mt-4 pl-6" >
<div className="col-span-2">
<FormField
control={form.control}
name="name_0211213336"
render={({ field }) => (
<FormUi label="설치완료일">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="설치비">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className=" my-4">
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 mt-4 pl-6" >
빗물이용시설 정보
</h1>
</div>
<div>
<h2 className="pl-3 text-xl">✓ 1. 집수면</h2>
</div>
<div className="grid grid-cols-10 gap-1 mt-4">
<div className="col-span-5 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="종류">
<SelectBox selectClass={selectClass} label={""} selectArray={rain.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-5" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="면적(m³)">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div>
<h2 className="pl-3 text-xl">✓ 2. 여과 등 처리시설</h2>
</div>
<div className="grid grid-cols-12 gap-2 mt-4">
<div className="col-span-4 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="유무">
<SelectBox selectClass={selectClass} label={""} selectArray={rain.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-4" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="용량(m³/일)">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-4" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="처리공정">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className=" my-2">
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 mt-4 ">
인센티브 정보
</h1>
</div>
<div>
<h2 className="pl-3 text-xl">✓ 1. 설치비 지원</h2>
</div>
<div className="grid grid-cols-10 gap-2 pl-6 mt-4">
<div className="col-span-5" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="설치비 지원 여부">
<SelectBox selectClass={selectClass} label={""} selectArray={rain.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-5" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="지원금액(원)">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div>
<h2 className="pl-3 text-xl">✓ 2. 녹색건축물 인증</h2>
</div>
<div className="grid grid-cols-6 gap-2 mt-4">
<div className="col-span-3 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증여부">
<SelectBox selectClass={selectClass} label={""} selectArray={rain.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-3" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증년도">
<Input className="" placeholder="2024" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증번호">
<Input className="" placeholder="ab1234678" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증등급">
<Input className="" placeholder="A" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
</form>
</Form>
</div>
);
}
@@ -0,0 +1,240 @@
"use client";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
FormUi,
SelectBox,
} from "@common/business_components/ui";
import { Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useWrStore } from "@/app/store";
import { selectClass } from "@/app/store/rainReuseFacilityInsert";
const formSchema = z.object({
username: z.string().min(2).max(50),
facilityName: z.string().min(2).max(50),
});
export function RswtrReuseFaciltyInsertForm() {
const { rswt } = useWrStore((state) => state);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
});
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values);
}
const onSelectValue=(val:string)=>{
console.log(val)
}
return (
<div className="">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="flex justify-between items-center mx-3"> */}
<div>
<div>
<h1 className="pl-3 text-xl ">○ 중수도 시설 운영정보</h1>
</div>
</div>
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 mt-4 ">
기본정보
</h1>
<div className="grid grid-cols-4 gap-2 mt-4 pl-6" >
<div className="col-span-2">
<FormField
control={form.control}
name="name_0211213336"
render={({ field }) => (
<FormUi label="설치완료일">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="설치비">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-4 gap-2 mt-4 pl-6" >
<div className="col-span-2">
<FormField
control={form.control}
name="name_0211213336"
render={({ field }) => (
<FormUi label="물사용량(m³)">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-2" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="의무대상 물사용량(m³)">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div className=" my-4">
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 mt-4 pl-6" >
중수도 시설 정보
</h1>
</div>
<div className="grid grid-cols-10 gap-1 mt-4">
<div className="col-span-5 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="필수 종류">
<SelectBox selectClass={selectClass} selectArray={rswt.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-5" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="주요처리공정">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-5 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="이용용도">
<SelectBox selectClass={selectClass}label={""} selectArray={rswt.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
</div>
<div className=" my-2">
<h1 className="bg-gray-400 text-xl text-center border-2 p-2 mt-4 ">
인센티브 정보
</h1>
</div>
<div>
<h2 className="pl-3 text-xl">✓ 1. 설치비 지원</h2>
</div>
<div className="grid grid-cols-2 gap-2 mt-4">
<div className="col-span-1 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="설치비 지원여부">
<SelectBox selectClass={selectClass} label={""} selectArray={rswt.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-1" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="지원금액(원)">
<Input className="" placeholder="1,000,000" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
<div>
<h2 className="pl-3 text-xl">✓ 2. 녹색건축물 인증</h2>
</div>
<div className="grid grid-cols-6 gap-2 mt-4">
<div className="col-span-3 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증여부">
<SelectBox selectClass={selectClass} label={""} selectArray={rswt.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-3" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증년도">
<Input className="" placeholder="2024" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3 pl-6" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증번호">
<Input className="" placeholder="ab1234678" type="" {...field} />
</FormUi>
)}
/>
</div>
<div className="col-span-3" >
<FormField
control={form.control}
name="name_9765724553"
render={({ field }) => (
<FormUi label="인증등급">
<Input className="" placeholder="A" type="" {...field} />
</FormUi>
)}
/>
</div>
</div>
</form>
</Form>
</div>
);
}
+16
View File
@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;
+5
View File
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+41
View File
@@ -0,0 +1,41 @@
import type { NextConfig } from "next";
import webpack from "webpack";
const nextConfig: NextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
webpack: (config) => {
config.plugins.push(
new webpack.DefinePlugin({
"import.meta.env.VITE_PORT": JSON.stringify(
process.env.NEXT_PUBLIC_PORT
),
"import.meta.env.VITE_PROXY_HOST": JSON.stringify(
process.env.NEXT_PUBLIC_PROXY_HOST
),
"import.meta.env.VITE_SERVER_API_URL": JSON.stringify(
process.env.NEXT_PUBLIC_SERVER_API_URL
),
"import.meta.env.VITE_SITE_ID": JSON.stringify(process.env.SITE_ID),
"import.meta.env.VITE_CLAIM_NAME": JSON.stringify(
process.env.NEXT_PUBLIC_CLAIM_NAME
),
"import.meta.env.VITE_ACCESS_TOKEN": JSON.stringify(
process.env.NEXT_PUBLIC_ACCESS_TOKEN
),
"import.meta.env.VITE_REFRESH_TOKEN": JSON.stringify(
process.env.NEXT_PUBLIC_REFRESH_TOKEN
),
"import.meta.env.VITE_AUTH_USER_ID": JSON.stringify(
process.env.NEXT_PUBLIC_AUTH_USER_ID
),
"process.env.SITE_ID": JSON.stringify(process.env.NEXT_PUBLIC_SITE_ID),
})
);
return config;
},
};
export default nextConfig;
+34
View File
@@ -0,0 +1,34 @@
{
"name": "wr",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@common/business_components": "workspace:*",
"@common/components": "workspace:*",
"@hookform/resolvers": "^4.1.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.474.0",
"next": "15.1.7",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.5.2",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/webpack": "^5.28.5",
"eslint": "^9",
"eslint-config-next": "15.1.7",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
+1
View File
@@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

+66
View File
@@ -0,0 +1,66 @@
import baseConfig from "../../packages/common/components/tailwind.config";
import type { Config } from "tailwindcss";
export default {
darkMode: ["class"],
presets: [baseConfig],
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx,mdx}",
"../../packages/common/business_components/src/ui/**/*.{js,ts,jsx,tsx,mdx}",
],
plugins: [require("tailwindcss-animate")],
theme: {
extend: {
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
}
}
}
} satisfies Config;
+53
View File
@@ -0,0 +1,53 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"verbatimModuleSyntax": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
"@common/components/*": ["../../packages/common/components/src/*"],
"@common/components": ["../../packages/common/components/src/index"],
"@common/business_components/*": [
"../../packages/common/business_components/src/*"
],
"@common/business_components": [
"../../packages/common/business_components/src/index"
],
"@common/network": ["../../packages/common/network"]
}
},
"include": [
".eslintrc.*",
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.js",
"**/*.cjs",
"**/*.mjs",
"**/*.jsx",
"**/*.json",
".next/types/**/*.ts"
],
"exclude": ["**/node_modules", "**/.*/*"]
}
+3 -1
View File
@@ -12,12 +12,14 @@
"build": "turbo run build",
"dev": "turbo run dev",
"dev:cs": "turbo run dev --filter=cs",
"dev:wr": "turbo run dev --filter=wr",
"preview:components": "turbo run storybook --filter=@common/components",
"preinstall": "npx only-allow pnpm"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"zustand": "^5.0.3"
},
"devDependencies": {
"@types/node": "^22.13.9",
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*storybook.log
storybook-static
.vercel
.env.production
@@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}
@@ -0,0 +1,33 @@
## business_components 패키지
### 원앤올에서 제공받는 컴포넌트 외에 업무 파트에서 사용할 수 있는 별도 components 패키지지
&emsp; 1) 환경설정
&emsp; &emsp; (1) tsconfig.json 'path'에 '@common/business_components' 변수를 추가
```json
"paths": {
"@/*": ["./*"],
"@common/components/*": ["../../packages/common/components/src/*"],
"@common/components": ["../../packages/common/components/src/index"],
//아래에 해당하는 부분을 추가해줘야함.
"@common/business_components/*": ["../../packages/common/business_components/src/*"],
"@common/business_components": ["../../packages/common/business_components/src/index"]
}
```
&emsp; &emsp; (2) package.json 'dependencies'에 '@common/business_components' 추가
```json
"dependencies": {
"@common/business_components": "workspace:*",
"next": "15.1.7"
},
```
&emsp; &emsp; (3) tailwind.config.ts 추가
``` json
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx,mdx}",
//아래에 해당하는 부분을 추가해줘야함.
"../../packages/common/business_components/src/ui/**/*.{js,ts,jsx,tsx,mdx}",
],
```
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
@@ -0,0 +1,28 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
}
);
@@ -0,0 +1,83 @@
{
"name": "@common/business_components",
"version": "0.0.0",
"private": true,
"sideEffects": false,
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
}
},
"files": [
"dist/**"
],
"scripts": {
"build": "tsup",
"storybook": "storybook dev -p 6006",
"build:storybook": "storybook build",
"lint": "eslint .",
"clean": "rm -rf dist"
},
"dependencies": {
"@common/components": "workspace:*",
"@hookform/resolvers": "^4.1.3",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"lucide-react": "^0.474.0",
"path": "^0.12.7",
"react-day-picker": "^8.10.1",
"react-dropzone": "^14.3.8",
"react-hook-form": "^7.54.2",
"recharts": "^2.15.1",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3",
"@storybook/addon-onboarding": "^8.5.3",
"@storybook/addon-themes": "^8.5.4",
"@storybook/blocks": "^8.5.3",
"@storybook/manager-api": "^8.5.5",
"@storybook/react": "^8.5.3",
"@storybook/react-vite": "^8.5.3",
"@storybook/test": "^8.5.3",
"@storybook/theming": "^8.5.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"eslint-plugin-storybook": "^0.11.2",
"globals": "^15.14.0",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"storybook": "^8.5.3",
"tailwindcss": "^3.4.17",
"tsup": "8.3.6",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5",
"vite-tsconfig-paths": "5.1.4"
},
"eslintConfig": {
"extends": [
"plugin:storybook/recommended"
]
},
"packageManager": "pnpm@8.15.5+sha1.a58c038faac410c947dbdb93eb30994037d0fce2"
}
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
@@ -0,0 +1,73 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: #ffffff;
--foreground: #0a0a0a;
--card: #ffffff;
--card-foreground: #0a0a0a;
--popover: #ffffff;
--popover-foreground: #0a0a0a;
--primary: #2094fa;
--primary-foreground: #fafafa;
--secondary: #f5f5f5;
--secondary-foreground: #0f1e33;
--muted: #f5f5f5;
--muted-foreground: #737373;
--accent: #f5f5f5;
--accent-foreground: #0f1e33;
--destructive: #ff4141;
--destructive-foreground: #fafafa;
--border: #e5e5e5;
--input: #e5e5e5;
--label: #666666;
--ring: #0a0a0a;
--chart-1: #d95b43;
--chart-2: #299392;
--chart-3: #263e4d;
--chart-4: #dfb938;
--chart-5: #f08634;
--radius: 1rem;
}
[data-mode="dark"] {
--background: #0a0a0a;
--foreground: #fafafa;
--card: #0a0a0a;
--card-foreground: #fafafa;
--popover: #0a0a0a;
--popover-foreground: #fafafa;
--primary: #2094fa;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a2a2a2;
--accent: #262626;
--accent-foreground: #fafafa;
--destructive: #b22222;
--destructive-foreground: #fafafa;
--border: #262626;
--input: #262626;
--label: #f1f1f1;
--ring: #d4d4d4;
--chart-1: #3399ff;
--chart-2: #339966;
--chart-3: #ff9933;
--chart-4: #cc66ff;
--chart-5: #ff3399;
}
}
@layer base {
* {
@apply border-border;
}
html {
@apply text-[10px];
}
body {
@apply bg-background text-foreground;
}
}
@@ -0,0 +1,54 @@
## Chart 모듈
### 공통 모듈 중 Chart를 다양한 테마로 적용시킬 수 있도록 해주는 모듈
&emsp; 1. 차트 모듈 사용법
&emsp; &emsp; 1-1) 호출방법
&emsp; &emsp; &emsp; (1) 파일에 차트생성에 필요한 form, MainChart, MainConfig 임폴트
```js
import {type ChartConfig, MainChart, CHART_FORM } from "@common/business_components";
```
&emsp; &emsp; &emsp; (2) ChartForm 지정
```js
/**
* CHART_FORM.BAR : Bar차트
* CHART_FORM.LINE : Line차트
* DN : Daughnut차트
**/
const chartForm = CHART_FORM.BAR;
```
&emsp; &emsp; &emsp; (3) chartData 정의
``` js
/**
* label : 차트항목 / val : 데이터값
**/
const chartData = [
{ label: "January", val: 186},
{ label: "March", val: 237},
{ label: "February", val: 305},
{ label: "April", val: 73},
{ label: "May", val: 209},
{ label: "June", val: 214},
]
```
&emsp; &emsp; &emsp; (4) Chart생성에 필요한 chartConfig 정의
``` js
/**
* 차트 Config
*/
const chartConfig = chartData.reduce((config,>>element) => {
label: element.label,
config[element.label] = {
color : "hsl(var(--chart-1))"
};
return config;
}, {} as Record<string, { label: string; color: string; }>) satisfies ChartConfig;
```
&emsp; &emsp; &emsp; (5) Chart 객체 생성
```html
return (
<div>
<MainChart chartConfig={chartConfig} chartForm={chartForm} chartData={chartData}></MainChart>
</div>
);
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
export const BASE_ASSETS_PATH = process.env.BASE_ASSETS_PATH ?? "/assets";
@@ -0,0 +1,93 @@
## form 모듈
### 공통 모듈 중 chart를 다양한 테마로 적용시킬 수 있도록 해주는 모듈
&emsp; 1. 폼 모듈 사용법
&emsp; &emsp; 1-1) 패키지 사용법
&emsp; &emsp; &emsp; (1) zod 사용법
https://zod.dev/?id=introduction
&emsp; &emsp; 1-2) 호출방법
&emsp; &emsp; &emsp; (1) form에 필요한 Form,FormField,useForm useForm,z, zodResolver import
```js
import {
Form,
FormField,
SelectBox,
} from "@common/business_components/ui";
import { Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { FormUi } from "@common/business_components";
```
&emsp; &emsp; &emsp; (2)zod 정의
```js
const formSchema = z.object({
facilityName: z.string().min(2).max(50),
instlCmptnDt:z.string(), // FormField 내 name의 타입및 min max 구성 가능
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
//초기값 세팅 z.object에 등록한 값은 반드시 세팅해야함.
defaultValues: {
facilityName :'',
instlCmptnDt:'', //
},
});
function onSubmit(values: z.infer<typeof formSchema>) {
// form submit 함수
console.log(values);// zod.object에 등록하고 form에 구현한 값이 출력됨.
}
```
&emsp; &emsp; &emsp; (3) form구성
```js
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="col-span-3 pl-6" >
<FormField
control={form.control}
name="grntchCstSprtYn" //zodobject에 등록한 값 삽입
render={({ field }) => (
// zod에 등록된 name 값의 폼 객체 생성
//select box 삽입시
<FormUi label="인증여부">
<SelectBox className={""} selectClass={selectClass} label={""} selectArray={rain.selectYN} onSelectValue={onSelectValue} {...field}/>
</FormUi>
)}
/>
</div>
<div className="col-span-3" >
<FormField
control={form.control}
name="grntchBdstCertYr"
render={({ field }) => (
// zod에 등록된 name 값의 폼 객체 생성
//input 삽입시
<FormUi label="인증년도">
<Input className="" placeholder="2024" type="" {...field} />
</FormUi>
)}
/>
</div>
</form>
</Form>
```
@@ -0,0 +1,11 @@
@layer base {}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
@@ -0,0 +1 @@
export * from "./ui";
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
@@ -0,0 +1,42 @@
"use client"
import type { CSSProperties } from "react";
import {
Card,
CardContent
} from "./lib/card";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig
} from "./lib/chart";
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
export default function Chart(props: { chartConfig: ChartConfig; chartData: any[] | undefined; chartTitle: string}) {
return (
<Card className="h-full">
<CardContent className="h-full">
{props.chartTitle && props.chartTitle !== "" && props.chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
{props.chartTitle}
</div>): null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={props.chartConfig} className="h-full w-full">
<BarChart accessibilityLayer data={props.chartData} className="h-full">
<CartesianGrid vertical={false} />
<XAxis
dataKey="label"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="dashed" />}/>
<Bar dataKey="val" radius={10} />
</BarChart>
</ChartContainer>
</div>
</CardContent>
</Card>
)
}
@@ -0,0 +1,47 @@
"use client"
import { Pie, PieChart } from "recharts";
import {
Card,
CardContent
} from "./lib/card";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig
} from "./lib/chart";
// eslint-disable-next-line react-refresh/only-export-components
export default (props: { chartConfig: ChartConfig; chartData: never[] | undefined; chartTitle:string}) =>{
return (
<Card className="flex flex-col">
<CardContent className="flex-1 pb-0">
{props.chartTitle && props.chartTitle !== "" && props.chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
{props.chartTitle}
</div>): null}
<div className="h-[calc(100%-42px)] justify-center">
<ChartContainer
config={props.chartConfig}
className="mx-auto aspect-square max-h-[300px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={props.chartData}
dataKey="val"
nameKey="label"
innerRadius={60}
/>
</PieChart>
</ChartContainer>
</div>
</CardContent>
</Card>
)
}
@@ -0,0 +1,108 @@
"use client"
import type { CSSProperties } from "react";
import { Bar, CartesianGrid, ComposedChart, Legend, Line, Tooltip, XAxis, YAxis } from "recharts";
import {
Card,
CardContent
} from "./lib/card";
import {
ChartContainer,
type ChartConfig
} from "./lib/chart";
import React from "react";
// 툴팁 커스텀 css
/** 차트 기본 스타일 */
const styles = {
tooltipDiv: {
borderRadius: "5px",
backgroundColor: "white",
padding: "10px",
boxShadow: "0px 2px 10px rgba(0,0,0,0.2)",
width: "180px", // 적절한 크기로 설정
} as React.CSSProperties,
tooltipLabel: {
fontWeight: "bold",
fontSize: "12px",
marginBottom: "5px",
borderBottom: "1px solid #ddd",
paddingBottom: "3px",
} as React.CSSProperties,
tooltipText: {
fontSize: "11px",
fontWeight:"bold",
} as React.CSSProperties,
legendContainer: {
display: "flex",
justifyContent: "center",
gap: "20px",
fontSize: "9px",
fontWeight : "bold",
marginTop: "10px",
} as CSSProperties,
};
// 커스텀 툴팁
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div style={styles.tooltipDiv}>
<p style={styles.tooltipLabel}>{label}</p>
{payload.map((item, index) => (
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
{/* 색상 박스 추가 (범례 색상과 동일) */}
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
<span style={styles.tooltipText}>{`${item.name}: ${item.value}`}</span>
</div>
))}
</div>
);
}
return null;
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: any) => {
const { payload } = props;
return (
<div style={styles.legendContainer}>
{payload.map((entry, index) => (
<div key={`legend-${index}`} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<div style={{ width: "12px", height: "12px", backgroundColor: entry.color, borderRadius: "50%" }}></div>
<span>{entry.value}</span>
</div>
))}
</div>
);
};
export default function LineBarchart(props: { chartConfig: ChartConfig; chartData: any[] | undefined; chartTitle:string }) {
return (
<Card className="h-full">
<CardContent className="h-full">
{props.chartTitle && props.chartTitle !== "" && props.chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
{props.chartTitle}
</div>): null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={props.chartConfig} className="h-full w-full" >
<ComposedChart data={props.chartData} className="h-full ">
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
{/* 왼쪽 Y축: lineVal (정상) */}
<YAxis yAxisId="left" orientation="left" />
{/* 오른쪽 Y축: barVal (위에서 아래로 반전) */}
<YAxis yAxisId="right" orientation="right" reversed />
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
<Legend content={CustomLegend}/>
{/* 바 차트 (오른쪽 Y축 사용, 위에서 아래로) */}
<Bar yAxisId="right" dataKey="barVal" fill="#8884d8" name="강수량(mm/일)"/>
{/* 라인 차트 (왼쪽 Y축 사용) */}
<Line yAxisId="left" type="monotone" dataKey="lineVal" stroke="orange" strokeWidth={2} dot={{r: 0}} name="BOD(mg/L)"/>
</ComposedChart>
</ChartContainer>
</div>
</CardContent>
</Card>
)
}
@@ -0,0 +1,121 @@
"use client"
import type { CSSProperties } from "react";
import {
Card,
CardContent
} from "./lib/card";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig
} from "./lib/chart";
import { CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts";
/** 차트 기본 스타일 */
const styles = {
tooltipDiv: {
borderRadius: "5px",
backgroundColor: "white",
padding: "10px",
boxShadow: "0px 2px 10px rgba(0,0,0,0.2)",
width: "180px", // 적절한 크기로 설정
} as React.CSSProperties,
tooltipLabel: {
fontWeight: "bold",
fontSize: "12px",
marginBottom: "5px",
borderBottom: "1px solid #ddd",
paddingBottom: "3px",
} as React.CSSProperties,
tooltipText: {
fontSize: "11px",
fontWeight:"bold",
} as React.CSSProperties,
legendContainer: {
display: "flex",
justifyContent: "center",
gap: "20px",
fontSize: "9px",
fontWeight : "bold",
marginTop: "10px",
} as CSSProperties,
};
// 커스텀 툴팁
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div style={styles.tooltipDiv}>
<p style={styles.tooltipLabel}>{label}</p>
{payload.map((item, index) => (
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
{/* 색상 박스 추가 (범례 색상과 동일) */}
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
<span style={styles.tooltipText}>{`${item.name}: ${item.value}`}</span>
</div>
))}
</div>
);
}
return null;
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: any) => {
const { payload } = props;
return (
<div style={styles.legendContainer}>
{payload.map((entry, index) => (
<div key={`legend-${index}`} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<div style={{ width: "12px", height: "12px", backgroundColor: entry.color, borderRadius: "50%" }}></div>
<span>{entry.value}</span>
</div>
))}
</div>
);
};
type DataType = {label : string; val:number};
type ChartDataType = {
chartData1: DataType[];
chartData2: DataType[];
};
export default function Linechart(props: { chartConfig: ChartConfig; chartData: ChartDataType; chartTitle:string}) {
const mergedData = Object.values(props.chartData).reduce((acc, dataset) => {
dataset.forEach((entry, index) => {
if (!acc[index]) {
acc[index] = { label: entry.label }; // label 추가
}
const key = Object.keys(props.chartData).find(k => props.chartData[k] === dataset);
if (key) acc[index][key] = entry.val;
});
return acc;
}, [] as any[]);
return (
<Card className="h-full">
<CardContent className="h-full">
{props.chartTitle && props.chartTitle !== "" && props.chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
{props.chartTitle}
</div>): null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={props.chartConfig} className="h-full w-full">
<LineChart data={mergedData} className="h-full">
<CartesianGrid vertical={false} />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
<YAxis yAxisId="left" orientation="left" />
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
<Legend content={CustomLegend}/>
{Object.keys(props.chartData).map((key, index) => (
<Line key={key} yAxisId="left" dataKey={key} type="linear" strokeWidth={2} dot={false} name={key} />
))}
</LineChart>
</ChartContainer>
</div>
</CardContent>
</Card>
)
}
@@ -0,0 +1,48 @@
import React from 'react';
import BarChart from './BarChart';
import DoughnutChart from './DoughnutChart';
import { type ChartConfig } from './lib/chart';
import LineChart from './LineChart';
import LineBarchart from './LineBarChart';
/**차트 FORM */
const CHART_FORM = {
/**바 차트 형태*/
BAR : 'bar',
/**라인 차트 형태*/
LINE:'line',
/**도넛 차트 형태*/
DN :'daughnut',
/**LINE,BAR차트 */
LINEBAR : 'linebar'
};
function MainChart(props: { chartForm: string; chartConfig: any; chartData: any; chartTitle:string; }) {
if( props.chartForm == CHART_FORM.BAR ){
return (
<BarChart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle}>
</BarChart>
);
} else if( props.chartForm == CHART_FORM.LINE ){
return (
<LineChart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle}>
</LineChart>
);
} else if( props.chartForm == CHART_FORM.DN ){
return (
<DoughnutChart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle}>
</DoughnutChart>
);
} else if (props.chartForm == CHART_FORM.LINEBAR){
return (
<LineBarchart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle}>
</LineBarchart>
);
}
}
export { MainChart };
// eslint-disable-next-line react-refresh/only-export-components
export { CHART_FORM };
export type { ChartConfig };
@@ -0,0 +1,77 @@
import * as React from "react"
import { cn } from "../../../lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
@@ -0,0 +1,364 @@
"use client"
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import { cn } from "../../../lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
})
ChartContainer.displayName = "Chart"
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref
) => {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
)
ChartTooltipContent.displayName = "ChartTooltip"
const ChartLegend = RechartsPrimitive.Legend
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref
) => {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
)
ChartLegendContent.displayName = "ChartLegend"
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer, ChartLegend,
ChartLegendContent,
ChartStyle, ChartTooltip,
ChartTooltipContent
}
@@ -0,0 +1,7 @@
@tailwind components;
@layer components {
.date-picker-trigger {
@apply h-[3rem] w-[12rem] justify-between border-input !bg-background px-4 py-2 text-left text-[1.3rem] font-normal leading-none text-muted-foreground hover:!bg-accent;
}
}
@@ -0,0 +1,48 @@
"use client";
import "./date-picker.css";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import type { DayPickerSingleProps } from "react-day-picker";
import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from "@common/components/ui";
import { cn } from "../../lib/utils";
import { FormControl } from "../form";
type DatePickerProps = DayPickerSingleProps & { field: { value: Date; onChange: (date: Date | undefined) => void } ,selectClass:string|undefined };
export function DatePicker({ field, selectClass }: DatePickerProps) {
return (
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
color="white"
variant={"outline"}
className={!selectClass? cn(
"w-[240px] pl-3 text-left font-normal",
!field?.value && "text-muted-foreground",
):selectClass}
>
{field?.value ? (
format(field?.value, "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="h-4" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field?.value}
onSelect={field?.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
);
}
@@ -0,0 +1,46 @@
"use client";
import { CloudUpload, Paperclip } from "lucide-react";
import { FileInput, FileUploader, FileUploaderContent, FileUploaderItem } from "./lib/file_upload";
const dropZoneConfig = {
maxFiles: 5,
maxSize: 1024 * 1024 * 4,
multiple: true,
};
export const FileUpload =
(({ ...props }) => {
return (
<FileUploader
value={props.files}
onValueChange={props.setFiles}
dropzoneOptions={dropZoneConfig}
acceptName={props?.fileTypes}
className="relative bg-background rounded-lg p-2"
>
<FileInput
id="fileInput"
className="outline-dashed outline-1 outline-slate-500"
>
<div className="flex items-center justify-center flex-col p-8 w-full ">
<CloudUpload className='text-gray-500 w-10 h-10' />
<p className="mb-1 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Click to upload</span>
&nbsp; or drag and drop
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{!props?.fileTypes?"SVG, PNG, JPG, GIF":props?.fileTypes}
</p>
</div>
</FileInput>
<FileUploaderContent>
{props.files &&
props.files.length > 0 &&
props.files.map((file:File, i:number) => (
<FileUploaderItem key={i} index={i}>
<Paperclip className="h-4 w-4 stroke-current" />
<span>{file.name}</span>
</FileUploaderItem>
))}
</FileUploaderContent>
</FileUploader>
)
})
@@ -0,0 +1,364 @@
"use client";
import { Input, buttonVariants } from "@common/components";
import { Trash2 as RemoveIcon } from "lucide-react";
import {
type Dispatch,
type SetStateAction,
createContext,
forwardRef,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import {
type DropzoneOptions,
type DropzoneState,
type FileRejection,
useDropzone,
} from "react-dropzone";
import { toast } from "sonner";
import { cn } from "../../../lib/utils";
type DirectionOptions = "rtl" | "ltr" | undefined;
type FileUploaderContextType = {
dropzoneState: DropzoneState;
isLOF: boolean;
isFileTooBig: boolean;
removeFileFromSet: (index: number) => void;
activeIndex: number;
setActiveIndex: Dispatch<SetStateAction<number>>;
orientation: "horizontal" | "vertical";
direction: DirectionOptions;
};
const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
export const useFileUpload = () => {
const context = useContext(FileUploaderContext);
if (!context) {
throw new Error("useFileUpload must be used within a FileUploaderProvider");
}
return context;
};
type FileUploaderProps = {
value: File[] | null;
reSelect?: boolean;
onValueChange: (value: File[] | null) => void;
dropzoneOptions: DropzoneOptions;
orientation?: "horizontal" | "vertical";
};
/**
* File upload Docs: {@link: https://localhost:3000/docs/file-upload}
*/
export const FileUploader = forwardRef<
HTMLDivElement,
FileUploaderProps & React.HTMLAttributes<HTMLDivElement> & {acceptName?: string|undefined}
>(
(
{
className,
dropzoneOptions,
value,
onValueChange,
reSelect,
orientation = "vertical",
children,
dir,
acceptName,
...props
},
ref,
) => {
const [isFileTooBig, setIsFileTooBig] = useState(false);
const [isLOF, setIsLOF] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const {
accept = {
"image/*": acceptName?acceptName.split(",").map(extend=>("."+extend)):[".jpg", ".jpeg", ".png", ".gif"],
},
maxFiles = 1,
maxSize = 4 * 1024 * 1024,
multiple = true,
} = dropzoneOptions;
const reSelectAll = maxFiles === 1 ? true : reSelect;
const direction: DirectionOptions = dir === "rtl" ? "rtl" : "ltr";
const removeFileFromSet = useCallback(
(i: number) => {
if (!value) return;
const newFiles = value.filter((_, index) => index !== i);
onValueChange(newFiles);
},
[value, onValueChange],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
if (!value) return;
const moveNext = () => {
const nextIndex = activeIndex + 1;
setActiveIndex(nextIndex > value.length - 1 ? 0 : nextIndex);
};
const movePrev = () => {
const nextIndex = activeIndex - 1;
setActiveIndex(nextIndex < 0 ? value.length - 1 : nextIndex);
};
const prevKey =
orientation === "horizontal"
? direction === "ltr"
? "ArrowLeft"
: "ArrowRight"
: "ArrowUp";
const nextKey =
orientation === "horizontal"
? direction === "ltr"
? "ArrowRight"
: "ArrowLeft"
: "ArrowDown";
if (e.key === nextKey) {
moveNext();
} else if (e.key === prevKey) {
movePrev();
} else if (e.key === "Enter" || e.key === "Space") {
if (activeIndex === -1) {
dropzoneState.inputRef.current?.click();
}
} else if (e.key === "Delete" || e.key === "Backspace") {
if (activeIndex !== -1) {
removeFileFromSet(activeIndex);
if (value.length - 1 === 0) {
setActiveIndex(-1);
return;
}
movePrev();
}
} else if (e.key === "Escape") {
setActiveIndex(-1);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[value, activeIndex, removeFileFromSet],
);
const onDrop = useCallback(
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
const files = acceptedFiles;
if (!files) {
toast.error("file error , probably too big");
return;
}
const newValues: File[] = value ? [...value] : [];
if (reSelectAll) {
newValues.splice(0, newValues.length);
}
files.forEach((file) => {
if (newValues.length < maxFiles) {
newValues.push(file);
}
});
onValueChange(newValues);
if (rejectedFiles?.length > 0) {
for (let i = 0; i < rejectedFiles?.length; i++) {
if (rejectedFiles[i]?.errors[0]?.code === "file-too-large") {
toast.error(
`File is too large. Max size is ${maxSize / 1024 / 1024}MB`,
);
break;
}
if (rejectedFiles[i]?.errors[0]?.message) {
toast.error(rejectedFiles[i]?.errors[0]?.message);
break;
}
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[reSelectAll, value],
);
useEffect(() => {
if (!value) return;
if (value.length === maxFiles) {
setIsLOF(true);
return;
}
setIsLOF(false);
}, [value, maxFiles]);
const opts = dropzoneOptions
? dropzoneOptions
: { accept, maxFiles, maxSize, multiple };
const dropzoneState = useDropzone({
...opts,
onDrop,
onDropRejected: () => setIsFileTooBig(true),
onDropAccepted: () => setIsFileTooBig(false),
});
return (
<FileUploaderContext.Provider
value={{
dropzoneState,
isLOF,
isFileTooBig,
removeFileFromSet,
activeIndex,
setActiveIndex,
orientation,
direction,
}}
>
<div
ref={ref}
tabIndex={0}
onKeyDownCapture={handleKeyDown}
className={cn(
"grid w-full focus:outline-none overflow-hidden ",
className,
{
"gap-2": value && value.length > 0,
},
)}
dir={dir}
{...props}
>
{children}
</div>
</FileUploaderContext.Provider>
);
},
);
FileUploader.displayName = "FileUploader";
export const FileUploaderContent = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ children, className, ...props }, ref) => {
const { orientation } = useFileUpload();
const containerRef = useRef<HTMLDivElement>(null);
return (
<div
className={cn("w-full px-1")}
ref={containerRef}
aria-description="content file holder"
>
<div
{...props}
ref={ref}
className={cn(
"flex rounded-xl gap-1",
orientation === "horizontal" ? "flex-raw flex-wrap" : "flex-col",
className,
)}
>
{children}
</div>
</div>
);
});
FileUploaderContent.displayName = "FileUploaderContent";
export const FileUploaderItem = forwardRef<
HTMLDivElement,
{ index: number } & React.HTMLAttributes<HTMLDivElement>
>(({ className, index, children, ...props }, ref) => {
const { removeFileFromSet, activeIndex, direction } = useFileUpload();
const isSelected = index === activeIndex;
return (
<div
ref={ref}
className={cn(
buttonVariants({ variant: "ghost" }),
"h-6 p-1 justify-between cursor-pointer relative",
className,
isSelected ? "bg-muted" : "",
)}
{...props}
>
<div className="font-medium leading-none tracking-tight flex items-center gap-1.5 h-full w-full">
{children}
</div>
<button
type="button"
className={cn(
"absolute",
direction === "rtl" ? "top-1 left-1" : "top-1 right-1",
)}
onClick={() => removeFileFromSet(index)}
>
<span className="sr-only">remove item {index}</span>
<RemoveIcon className="w-4 h-4 hover:stroke-destructive duration-200 ease-in-out" />
</button>
</div>
);
});
FileUploaderItem.displayName = "FileUploaderItem";
export const FileInput = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, children, ...props }, ref) => {
const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();
const rootProps = isLOF ? {} : dropzoneState.getRootProps();
return (
<div
ref={ref}
{...props}
className={`relative w-full ${
isLOF ? "opacity-50 cursor-not-allowed " : "cursor-pointer "
}`}
>
<div
className={cn(
`w-full rounded-lg duration-300 ease-in-out
${
dropzoneState.isDragAccept
? "border-green-500"
: dropzoneState.isDragReject || isFileTooBig
? "border-red-500"
: "border-gray-300"
}`,
className,
)}
{...rootProps}
>
{children}
</div>
<Input
ref={dropzoneState.inputRef}
disabled={isLOF}
{...dropzoneState.getInputProps()}
className={`${isLOF ? "cursor-not-allowed" : ""}`}
/>
</div>
);
});
FileInput.displayName = "FileInput";
@@ -0,0 +1,174 @@
"use client"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import * as React from "react"
import {
Controller,
type ControllerProps,
type FieldPath,
type FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "../../lib/utils"
import { Label } from "./lib/label"
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-sm 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-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
Form, FormControl,
FormDescription, FormField, FormItem,
FormLabel, FormMessage, useFormField
}
@@ -0,0 +1,58 @@
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
@@ -0,0 +1,174 @@
"use client"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import * as React from "react"
import {
Controller,
type ControllerProps,
type FieldPath,
type FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "../../../lib/utils"
import { Label } from "../lib/label"
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-sm 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-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
Form, FormControl,
FormDescription, FormField, FormItem,
FormLabel, FormMessage, useFormField
}
@@ -0,0 +1,26 @@
"use client"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { cn } from "../../../lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
@@ -0,0 +1,56 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./lib/form"
import { Input } from "@common/components/ui"
import { UiForm } from "."
export default function Test (){
const formSchema= z.object({
username: z.string().min(2).max(50),
})
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
})
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values)
}
const formField = ()=>{
return(
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
) }
const makeFromField= formField();
return (
<div>
<UiForm form={form} onSubmit ={onSubmit} formField={makeFromField} ></UIForm>
</div>
)
}
@@ -0,0 +1,19 @@
import React from "react";
import { FormControl, FormItem, FormLabel, FormMessage } from "../form";
const FormUi = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {label:string|undefined}
>(({children, label},ref)=>{
return (
<FormItem ref={ref}>
{label?<FormLabel className="text-xl">{label}</FormLabel>:<></>}
<FormControl>
{children}
</FormControl>
<FormMessage />
</FormItem>
)
})
FormUi.displayName = 'FormUi';
export { FormUi };
@@ -0,0 +1,12 @@
export * from "./chart";
export * from "./date_picker";
export * from "./file-upload";
export * from "./form";
export * from "./form_ui";
export * from "./scroll";
export * from "./search_form";
export * from "./search_input";
export * from "./search_input_select";
export * from "./select";
export * from "./table";
@@ -0,0 +1,46 @@
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import * as React from "react"
import { cn } from "../../lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
@@ -0,0 +1,9 @@
function SearchDiv ({children}:{ children: React.ReactNode; }) {
return (
<div className="flex border border-black m-4 rounded-lg min-h-[80px]">
{children}
</div>
)
}
export { SearchDiv };
@@ -0,0 +1,15 @@
import { Input, Label } from "@common/components";
import React from "react";
const SearchInput = React.forwardRef<
HTMLDivElement& HTMLInputElement,
React.HTMLAttributes<HTMLDivElement> & {className:string, label:string}>
(({className, label}, ref) => {
return (
<div className={className+" flex"} >
{label && <Label className="m-4 text-2xl w-1/4">{label}</Label>}
<Input className="ml-8 w-3/4 text-lg" ref={ref}></Input>
</div>
)
});
export { SearchInput };
@@ -0,0 +1,41 @@
import { Input, Label } from "@common/components";
import React from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/lib/selectBox";
export type SelectDataType = {
text: string;
val: string;
}
/**
* 검색 입력 및 선택 컴포넌트
* @param className - string tailwindcss class
* @param label - string label
* @param selectData - SelectDataType[] 선택 데이터
* @param onSelectValue - (val:string)=>void 선택 값 콜백
* @param refs - React.forwardRef<HTMLInputElement> | null 입력값 ref
* @returns React.FC
*/
const SearchInputSelect = React.forwardRef<HTMLInputElement, {className:string, label:string, selectData: SelectDataType[], onSelectValue:(val:string)=>void, textValue:string, setText:(val:string)=>void}>(({className, label, selectData, onSelectValue, textValue, setText}) => {
const changeText=(e:React.ChangeEvent<HTMLInputElement>)=>{
if(e?.target?.value) setText(e.target.value)
}
return (
<div className={className+" flex items-center"} >
{label && <Label className="m-4 text-2xl w-1/4">{label}</Label>}
<div className="flex w-full items-center">
<Input className="w-2/3 text-lg" value={textValue} onChange={changeText} />
<Select onValueChange={onSelectValue}>
<SelectTrigger className="ml-2 w-1/3 text-lg" >
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
</SelectTrigger>
{/* <SelectContent ref={refs ? (refs as unknown as RefType)?.selectValue : null}> */}
<SelectContent >
{selectData.map((selectArray) => (
<SelectItem key={selectArray.val} value={selectArray.val}>{selectArray.text}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
)
});
export { SearchInputSelect };
@@ -0,0 +1,35 @@
import { Label } from "@common/components";
import React from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./lib/selectBox";
/** 차트 기본 스타일 */
interface SelectBox1 {
text:string,
val:string
}
const SelectBox = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {label:string, selectClass:string|undefined, className:string, selectArray:SelectBox1[], onSelectValue:(val:string)=>void}
>(({ label, className, selectClass, selectArray, onSelectValue }, ref)=>{
const placeHolder = selectArray?.[0]?.text ?? "";
return (
<div className={className+" flex items-center"} ref={ref}>
{label && <Label className="m-4 text-2xl w-1/4">{label}</Label>}
<Select onValueChange={onSelectValue}>
<SelectTrigger className={selectClass?selectClass:"ml-8 w-3/4 text-2xl"}>
<SelectValue placeholder={placeHolder} />
</SelectTrigger>
<SelectContent>
{selectArray.map((selectArray) => (
<SelectItem key={selectArray.val} value={selectArray.val}>{selectArray.text}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)
})
export { SelectBox };
@@ -0,0 +1,158 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "../../../lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
@@ -0,0 +1,47 @@
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
import UiPageNations from "./uiPageNations";
type UiTableProps = {
headName: React.ReactNode | undefined;
tableData: { [key: string]: string | number | boolean }[];
headlist: UiHeadProps[];
pageSize: number;
total: number;
children: React.ReactNode | undefined;
cellClick : (index:number)=>void | undefined;
}
type UiHeadProps = {
id: string;
title: string;
}
export function UiTable({ tableData, headlist, pageSize, total, children, headName, cellClick }: UiTableProps) {
const cur = 1;
return (
<>
{headName ? headName : <></>}
<Table>
<TableHeader>
{children ? <>{children}</> : <TableRow>
{headlist?.map((head) => (
<TableHead key={head.id}>{head.title}</TableHead>
))}
</TableRow>}
</TableHeader>
<TableBody className='text-center'>
{tableData?.map((el: { [key: string]: string | number | boolean }, index) => (
<TableRow key={index + "row"} >
{headlist.map((head) => (
<TableCell onClick={()=>cellClick(index)} key={head.id + index}>{el[head.id]}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
{total <= 0 ? <></> : <UiPageNations total={total} cur={cur} pageSize={pageSize} />}
</>
);
}
@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "../../../lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
@@ -0,0 +1,117 @@
import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "../../../lib/utils"
import { type ButtonProps, buttonVariants } from "./button"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
Pagination.displayName = "Pagination"
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
))
PaginationContent.displayName = "PaginationContent"
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">
const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
)
PaginationLink.displayName = "PaginationLink"
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
}
@@ -0,0 +1,117 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
@@ -0,0 +1,46 @@
'use client'
import { useEffect, useState } from "react";
import { Pagination, PaginationContent, PaginationItem, PaginationPrevious, PaginationLink, PaginationNext } from "./lib/pagination";
interface CommonPageNationsProps {
total: number;
cur: number;
pageSize: number;
}
export default function UiPageNations(commonPageNationsProps: CommonPageNationsProps) {
const [pageProps, setPageProps] = useState(commonPageNationsProps);
useEffect(() => {
setPageProps(commonPageNationsProps);
} ,[commonPageNationsProps]);
return(
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
{
Array.from({length:Math.ceil(commonPageNationsProps.total/commonPageNationsProps.pageSize)},(_,i)=>i+1).map((el,index)=>(
<PaginationItem key={index}>
<PaginationLink href= '#'>{el}</PaginationLink>
</PaginationItem>
))
}
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
@@ -0,0 +1,93 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class", "[data-mode='dark']"],
content: ["./src/**/*.{ts,tsx,js,jsx}", "./.storybook/**/*.{ts,tsx,js,jsx}"],
theme: {
extend: {
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
card: {
DEFAULT: "var(--card)",
foreground: "var(--card-foreground)",
},
popover: {
DEFAULT: "var(--popover)",
foreground: "var(--popover-foreground)",
},
primary: {
DEFAULT: "var(--primary)",
foreground: "var(--primary-foreground)",
},
secondary: {
DEFAULT: "var(--secondary)",
foreground: "var(--secondary-foreground)",
},
muted: {
DEFAULT: "var(--muted)",
foreground: "var(--muted-foreground)",
},
accent: {
DEFAULT: "var(--accent)",
foreground: "var(--accent-foreground)",
},
destructive: {
DEFAULT: "var(--destructive)",
foreground: "var(--destructive-foreground)",
},
border: "var(--border)",
input: "var(--input)",
label: "var(--label)",
ring: "var(--ring)",
chart: {
1: "var(--chart-1)",
2: "var(--chart-2)",
3: "var(--chart-3)",
4: "var(--chart-4)",
5: "var(--chart-5)",
},
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"spin-parent": {
from: { transform: "translate(-50%,-50%) rotate(0deg)" },
to: { transform: "translate(-50%,-50%) rotate(360deg)" },
},
"spin-child": {
from: { transform: "translate(0,0) rotate(0deg)" },
to: { transform: "translate(0,0) rotate(360deg)" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"spin-parent": "spin-parent 6s linear infinite",
"spin-child": "spin-child 1s ease-in-out infinite",
},
backgroundImage: {
"gradient-blue": "linear-gradient(.25turn, #2094fa, #00e5dd)",
"gradient-blue-hover":
"linear-gradient(.25turn, #2298ff, #01dcda, #00e8b4)",
"gradient-green": "linear-gradient(.25turn, #00bad0, #00eb6f)",
"gradient-green-hover": "linear-gradient(.25turn, #00bad0, #12fe81)",
"gradient-red": "linear-gradient(.25turn, #ff487c, #ffc425)",
"gradient-red-hover": "linear-gradient(.25turn, #ff487c, #ffea3a)",
"gradient-purple": "linear-gradient(.25turn, #be30d7, #ff9264)",
"gradient-purple-hover": "linear-gradient(.25turn, #be30d7, #ffaf5a)",
},
},
},
plugins: [require("tailwindcss-animate")],
};
@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"jsx": "react-jsx",
"incremental": false
},
"exclude": [
"**/*.stories.tsx",
"**/*.stories.ts",
"**/*.stories.mdx",
".storybook/**"
]
}
@@ -0,0 +1,34 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"isolatedModules": true,
"jsx": "react-jsx",
"noEmit": false,
"incremental": true,
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@common/assets": ["../assets/dist"],
"@common/components/*": ["../components/src/*"],
"@common/components": ["../components/src/index"]
}
},
"exclude": ["**/node_modules", "**/.*/*", "dist"],
"include": [
".eslintrc.*",
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.cjs",
"**/*.mjs",
"**/*.jsx",
"**/*.json"
]
}
@@ -0,0 +1,26 @@
import path from "path";
import { defineConfig } from "tsup";
export default defineConfig((options) => ({
entry: ["src/index.ts"],
splitting: true,
treeshake: true,
clean: true,
dts: true,
format: ["esm"],
external: ["react", "react-dom"],
platform: "browser",
target: ["es2020", "chrome70", "edge18", "firefox70", "node18"],
tsconfig: path.resolve(__dirname, "tsconfig.build.json"),
sourcemap: !options.watch,
minify: !options.watch,
cssMinify: !options.watch,
cssModules: true,
postcss: true,
esbuildOptions(options) {
options.alias = {
"@/*": path.resolve(__dirname, "src/*"),
"@assets/*": path.resolve(__dirname, "public/assets/*"),
};
},
}));
+4
View File
@@ -0,0 +1,4 @@
declare module "*.mdx" {
let MDXComponent: (props: unknown) => JSX.Element;
export default MDXComponent;
}
+572 -9
View File
@@ -20,6 +20,9 @@ importers:
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@18.3.18)(react@18.3.1)
devDependencies:
'@types/node':
specifier: ^22.13.9
@@ -131,6 +134,67 @@ importers:
specifier: ^5
version: 5.6.3
apps/wr:
dependencies:
'@common/business_components':
specifier: workspace:*
version: link:../../packages/common/business_components
'@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)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
lucide-react:
specifier: ^0.474.0
version: 0.474.0(react@18.3.1)
next:
specifier: 15.1.7
version: 15.1.7(react-dom@18.3.1)(react@18.3.1)
react-hook-form:
specifier: ^7.54.2
version: 7.54.2(react@18.3.1)
react-hot-toast:
specifier: ^2.5.2
version: 2.5.2(react-dom@18.3.1)(react@18.3.1)
tailwind-merge:
specifier: ^3.0.1
version: 3.0.2
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
version: 3.3.0
'@types/webpack':
specifier: ^5.28.5
version: 5.28.5
eslint:
specifier: ^9
version: 9.21.0
eslint-config-next:
specifier: 15.1.7
version: 15.1.7(eslint@9.21.0)(typescript@5.6.3)
postcss:
specifier: ^8
version: 8.5.3
tailwindcss:
specifier: ^3.4.1
version: 3.4.17
typescript:
specifier: ^5
version: 5.6.3
packages/common/assets:
devDependencies:
'@types/node':
@@ -143,6 +207,148 @@ importers:
specifier: ^5.3.3
version: 5.6.3
packages/common/business_components:
dependencies:
'@common/components':
specifier: workspace:*
version: link:../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-scroll-area':
specifier: ^1.2.3
version: 1.2.3(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-select':
specifier: ^2.1.6
version: 2.1.6(@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)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
date-fns:
specifier: ^3.6.0
version: 3.6.0
lucide-react:
specifier: ^0.474.0
version: 0.474.0(react@18.3.1)
path:
specifier: ^0.12.7
version: 0.12.7
react-day-picker:
specifier: ^8.10.1
version: 8.10.1(date-fns@3.6.0)(react@18.3.1)
react-dropzone:
specifier: ^14.3.8
version: 14.3.8(react@18.3.1)
react-hook-form:
specifier: ^7.54.2
version: 7.54.2(react@18.3.1)
recharts:
specifier: ^2.15.1
version: 2.15.1(react-dom@18.3.1)(react@18.3.1)
sonner:
specifier: ^2.0.1
version: 2.0.1(react-dom@18.3.1)(react@18.3.1)
tailwind-merge:
specifier: ^3.0.1
version: 3.0.2
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@eslint/js':
specifier: ^9.17.0
version: 9.21.0
'@storybook/addon-essentials':
specifier: ^8.5.3
version: 8.6.0(@types/react@18.3.18)(storybook@8.6.0)
'@storybook/addon-interactions':
specifier: ^8.5.3
version: 8.6.0(storybook@8.6.0)
'@storybook/addon-onboarding':
specifier: ^8.5.3
version: 8.6.0(storybook@8.6.0)
'@storybook/addon-themes':
specifier: ^8.5.4
version: 8.6.0(storybook@8.6.0)
'@storybook/blocks':
specifier: ^8.5.3
version: 8.6.0(react-dom@18.3.1)(react@18.3.1)(storybook@8.6.0)
'@storybook/manager-api':
specifier: ^8.5.5
version: 8.6.0(storybook@8.6.0)
'@storybook/react':
specifier: ^8.5.3
version: 8.6.0(@storybook/test@8.6.0)(react-dom@18.3.1)(react@18.3.1)(storybook@8.6.0)(typescript@5.6.3)
'@storybook/react-vite':
specifier: ^8.5.3
version: 8.6.0(@storybook/test@8.6.0)(react-dom@18.3.1)(react@18.3.1)(storybook@8.6.0)(typescript@5.6.3)(vite@6.2.0)
'@storybook/test':
specifier: ^8.5.3
version: 8.6.0(storybook@8.6.0)
'@storybook/theming':
specifier: ^8.5.5
version: 8.6.0(storybook@8.6.0)
'@vitejs/plugin-react':
specifier: ^4.3.4
version: 4.3.4(vite@6.2.0)
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.5.3)
eslint:
specifier: ^9.17.0
version: 9.21.0
eslint-plugin-react-hooks:
specifier: ^5.0.0
version: 5.1.0(eslint@9.21.0)
eslint-plugin-react-refresh:
specifier: ^0.4.16
version: 0.4.19(eslint@9.21.0)
eslint-plugin-storybook:
specifier: ^0.11.2
version: 0.11.3(eslint@9.21.0)(typescript@5.6.3)
globals:
specifier: ^15.14.0
version: 15.15.0
postcss:
specifier: ^8.5.1
version: 8.5.3
prettier:
specifier: ^3.4.2
version: 3.5.2
prettier-plugin-tailwindcss:
specifier: ^0.6.11
version: 0.6.11(prettier@3.5.2)
storybook:
specifier: ^8.5.3
version: 8.6.0(prettier@3.5.2)
tailwindcss:
specifier: ^3.4.17
version: 3.4.17
tsup:
specifier: 8.3.6
version: 8.3.6(postcss@8.5.3)(typescript@5.6.3)
typescript:
specifier: ~5.6.2
version: 5.6.3
typescript-eslint:
specifier: ^8.18.2
version: 8.25.0(eslint@9.21.0)(typescript@5.6.3)
vite:
specifier: ^6.0.5
version: 6.2.0(@types/node@22.13.9)
vite-tsconfig-paths:
specifier: 5.1.4
version: 5.1.4(typescript@5.6.3)(vite@6.2.0)
packages/common/components:
dependencies:
'@common/assets':
@@ -1402,7 +1608,7 @@ packages:
magic-string: 0.27.0
react-docgen-typescript: 2.2.2(typescript@5.6.3)
typescript: 5.6.3
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
dev: true
/@jridgewell/gen-mapping@0.3.8:
@@ -2131,6 +2337,34 @@ packages:
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-scroll-area@1.2.3(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==}
peerDependencies:
'@types/react': ^18.3.18
'@types/react-dom': ^18.3.5
react: ^18.3.1
react-dom: ^18.3.1
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
'@radix-ui/react-presence': 1.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-primitive': 2.0.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
'@types/react': 18.3.18
'@types/react-dom': 18.3.5(@types/react@18.3.18)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-select@2.1.6(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==}
peerDependencies:
@@ -2810,7 +3044,7 @@ packages:
browser-assert: 1.2.1
storybook: 8.6.0(prettier@3.5.2)
ts-dedent: 2.2.0
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
dev: true
/@storybook/components@8.6.0(storybook@8.6.0):
@@ -2942,7 +3176,7 @@ packages:
resolve: 1.22.10
storybook: 8.6.0(prettier@3.5.2)
tsconfig-paths: 4.2.0
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
transitivePeerDependencies:
- rollup
- supports-color
@@ -3109,6 +3343,48 @@ packages:
'@babel/types': 7.26.9
dev: true
/@types/d3-array@3.2.1:
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
dev: false
/@types/d3-color@3.1.3:
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
dev: false
/@types/d3-ease@3.0.2:
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
dev: false
/@types/d3-interpolate@3.0.4:
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
dependencies:
'@types/d3-color': 3.1.3
dev: false
/@types/d3-path@3.1.1:
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
dev: false
/@types/d3-scale@4.0.9:
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
dependencies:
'@types/d3-time': 3.0.4
dev: false
/@types/d3-shape@3.1.7:
resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
dependencies:
'@types/d3-path': 3.1.1
dev: false
/@types/d3-time@3.0.4:
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
dev: false
/@types/d3-timer@3.0.2:
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
dev: false
/@types/doctrine@0.0.9:
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
dev: true
@@ -3346,7 +3622,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
transitivePeerDependencies:
- supports-color
dev: true
@@ -3790,6 +4066,11 @@ packages:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/attr-accept@2.2.5:
resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
engines: {node: '>=4'}
dev: false
/autoprefixer@10.4.20(postcss@8.5.3):
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14}
@@ -4129,6 +4410,77 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
/d3-array@3.2.4:
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
engines: {node: '>=12'}
dependencies:
internmap: 2.0.3
dev: false
/d3-color@3.1.0:
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
engines: {node: '>=12'}
dev: false
/d3-ease@3.0.1:
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
engines: {node: '>=12'}
dev: false
/d3-format@3.1.0:
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
engines: {node: '>=12'}
dev: false
/d3-interpolate@3.0.1:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
dependencies:
d3-color: 3.1.0
dev: false
/d3-path@3.1.0:
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
engines: {node: '>=12'}
dev: false
/d3-scale@4.0.2:
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
engines: {node: '>=12'}
dependencies:
d3-array: 3.2.4
d3-format: 3.1.0
d3-interpolate: 3.0.1
d3-time: 3.1.0
d3-time-format: 4.1.0
dev: false
/d3-shape@3.2.0:
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
engines: {node: '>=12'}
dependencies:
d3-path: 3.1.0
dev: false
/d3-time-format@4.1.0:
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
engines: {node: '>=12'}
dependencies:
d3-time: 3.1.0
dev: false
/d3-time@3.1.0:
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
engines: {node: '>=12'}
dependencies:
d3-array: 3.2.4
dev: false
/d3-timer@3.0.1:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
dev: false
/damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
dev: true
@@ -4191,6 +4543,10 @@ packages:
ms: 2.1.3
dev: true
/decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
dev: false
/deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
@@ -4378,6 +4734,13 @@ packages:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
dev: true
/dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
dependencies:
'@babel/runtime': 7.26.9
csstype: 3.1.3
dev: false
/dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -4996,6 +5359,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: false
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
@@ -5004,6 +5371,11 @@ packages:
/fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
/fast-equals@5.2.2:
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
engines: {node: '>=6.0.0'}
dev: false
/fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
engines: {node: '>=8.6.0'}
@@ -5065,6 +5437,13 @@ packages:
flat-cache: 4.0.1
dev: true
/file-selector@2.1.2:
resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
engines: {node: '>= 12'}
dependencies:
tslib: 2.8.1
dev: false
/fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -5310,6 +5689,14 @@ packages:
minimist: 1.2.8
dev: true
/goober@2.1.16(csstype@3.1.3):
resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==}
peerDependencies:
csstype: ^3.0.10
dependencies:
csstype: 3.1.3
dev: false
/gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -5399,6 +5786,10 @@ packages:
engines: {node: '>=8'}
dev: true
/inherits@2.0.3:
resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
dev: false
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
@@ -5412,6 +5803,11 @@ packages:
side-channel: 1.1.0
dev: true
/internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
dev: false
/is-arguments@1.2.0:
resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
engines: {node: '>= 0.4'}
@@ -5842,7 +6238,6 @@ packages:
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
@@ -6217,6 +6612,13 @@ packages:
engines: {node: '>=18'}
dev: true
/path@0.12.7:
resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==}
dependencies:
process: 0.11.10
util: 0.10.4
dev: false
/pathval@2.0.0:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
@@ -6483,7 +6885,6 @@ packages:
/process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
dev: true
/prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -6491,7 +6892,6 @@ packages:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
dev: true
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
@@ -6608,6 +7008,18 @@ packages:
react: 18.3.1
scheduler: 0.23.2
/react-dropzone@14.3.8(react@18.3.1):
resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==}
engines: {node: '>= 10.13'}
peerDependencies:
react: ^18.3.1
dependencies:
attr-accept: 2.2.5
file-selector: 2.1.2
prop-types: 15.8.1
react: 18.3.1
dev: false
/react-hook-form@7.54.2(react@18.3.1):
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
engines: {node: '>=18.0.0'}
@@ -6617,6 +7029,19 @@ packages:
react: 18.3.1
dev: false
/react-hot-toast@2.5.2(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==}
engines: {node: '>=10'}
peerDependencies:
react: ^18.3.1
react-dom: ^18.3.1
dependencies:
csstype: 3.1.3
goober: 2.1.16(csstype@3.1.3)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -6624,6 +7049,10 @@ packages:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
dev: true
/react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
dev: false
/react-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
@@ -6664,6 +7093,19 @@ packages:
use-sidecar: 1.1.3(@types/react@18.3.18)(react@18.3.1)
dev: false
/react-smooth@4.0.4(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
peerDependencies:
react: ^18.3.1
react-dom: ^18.3.1
dependencies:
fast-equals: 5.2.2
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1)
dev: false
/react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1):
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -6680,6 +7122,20 @@ packages:
tslib: 2.8.1
dev: false
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
react: ^18.3.1
react-dom: ^18.3.1
dependencies:
'@babel/runtime': 7.26.9
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/react-use-measure@2.1.7(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==}
peerDependencies:
@@ -6726,6 +7182,31 @@ packages:
tslib: 2.8.1
dev: true
/recharts-scale@0.4.5:
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
dependencies:
decimal.js-light: 2.5.1
dev: false
/recharts@2.15.1(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==}
engines: {node: '>=14'}
peerDependencies:
react: ^18.3.1
react-dom: ^18.3.1
dependencies:
clsx: 2.1.1
eventemitter3: 4.0.7
lodash: 4.17.21
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-is: 18.3.1
react-smooth: 4.0.4(react-dom@18.3.1)(react@18.3.1)
recharts-scale: 0.4.5
tiny-invariant: 1.3.3
victory-vendor: 36.9.2
dev: false
/redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -7045,6 +7526,16 @@ packages:
engines: {node: '>=14.16'}
dev: true
/sonner@2.0.1(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-FRBphaehZ5tLdLcQ8g2WOIRE+Y7BCfWi5Zyd8bCvBjiW8TxxAyoWZIxS661Yz6TGPqFQ4VLzOF89WEYhfynSFQ==}
peerDependencies:
react: ^18.3.1
react-dom: ^18.3.1
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -7382,7 +7873,6 @@ packages:
/tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
dev: true
/tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@@ -7753,6 +8243,12 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
/util@0.10.4:
resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==}
dependencies:
inherits: 2.0.3
dev: false
/util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
dependencies:
@@ -7768,6 +8264,25 @@ packages:
hasBin: true
dev: true
/victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
dependencies:
'@types/d3-array': 3.2.1
'@types/d3-ease': 3.0.2
'@types/d3-interpolate': 3.0.4
'@types/d3-scale': 4.0.9
'@types/d3-shape': 3.1.7
'@types/d3-time': 3.0.4
'@types/d3-timer': 3.0.2
d3-array: 3.2.4
d3-ease: 3.0.1
d3-interpolate: 3.0.1
d3-scale: 4.0.2
d3-shape: 3.2.0
d3-time: 3.1.0
d3-timer: 3.0.1
dev: false
/vite-tsconfig-paths@5.1.4(typescript@5.6.3)(vite@6.2.0):
resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
peerDependencies:
@@ -7779,7 +8294,7 @@ packages:
debug: 4.4.0
globrex: 0.1.2
tsconfck: 3.1.5(typescript@5.6.3)
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
transitivePeerDependencies:
- supports-color
- typescript
@@ -7833,6 +8348,54 @@ packages:
fsevents: 2.3.3
dev: true
/vite@6.2.0(@types/node@22.13.9):
resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
dependencies:
'@types/node': 22.13.9
esbuild: 0.25.0
postcss: 8.5.3
rollup: 4.34.8
optionalDependencies:
fsevents: 2.3.3
dev: true
/watchpack@2.4.2:
resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
engines: {node: '>=10.13.0'}