Merge branch 'iipbs_common' into iipbs_wr-dev

This commit is contained in:
leedj312
2025-02-28 17:04:04 +09:00
15 changed files with 712 additions and 87 deletions
@@ -0,0 +1,22 @@
## business_components 패키지
### 원앤올에서 제공받는 컴포넌트 외에 업무 파트에서 사용할 수 있는 별도 components 패키지지
  1) 설정
    (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"]
}
```
    (2) package.json 'dependencies'에 '@common/business_components' 추가
```json
"dependencies": {
"@common/business_components": "workspace:*",
"next": "15.1.7"
},
```
@@ -26,10 +26,17 @@
},
"dependencies": {
"@common/components": "workspace:*",
"@hookform/resolvers": "^4.1.2",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.474.0",
"path": "^0.12.7",
"react-hook-form": "^7.54.2",
"recharts": "^2.15.1",
"tailwind-merge": "^3.0.1"
"tailwind-merge": "^3.0.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
@@ -0,0 +1,54 @@
## Chart 모듈
### 공통 모듈 중 Chart를 다양한 테마로 적용시킬 수 있도록 해주는 모듈
  1. 차트 모듈 사용법
    1-1) 호출방법
      (1) 파일에 차트생성에 필요한 form, MainChart, MainConfig 임폴트
```js
import {type ChartConfig, MainChart, CHART_FORM } from "@common/business_components";
```
      (2) ChartForm 지정
```js
/**
* CHART_FORM.BAR : Bar차트
* CHART_FORM.LINE : Line차트
* DN : Daughnut차트
**/
const chartForm = CHART_FORM.BAR;
```
      (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},
]
```
      (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>
);
```
@@ -0,0 +1,11 @@
@layer base {}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
@@ -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,176 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
type ControllerProps,
type FieldPath,
type FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/ui/form/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 {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
@@ -0,0 +1,24 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
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 }
@@ -1 +1,3 @@
export * from "./chart";
export * from "./table";
export * from "./form";
@@ -0,0 +1,47 @@
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@common/components";
import UiPageNations from "./uiPageNations";
interface UiTableProps {
publicReuseFacility: any[];
headlist: { id: string; title: string; }[];
pageSize: number;
total: number;
}
export function UiTable(UiTableProps: UiTableProps) {
const { publicReuseFacility, headlist,pageSize , total} = UiTableProps;
let cur = 1;
return (
<>
<p>공공하수처리시설 목록</p>
<Table>
<TableHeader>
<TableRow>
{headlist.map((head) => (
<TableHead key={head.id}>{head.title}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{publicReuseFacility.map((el: any,index) => (
<TableRow key={index+"row"} >
{headlist.map((head) => (
<TableCell key={head.id+index}>{el[head.id]}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
<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,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>
);
}
@@ -24,7 +24,7 @@ const buttonVariants = cva(
size: {
default: "h-[3.2rem] px-10",
xs: "h-8 px-2 !text-[1.1rem]",
sm: "h-10 px-4",
sm: "h-10 px-4 !text-[1.2rem]",
lg: "h-16 px-12",
icon: "h-10 w-10",
},
@@ -1,5 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import { createColumnHelper } from "@tanstack/react-table";
import {
createColumnHelper,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { DataTable } from ".";
const meta: Meta<typeof DataTable> = {
@@ -14,8 +18,6 @@ const meta: Meta<typeof DataTable> = {
},
source: {
code: `
import { createColumnHelper } from "@tanstack/react-table";
type Person = {
firstName: string;
lastName: string;
@@ -25,7 +27,7 @@ type Person = {
progress: number;
};
const defaultData: Person[] = [
const data: Person[] = [
{
firstName: "tanner",
lastName: "linsley",
@@ -78,7 +80,17 @@ const columns = [
}),
];
return <Table data={defaultData} columns={columns} />;
function Table() {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return <DataTable table={table} />;
}
`,
},
},
@@ -89,69 +101,77 @@ export default meta;
type Story = StoryObj<typeof DataTable>;
export const Default: Story = {
render: () => {
type Person = {
firstName: string;
lastName: string;
age: number;
visits: number;
status: string;
progress: number;
};
const defaultData: Person[] = [
{
firstName: "tanner",
lastName: "linsley",
age: 24,
visits: 100,
status: "In Relationship",
progress: 50,
},
{
firstName: "tandy",
lastName: "miller",
age: 40,
visits: 40,
status: "Single",
progress: 80,
},
{
firstName: "joe",
lastName: "dirte",
age: 45,
visits: 20,
status: "Complicated",
progress: 10,
},
];
const columnHelper = createColumnHelper<Person>();
const columns = [
columnHelper.accessor("firstName", {
cell: (info) => info.getValue(),
}),
columnHelper.accessor((row) => row.lastName, {
id: "lastName",
cell: (info) => <i>{info.getValue()}</i>,
header: () => <span>Last Name</span>,
}),
columnHelper.accessor("age", {
header: () => "Age",
cell: (info) => info.renderValue(),
}),
columnHelper.accessor("visits", {
header: () => <span>Visits</span>,
}),
columnHelper.accessor("status", {
header: "Status",
}),
columnHelper.accessor("progress", {
header: "Profile Progress",
}),
];
return <DataTable data={defaultData} columns={columns} />;
},
render: () => <Table />,
};
type Person = {
firstName: string;
lastName: string;
age: number;
visits: number;
status: string;
progress: number;
};
const data: Person[] = [
{
firstName: "tanner",
lastName: "linsley",
age: 24,
visits: 100,
status: "In Relationship",
progress: 50,
},
{
firstName: "tandy",
lastName: "miller",
age: 40,
visits: 40,
status: "Single",
progress: 80,
},
{
firstName: "joe",
lastName: "dirte",
age: 45,
visits: 20,
status: "Complicated",
progress: 10,
},
];
const columnHelper = createColumnHelper<Person>();
const columns = [
columnHelper.accessor("firstName", {
cell: (info) => info.getValue(),
}),
columnHelper.accessor((row) => row.lastName, {
id: "lastName",
cell: (info) => <i>{info.getValue()}</i>,
header: () => <span>Last Name</span>,
}),
columnHelper.accessor("age", {
header: () => "Age",
cell: (info) => info.renderValue(),
}),
columnHelper.accessor("visits", {
header: () => <span>Visits</span>,
}),
columnHelper.accessor("status", {
header: "Status",
}),
columnHelper.accessor("progress", {
header: "Profile Progress",
}),
];
function Table() {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return <DataTable table={table} />;
}
@@ -1,8 +1,5 @@
import {
flexRender,
getCoreRowModel,
useReactTable,
type ColumnDef,
type RowData,
type Table as TableType,
} from "@tanstack/react-table";
@@ -39,19 +36,9 @@ function THead<T>({ table }: { table: TableType<T> }) {
);
}
interface TableProps<T> {
data: T[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
columns: ColumnDef<T, any>[];
}
function DataTable<T>({ data, columns }: TableProps<T>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
type TableProps<T> = { table: TableType<T> };
function DataTable<T>({ table }: TableProps<T>) {
return (
<Table className="w-full">
<THead table={table} />