Merge commit 'a5c3405049094e015c7e159e4b1c65a4b851396b' into iipbs/sn/oi
This commit is contained in:
@@ -63,7 +63,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"eslint-plugin-storybook": "^0.11.2",
|
||||
"globals": "^15.14.0",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"storybook": "^8.5.3",
|
||||
|
||||
@@ -1,69 +1,10 @@
|
||||
@import "@common/assets/styles/base.css";
|
||||
@import "@common/assets/styles/diveicon.css";
|
||||
@import "@common/assets/styles/components.css";
|
||||
|
||||
@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;
|
||||
@@ -74,4 +15,4 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
|
||||
@layer base {}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,23 @@ 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";
|
||||
import { ko } from "date-fns/locale";
|
||||
import { useState } from "react";
|
||||
|
||||
type DatePickerProps = DayPickerSingleProps & { field: { value: Date; onChange: (date: Date | undefined) => void } ,selectClass:string|undefined };
|
||||
|
||||
|
||||
export function DatePicker({ field, selectClass }: DatePickerProps) {
|
||||
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleSelect = (date: Date | undefined) => {
|
||||
field.onChange(date); // React Hook Form 업데이트
|
||||
setOpen(false);
|
||||
};
|
||||
return (
|
||||
<Popover>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
@@ -27,7 +38,12 @@ export function DatePicker({ field, selectClass }: DatePickerProps) {
|
||||
):selectClass}
|
||||
>
|
||||
{field?.value ? (
|
||||
format(field?.value, "PPP")
|
||||
format(
|
||||
field.value instanceof Date && !isNaN(field.value.getTime())
|
||||
? field.value
|
||||
: new Date()
|
||||
|
||||
, "PPP", { locale: ko })
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
@@ -39,8 +55,9 @@ export function DatePicker({ field, selectClass }: DatePickerProps) {
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field?.value}
|
||||
onSelect={field?.onChange}
|
||||
onSelect={handleSelect}
|
||||
initialFocus
|
||||
locale={ko}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -1,52 +1,47 @@
|
||||
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@common/components";
|
||||
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTrigger } from "@common/components";
|
||||
import { useState } from "react";
|
||||
|
||||
type ModalType = {
|
||||
bodyClass: string;
|
||||
butNm: string;
|
||||
title: string;
|
||||
body: React.ReactNode;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
noHeaderYn?: 'N' | 'Y';
|
||||
noFooterYn?: 'N' | 'Y';
|
||||
headerTitle?: string;
|
||||
footerContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function UiModal({ bodyClass, butNm, title, body, open, onOpenChange }: ModalType) {
|
||||
const dialogProps = open !== undefined && onOpenChange !== undefined
|
||||
? { open, onOpenChange }
|
||||
: {};
|
||||
|
||||
export function UiModal({
|
||||
bodyClass,
|
||||
body,
|
||||
open,
|
||||
onOpenChange,
|
||||
noHeaderYn = 'N',
|
||||
noFooterYn = 'N',
|
||||
headerTitle,
|
||||
footerContent
|
||||
}: ModalType) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!open && (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="mr-4 rounded-lg">{butNm}</Button>
|
||||
</DialogTrigger>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={bodyClass}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{body}
|
||||
<DialogFooter className="sm:justify-start">
|
||||
</DialogFooter>
|
||||
{noHeaderYn === 'N' && (
|
||||
<DialogHeader>
|
||||
<DialogDescription>
|
||||
{headerTitle}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
)}
|
||||
{(open && body)}
|
||||
{noFooterYn === 'N' && (
|
||||
<DialogFooter className="sm:justify-start">
|
||||
{footerContent}
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
{open && (
|
||||
<Dialog {...dialogProps}>
|
||||
<DialogContent className={bodyClass}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{body}
|
||||
<DialogFooter className="sm:justify-start">
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
function ListDiv(props: {children: React.ReactNode}) {
|
||||
function ListDiv(props: {children: React.ReactNode, className?: string|undefined}) {
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className={`card card-border ${props.className}`}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,13 +3,26 @@ import React from "react";
|
||||
|
||||
const SearchInput = React.forwardRef<HTMLInputElement, {className:string, label:string, textValue:string, setText:(val:string)=>void}>(({className, label, textValue, setText}, ref) => {
|
||||
const changeText=(e:React.ChangeEvent<HTMLInputElement>)=>{
|
||||
if(e?.target?.value) setText(e.target.value)
|
||||
}
|
||||
|
||||
if(e?.target?.value){
|
||||
|
||||
setText(e.target.value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const keyUp=(e:any)=>{
|
||||
if(e.keyCode===8 && e.target.value.length==1){
|
||||
setText('')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className+" flex items-center"} >
|
||||
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
|
||||
<Input className="flex-1 ml-8 w-3/4 h-10 border rounded" value={textValue} onChange={changeText}></Input>
|
||||
<Input className="flex-1 ml-8 w-3/4 h-10 border rounded" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,30 +1,55 @@
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import UiPageNations from "./uiPageNations";
|
||||
|
||||
type UiTableProps = {
|
||||
headName: React.ReactNode | undefined;
|
||||
tableData: { [key: string]: string | number | boolean }[];
|
||||
tableData: { [key: string]: string | number | boolean | object}[];
|
||||
headlist: UiHeadProps[];
|
||||
pageSize: number;
|
||||
total: number;
|
||||
children: React.ReactNode | undefined;
|
||||
cellClick : ((index:number|undefined)=>void) | undefined;
|
||||
cellClick : ((index:number|undefined, headId:string)=>void )| undefined;
|
||||
onPageChange: (page: number) => void;
|
||||
className?: string;
|
||||
resetPageNo?: boolean;
|
||||
}
|
||||
|
||||
type UiHeadProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
align?: string;
|
||||
}
|
||||
|
||||
export function UiTable({ tableData, headlist, pageSize, total, children, headName, cellClick }: UiTableProps) {
|
||||
const cur = 1;
|
||||
export function UiTable({ tableData, headlist, pageSize, total, children, headName, cellClick, onPageChange, className = '', resetPageNo = false }: UiTableProps) {
|
||||
const [cur, setCur] = useState<number>(1);
|
||||
const [idx, setIdx] = useState<number>(-1);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetPageNo) {
|
||||
setCur(1);
|
||||
onPageChange(1);
|
||||
}
|
||||
}, [onPageChange, resetPageNo]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCur(page);
|
||||
onPageChange(page);
|
||||
}
|
||||
|
||||
const handleCellClick = (index: number,headId:string )=> {
|
||||
if (cellClick) {
|
||||
cellClick(index,headId);
|
||||
setIdx(index);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{headName ? <div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">{headName}</h3>
|
||||
</div> : <></>}
|
||||
<div className="card !m-[1.2rem] max-h-[40rem] overflow-auto !p-0r">
|
||||
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{children ? <>{children}</> : <TableRow>
|
||||
@@ -34,17 +59,26 @@ export function UiTable({ tableData, headlist, pageSize, total, children, headNa
|
||||
</TableRow>}
|
||||
</TableHeader>
|
||||
<TableBody className='text-center'>
|
||||
{tableData?.map((el: { [key: string]: string | number | boolean }, index) => (
|
||||
<TableRow key={index + "row"} >
|
||||
{headlist.map((head) => (
|
||||
<TableCell className="cursor-pointer" onClick={()=>cellClick && cellClick(index)} key={head.id + index}>{el[head.id]}</TableCell>
|
||||
{tableData?.map((el: { [key: string]: string | number | boolean | object}, index) => (
|
||||
<TableRow key={index + "row"}>
|
||||
{headlist?.map((head) => (
|
||||
<TableCell
|
||||
className={`${head.align === 'right' ? 'text-right' : 'text-center'} ${
|
||||
cellClick && idx === index ? 'opacity-40 cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={() => handleCellClick(index, head.id)}
|
||||
key={head.id + index}
|
||||
>
|
||||
{String(el[head.id] ?? '')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{total <= 0 ? <></> : <UiPageNations total={total} cur={cur} pageSize={pageSize} />}
|
||||
{total <= 0 ? <></> : <UiPageNations total={total} cur={cur} pageSize={pageSize} onPageChange={handlePageChange} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,77 @@
|
||||
'use client'
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pagination, PaginationContent, PaginationItem, PaginationPrevious, PaginationLink, PaginationNext } from "./lib/pagination";
|
||||
|
||||
import { PaginationNext } from "./lib/pagination";
|
||||
import { PaginationLink } from "./lib/pagination";
|
||||
import { PaginationPrevious } from "./lib/pagination";
|
||||
import { PaginationItem } from "./lib/pagination";
|
||||
import { PaginationContent } from "./lib/pagination";
|
||||
import { Pagination } from "./lib/pagination";
|
||||
|
||||
interface CommonPageNationsProps {
|
||||
total: number;
|
||||
cur: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
cur: number;
|
||||
pageSize: number;
|
||||
onPageChange: (page: number) => void;
|
||||
}
|
||||
|
||||
export default function UiPageNations(commonPageNationsProps: CommonPageNationsProps) {
|
||||
|
||||
const [pageProps, setPageProps] = useState(commonPageNationsProps);
|
||||
const [pageProps, setPageProps] = useState(commonPageNationsProps);
|
||||
|
||||
useEffect(() => {
|
||||
setPageProps(commonPageNationsProps);
|
||||
}, [commonPageNationsProps]);
|
||||
|
||||
useEffect(() => {
|
||||
setPageProps(commonPageNationsProps);
|
||||
} ,[commonPageNationsProps]);
|
||||
const totalPages = Math.ceil(pageProps.total / pageProps.pageSize);
|
||||
const pagesPerGroup = 10;
|
||||
const currentGroup = Math.floor((pageProps.cur - 1) / pagesPerGroup);
|
||||
const startPage = currentGroup * pagesPerGroup + 1;
|
||||
const endPage = Math.min(startPage + pagesPerGroup - 1, totalPages);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
pageProps.onPageChange(page);
|
||||
};
|
||||
|
||||
return(
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious href="#" />
|
||||
</PaginationItem>
|
||||
return (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
|
||||
{/* 이전 그룹으로 이동 */}
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
href="#"
|
||||
onClick={() => {
|
||||
if (startPage > 1) handlePageChange(startPage - 1);
|
||||
}}
|
||||
/>
|
||||
</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="#" />
|
||||
Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i).map((pageNum) => (
|
||||
<PaginationItem key={pageNum}>
|
||||
<PaginationLink
|
||||
href="#"
|
||||
isActive={pageNum === pageProps.cur}
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
>
|
||||
{pageNum}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
|
||||
|
||||
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
);
|
||||
))
|
||||
}
|
||||
|
||||
{/* 다음 그룹으로 이동 */}
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
href="#"
|
||||
onClick={() => {
|
||||
if (endPage < totalPages) handlePageChange(endPage + 1);
|
||||
}}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
@@ -1,93 +1,8 @@
|
||||
import presetConfig from "../../../tailwind.config.preset";
|
||||
|
||||
/** @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)",
|
||||
},
|
||||
},
|
||||
},
|
||||
presets: [presetConfig],
|
||||
content: ["./src/**/*.{ts,tsx,js,jsx}"],
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user