diff --git a/.gitignore b/.gitignore
index d32cc78..5d4a153 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+*.idea
\ No newline at end of file
diff --git a/package.json b/package.json
index f38b46f..2ba0f33 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,10 @@
"*.{ts,tsx,js,jsx}": "eslint"
},
"dependencies": {
+ "@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-label": "^2.1.2",
+ "@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^5.52.2",
"@tanstack/react-query-devtools": "^5.52.2",
"@testing-library/react": "^16.0.1",
diff --git a/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx b/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
index 6823571..69265a5 100644
--- a/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
+++ b/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
@@ -1,3 +1,4 @@
+import { useState } from 'react';
import {
Table,
TableBody,
@@ -6,42 +7,125 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
+import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
interface Invoice {
name: string;
student_id: string;
- admin: boolean;
+ admin?: boolean;
}
interface TableComponentProps {
data: Invoice[];
+ showCheckboxes?: boolean; // 고민이에요... ESLint: propType "handleDelete" is not required, but has no corresponding defaultProps declaration 에러가 뜸
+ headers?: string[];
+ selected: string[];
+ setSelected: (selectedIds: (prev: string[]) => string[]) => void;
+ handleDelete?: (selectedIds: string[]) => void; // 22
}
-export default function TableComponent({ data }: TableComponentProps) {
+export default function TableComponent({
+ data,
+ showCheckboxes = true,
+ headers = ['이름', '학번', '관리자 여부'], // 기본값을 설정
+ selected,
+ setSelected,
+ handleDelete = () => {}, // 기본값으로 빈 함수 설정
+}: TableComponentProps) {
+ const [currentPage, setCurrentPage] = useState(1);
+ const rowsPerPage = 10;
+
+ const handleSelect = (student_id: string) => {
+ setSelected((prev: string[]) =>
+ prev.includes(student_id)
+ ? prev.filter((id) => id !== student_id)
+ : [...prev, student_id],
+ );
+ };
+
+ const paginatedData = data.slice(
+ (currentPage - 1) * rowsPerPage,
+ currentPage * rowsPerPage,
+ );
+
+ const handleSelectAll = () => {
+ const visibleIds = paginatedData.map((item) => item.student_id);
+ setSelected((prev: string[]) =>
+ prev.length === visibleIds.length ? [] : visibleIds,
+ );
+ };
+
return (
-
+
- 이름
- 학번
- 관리자 여부
+ {showCheckboxes && (
+
+
+
+ )}
+ {headers.map((header) => (
+
+ {header}
+
+ ))}
- {data.map((item, index) => (
-
+ {paginatedData.map((item) => (
+
+ {showCheckboxes && (
+
+ handleSelect(item.student_id)}
+ />
+
+ )}
{item.name}
{item.student_id}
-
- {item.admin ? '⭕' : '❌'}
-
+ {headers.includes('관리자 여부') && (
+
+ {item.admin !== undefined && (item.admin ? 'o' : 'x')}
+
+ )}
))}
+
+
+
+ {currentPage} / {Math.ceil(data.length / rowsPerPage)}
+
+
+
);
}
+
+// defaultProps를 사용하여 headers에 기본값 설정
+TableComponent.defaultProps = {
+ headers: ['이름', '학번', '관리자 여부'],
+};
diff --git a/src/app/desktop/payer-inquiry/page.tsx b/src/app/desktop/payer-inquiry/page.tsx
index d52ba72..3370367 100644
--- a/src/app/desktop/payer-inquiry/page.tsx
+++ b/src/app/desktop/payer-inquiry/page.tsx
@@ -1,30 +1,193 @@
-import Sidebar from '@/components/desktop/SideBar';
+'use client';
+
+import Sidebar from 'src/components/desktop/Sidebar';
import Search from '@/components/desktop/Search';
+import { useState } from 'react';
+import AddStudentId from '@/components/desktop/AddStudentId';
+import { Button } from '@/components/ui/button';
import TableComponent from './_components/TableComponent';
+import AddInput from '../../../components/desktop/AddInput';
const dummyData = [
{ name: '조다운', student_id: '20223139', admin: true },
- { name: '조다운', student_id: '20223139', admin: true },
- { name: '조다운', student_id: '20223139', admin: false },
+ { name: '이정욱', student_id: '20223888', admin: true },
+ { name: '윤신지', student_id: '20223122', admin: false },
+ { name: '황수민', student_id: '20223130', admin: true },
+];
+
+const dummyData2 = [
+ { name: '조다운', student_id: '20223139' },
+ { name: '황현진', student_id: '20223158' },
];
export default function PayerInquiryPage() {
+ const [data, setData] = useState(dummyData); // 기존 데이터
+ const [addedData, setAddedData] = useState(dummyData2); // 추가된 데이터
+ const [isDeleteModeOriginal, setIsDeleteModeOriginal] = useState(false); // 기존 데이터 삭제 모드
+ const [isDeleteModeAdded, setIsDeleteModeAdded] = useState(false); // 추가된 데이터 삭제 모드
+ const [selectedOriginal, setSelectedOriginal] = useState
([]); // 기존 데이터에서 선택된 항목
+ const [selectedAdded, setSelectedAdded] = useState([]); // 추가된 데이터에서 선택된 항목
+
+ const [newStudentId, setNewStudentId] = useState('');
+ const [newStudentName, setNewStudentName] = useState('');
+
+ // 학번 입력 핸들러
+ const handleStudentIdChange = (e: React.ChangeEvent) => {
+ setNewStudentId(e.target.value);
+ };
+
+ // 이름 입력 핸들러
+ const handleStudentNameChange = (e: React.ChangeEvent) => {
+ setNewStudentName(e.target.value);
+ };
+
+ // 추가 버튼 클릭 시 실행될 함수
+ const handleAddStudent = () => {
+ if (!newStudentId || !newStudentName) {
+ alert('이름과 학번을 입력해주세요.');
+ return;
+ }
+
+ // 학번이 8자리 숫자인지 검증
+ const studentIdPattern = /^\d{8}$/;
+ if (!studentIdPattern.test(newStudentId)) {
+ alert('학번은 8자리 숫자로 입력해야 합니다.');
+ return;
+ }
+
+ const newEntry = { name: newStudentName, student_id: newStudentId };
+ setAddedData([...addedData, newEntry]); // 추가된 데이터 업데이트
+ setNewStudentId('');
+ setNewStudentName('');
+ };
+
+ // 기존 데이터 삭제
+ const handleDeleteOriginal = () => {
+ const updatedData = data.filter(
+ (item) => !selectedOriginal.includes(item.student_id),
+ );
+ setData(updatedData);
+ setIsDeleteModeOriginal(false);
+ setSelectedOriginal([]);
+ };
+
+ // 추가된 데이터 삭제
+ const handleDeleteAdded = () => {
+ const updatedData = addedData.filter(
+ (item) => !selectedAdded.includes(item.student_id),
+ );
+ setAddedData(updatedData);
+ setIsDeleteModeAdded(false);
+ setSelectedAdded([]);
+ };
+
+ // 기존 데이터 삭제 모드 토글
+ const toggleDeleteModeOriginal = () => {
+ setIsDeleteModeOriginal((prev) => !prev);
+ setSelectedOriginal([]);
+ };
+
+ // 추가된 데이터 삭제 모드 토글
+ const toggleDeleteModeAdded = () => {
+ setIsDeleteModeAdded((prev) => !prev);
+ setSelectedAdded([]);
+ };
+
+ const api = () => {
+ console.log('api 적용할 곳입니다.');
+ };
+
return (
-
+
-
+
- Here you can change your account settings.
+
+
+
+
+
+
+ {isDeleteModeAdded && (
+
+ )}
+
+
+
+
+
+
-
+
+
+
+
+
+ {isDeleteModeOriginal && (
+
+ )}
+
+
);
}
diff --git a/src/components/desktop/AddInput/index.tsx b/src/components/desktop/AddInput/index.tsx
new file mode 100644
index 0000000..34a7fbe
--- /dev/null
+++ b/src/components/desktop/AddInput/index.tsx
@@ -0,0 +1,24 @@
+import { Input } from '@/components/ui/input';
+import { Plus } from 'lucide-react';
+
+interface AddInputProps {
+ value: string;
+ onClick: () => void;
+ onChange: (event: React.ChangeEvent
) => void;
+}
+
+export default function AddInput({ onClick, value, onChange }: AddInputProps) {
+ return (
+
+ }
+ value={value}
+ onChange={onChange}
+ placeholder="이름을 입력해주세요."
+ />
+ );
+}
diff --git a/src/components/desktop/AddStudentId/index.tsx b/src/components/desktop/AddStudentId/index.tsx
new file mode 100644
index 0000000..cd84cf5
--- /dev/null
+++ b/src/components/desktop/AddStudentId/index.tsx
@@ -0,0 +1,23 @@
+import { Input } from '../../ui/input';
+import { Label } from '../../ui/label';
+
+interface AddStudentIdProps {
+ value: string;
+ onChange: (event: React.ChangeEvent) => void;
+}
+
+export default function AddStudentId({ value, onChange }: AddStudentIdProps) {
+ return (
+
+ {/* */}
+
+
+ );
+}
diff --git a/src/components/desktop/Search/index.tsx b/src/components/desktop/Search/index.tsx
index 128b6e1..f466474 100644
--- a/src/components/desktop/Search/index.tsx
+++ b/src/components/desktop/Search/index.tsx
@@ -2,10 +2,16 @@
import { SearchInput } from '../../ui/search-input';
-export default function Search() {
+interface SearchProps {
+ placeholder?: string;
+}
+
+export default function Search({
+ placeholder = '검색어를 입력하세요.',
+}: SearchProps) {
return (
console.log(query)}
/>
);
diff --git a/src/components/desktop/SideBar/index.tsx b/src/components/desktop/Sidebar/index.tsx
similarity index 65%
rename from src/components/desktop/SideBar/index.tsx
rename to src/components/desktop/Sidebar/index.tsx
index 35e3179..7f06a76 100644
--- a/src/components/desktop/SideBar/index.tsx
+++ b/src/components/desktop/Sidebar/index.tsx
@@ -18,18 +18,20 @@ interface SidebarProps {
export default function Sidebar({
children,
title = 'Sidebar Title',
- description = '',
+ // description = '',
triggerText = 'Open',
}: SidebarProps) {
return (
-
+
{triggerText}
- {title}
- {description && {description}}
+
+ {title}
+
+ {/* {description && {description}} */}
{children}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..a49fc42
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,65 @@
+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 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring 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 shadow hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
+ outline:
+ 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
+ link: 'text-primary underline-offset-4 hover:underline',
+ // 우리 버튼 추가
+ primary:
+ 'whitespace-nowrap rounded-md bg-gray-primary px-3 py-1 text-sm text-white-primary',
+ secondary:
+ 'whitespace-nowrap rounded-md bg-gray-secondary px-3 py-1 text-sm text-white-primary',
+ deletePrimary:
+ 'whitespace-nowrap rounded-md bg-gray-primary px-1 py-1 text-sm text-white-primary',
+ deleteSecondary:
+ 'whitespace-nowrap rounded-md bg-gray-secondary px-1 py-1 text-sm text-white-primary',
+ },
+ size: {
+ default: 'h-9 px-4 py-2',
+ sm: 'h-8 rounded-md px-3 text-xs',
+ lg: 'h-10 rounded-md px-8',
+ icon: 'h-9 w-9',
+ chevron: 'h-10 w-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button';
+ return (
+
+ );
+ },
+);
+Button.displayName = 'Button';
+
+export { Button, buttonVariants };
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..c6fdd07
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index 993530e..4eacb59 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -1,9 +1,21 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
-import { Search } from 'lucide-react';
+import { Plus } from 'lucide-react'; // 기본 아이콘은 Plus로 설정
-const Input = React.forwardRef>(
- ({ className, type = 'text', ...props }, ref) => {
+interface InputProps extends React.ComponentProps<'input'> {
+ Icon?: React.ReactNode; // 아이콘을 전달할 수 있는 props
+}
+
+const Input = React.forwardRef(
+ (
+ {
+ className,
+ type = 'text',
+ Icon = ,
+ ...props
+ },
+ ref,
+ ) => {
return (
>(
ref={ref}
{...props}
/>
-
+
+ {Icon} {/* 전달된 아이콘을 렌더링 */}
+
);
},
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..7114fb0
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+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,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+));
+Label.displayName = LabelPrimitive.Root.displayName;
+
+export { Label };
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
index c0df655..d0c6422 100644
--- a/src/components/ui/table.tsx
+++ b/src/components/ui/table.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const Table = React.forwardRef<
HTMLTableElement,
@@ -9,20 +9,20 @@ const Table = React.forwardRef<
-))
-Table.displayName = "Table"
+));
+Table.displayName = 'Table';
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
-
-))
-TableHeader.displayName = "TableHeader"
+
+));
+TableHeader.displayName = 'TableHeader';
const TableBody = React.forwardRef<
HTMLTableSectionElement,
@@ -30,11 +30,11 @@ const TableBody = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-TableBody.displayName = "TableBody"
+));
+TableBody.displayName = 'TableBody';
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
@@ -43,13 +43,13 @@ const TableFooter = React.forwardRef<
tr]:last:border-b-0",
- className
+ 'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
+ className,
)}
{...props}
/>
-))
-TableFooter.displayName = "TableFooter"
+));
+TableFooter.displayName = 'TableFooter';
const TableRow = React.forwardRef<
HTMLTableRowElement,
@@ -58,13 +58,13 @@ const TableRow = React.forwardRef<
-))
-TableRow.displayName = "TableRow"
+));
+TableRow.displayName = 'TableRow';
const TableHead = React.forwardRef<
HTMLTableCellElement,
@@ -73,13 +73,13 @@ const TableHead = React.forwardRef<
[role=checkbox]]:translate-y-[2px]",
- className
+ 'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
+ className,
)}
{...props}
/>
-))
-TableHead.displayName = "TableHead"
+));
+TableHead.displayName = 'TableHead';
const TableCell = React.forwardRef<
HTMLTableCellElement,
@@ -88,13 +88,13 @@ const TableCell = React.forwardRef<
| [role=checkbox]]:translate-y-[2px]",
- className
+ 'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
+ className,
)}
{...props}
/>
-))
-TableCell.displayName = "TableCell"
+));
+TableCell.displayName = 'TableCell';
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
@@ -102,11 +102,11 @@ const TableCaption = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-TableCaption.displayName = "TableCaption"
+));
+TableCaption.displayName = 'TableCaption';
export {
Table,
@@ -117,4 +117,4 @@ export {
TableRow,
TableCell,
TableCaption,
-}
+};
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 7fd69b7..aa075d7 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -13,7 +13,7 @@ export default {
pretendard: ['Pretendard', 'sans-serif'],
},
maxWidth: {
- sm: '40rem', // sm:max-w-sm 클래스를 40rem으로 설정
+ sm: '30rem', // sm:max-w-sm 클래스를 30rem으로 설정
},
colors: {
foreground: 'hsl(var(--foreground))',
|