Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5b970e4
feat: shadcn 버튼, 체크박스 추가
daun-up Feb 13, 2025
4bad500
style: tailwind.config 변경
daun-up Feb 13, 2025
7872725
feat: 학생회비 납부자 조회하기 ui 퍼블리싱
daun-up Feb 13, 2025
e973281
chore: package.json @radix-ui/react-label 추가
daun-up Feb 20, 2025
b58080a
chore: package.json @radix-ui/react-label 추가
daun-up Feb 20, 2025
0444313
fix: eslint 수정
daun-up Feb 20, 2025
adb2423
feat: 이름 등록 input 구현
daun-up Feb 20, 2025
a296f7e
feat: 학번 등록 input 구현
daun-up Feb 20, 2025
43f0818
feat: button 컴포넌트에 primary, secondary 속성 추가
daun-up Feb 20, 2025
40969c9
feat: sidebar sheet 컴포넌트 구현
daun-up Feb 20, 2025
cc4eec7
chore: yarn.lock
daun-up Feb 20, 2025
d2b7626
chore: package.json
daun-up Feb 20, 2025
385267c
fix: yarn.lock 충돌 문제
daun-up Feb 20, 2025
564ca0c
init: 모바일 뷰를 위한 레이아웃 구현
hyeonjin6530 Feb 15, 2025
36d5dfb
feat: 관리자 대시보드 기본적인 퍼블리싱
sinji2102 Feb 16, 2025
fed7402
feat: mobile header 공통 컴포넌트 svg 제외 구현
sinji2102 Feb 15, 2025
7907fa1
feat: svg 포함 구현
sinji2102 Feb 15, 2025
7ea2941
chore: hamburger 오타 수정
sinji2102 Feb 15, 2025
a32a863
feat: 명세서에 맞춰 기본적인 컴포넌트 연결
sinji2102 Feb 17, 2025
71c60e7
feat: 시간 변환 함수 구현
sinji2102 Feb 17, 2025
f54ff97
feat: 대시보드 svg 아이콘 세팅
sinji2102 Feb 17, 2025
1f4c54a
chore: 불필요한 use client 제거
sinji2102 Feb 17, 2025
12bb630
fix: yarn.lock 충돌 문제
sinji2102 Feb 15, 2025
df3cb1d
feat: NotificationItem 배경색, 폰트 색, 아이콘 제외 퍼블리싱
sinji2102 Feb 15, 2025
941d61c
design: 세팅에 없는 색 추가 및 svg 경로 수정
sinji2102 Feb 16, 2025
661511e
feat: 명세서에 맞춰 타입 수정
sinji2102 Feb 16, 2025
25a2553
feat: 알림 타입 const, 시간 계산 함수 추가
sinji2102 Feb 16, 2025
0e28f51
design: 디자인 시스템 네이밍 밀린 거 수정
sinji2102 Feb 16, 2025
b840b16
feat: 알림 상태별 색상 지정
sinji2102 Feb 16, 2025
360b277
feat: 유저 알림 퍼블리싱 및 타입 오류 해결
sinji2102 Feb 16, 2025
bf2025e
rename: 컨벤션에 맞도록 NotificationItem 컴포넌트 경로 수정
sinji2102 Feb 16, 2025
da13e89
feat: 알림 페이지 헤더 적용 및 헤더 높이 수정
sinji2102 Feb 16, 2025
00d5853
fix: svg 오류 해결
sinji2102 Feb 16, 2025
f20bff8
comment: 주석 제거
sinji2102 Feb 16, 2025
62f3ef8
style: const 띄어쓰기 오타 수정
sinji2102 Feb 16, 2025
5264ab2
feat: 드롭다운 공통 컴포넌트 hook 생성
sinji2102 Feb 17, 2025
1e2f110
feat: 위치 커스텀 가능하도록 수정
sinji2102 Feb 17, 2025
155deae
style: 인라인 스타일을 사용하지 않도록 수정
sinji2102 Feb 17, 2025
b9543b7
comment: 필요없는 주석 제거
sinji2102 Feb 17, 2025
c591214
feat: 드롭다운 연결
sinji2102 Feb 18, 2025
3a0c094
feat: 드롭다운 기능 연결 및 버튼 텍스트 수정
sinji2102 Feb 19, 2025
c19d409
fix: yarn build시 나오는 함수 오류 수정
sinji2102 Feb 19, 2025
bca6634
fix: svg allow 관련 config 파일 수정
sinji2102 Feb 20, 2025
b128f37
fix: frontend 파일 제거
sinji2102 Feb 20, 2025
8ecd412
fix: 완료 버튼 위치 변경
daun-up Feb 21, 2025
df80e38
comment: 불필요한 주석 삭제
daun-up Feb 21, 2025
a99ebda
feat: 삭제 버튼 위치 변경 및 스타일 수정
daun-up Feb 21, 2025
b993ec6
Merge branch 'develop' into feat/#14-sidebar-sheet
daun-up Feb 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

*.idea
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
106 changes: 95 additions & 11 deletions src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import {
Table,
TableBody,
Expand All @@ -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 (
<div className="flex justify-between p-10">
<div className="flex w-full flex-col p-10">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-30 text-center">이름</TableHead>
<TableHead className="w-30 text-center">학번</TableHead>
<TableHead className="w-30 text-center">관리자 여부</TableHead>
{showCheckboxes && (
<TableHead className="w-10 text-center">
<Checkbox
checked={selected.length === paginatedData.length}
onCheckedChange={handleSelectAll}
/>
</TableHead>
)}
{headers.map((header) => (
<TableHead key={header} className="w-30 text-center">
{header}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
{paginatedData.map((item) => (
<TableRow key={item.student_id}>
{showCheckboxes && (
<TableCell className="w-10 text-center">
<Checkbox
checked={selected.includes(item.student_id)}
onCheckedChange={() => handleSelect(item.student_id)}
/>
</TableCell>
)}
<TableCell className="w-30 text-center">{item.name}</TableCell>
<TableCell className="w-30 text-center">
{item.student_id}
</TableCell>
<TableCell className="w-30 text-center">
{item.admin ? '⭕' : '❌'}
</TableCell>
{headers.includes('관리자 여부') && (
<TableCell className="w-30 text-center">
{item.admin !== undefined && (item.admin ? 'o' : 'x')}
</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
<div className="flex items-center justify-center pt-4">
<Button
className="bg-transparent shadow-transparent hover:bg-transparent"
disabled={currentPage === 1}
onClick={() => setCurrentPage((prev) => prev - 1)}
>
<ChevronLeftIcon className="h-10 w-10 cursor-pointer text-black-primary" />
</Button>
<span>
{currentPage} / {Math.ceil(data.length / rowsPerPage)}
</span>
<Button
className="bg-transparent shadow-transparent hover:bg-transparent"
size="chevron"
disabled={currentPage === Math.ceil(data.length / rowsPerPage)}
onClick={() => setCurrentPage((prev) => prev + 1)}
>
<ChevronRightIcon className="h-6 w-6 cursor-pointer text-black-primary" />
</Button>
</div>
</div>
);
}

// defaultProps를 사용하여 headers에 기본값 설정
TableComponent.defaultProps = {
headers: ['이름', '학번', '관리자 여부'],
};
181 changes: 172 additions & 9 deletions src/app/desktop/payer-inquiry/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([]); // 기존 데이터에서 선택된 항목
const [selectedAdded, setSelectedAdded] = useState<string[]>([]); // 추가된 데이터에서 선택된 항목

const [newStudentId, setNewStudentId] = useState('');
const [newStudentName, setNewStudentName] = useState('');

// 학번 입력 핸들러
const handleStudentIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewStudentId(e.target.value);
};

// 이름 입력 핸들러
const handleStudentNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 (
<div className="flex flex-col justify-center gap-8 px-64">
<div className="flex flex-col justify-center gap-8 px-4 md:px-16 lg:px-64">
<div className="pt-20 text-center">
<p className="text-2xl">학생회비 납부자 조회하기</p>
</div>
<div className="flex justify-center gap-4">
<div className="flex flex-wrap justify-center gap-2">
<Search />
<Sidebar
triggerText="새로운 납부자 추가하기"
title="Settings"
description="Manage your preferences"
title="학생회비 납부자 추가하기"
description="설명"
>
<p>Here you can change your account settings.</p>
<div className="flex w-full flex-col items-center justify-center">
<div className="flex items-end justify-center gap-4">
<AddStudentId
value={newStudentId}
onChange={handleStudentIdChange}
/>
<AddInput
value={newStudentName}
onChange={handleStudentNameChange}
onClick={handleAddStudent}
/>
</div>
<div className="flex w-full flex-col">
<TableComponent
data={addedData}
headers={['추가된 이름', '추가된 학번']}
showCheckboxes={isDeleteModeAdded}
selected={selectedAdded}
setSelected={setSelectedAdded}
/>
<div className="flex justify-end gap-2">
<Button
size="sm"
type="button"
variant="deleteSecondary"
onClick={toggleDeleteModeAdded}
>
{isDeleteModeAdded ? '취소' : '삭제'}
</Button>
{isDeleteModeAdded && (
<Button
size="sm"
variant="deletePrimary"
type="button"
onClick={handleDeleteAdded}
>
완료
</Button>
)}
</div>
</div>
</div>
<div className="bottom-0 flex justify-center gap-2 pt-10">
<Button type="button" variant="primary" onClick={api}>
적용하기
</Button>
</div>
</Sidebar>
</div>
<TableComponent data={dummyData} />
<div className="flex flex-col justify-between">
<TableComponent
data={data}
showCheckboxes={isDeleteModeOriginal}
selected={selectedOriginal}
setSelected={setSelectedOriginal}
/>
<div className="flex justify-end gap-2">
<Button
size="sm"
type="button"
variant="deleteSecondary"
onClick={toggleDeleteModeOriginal}
>
{isDeleteModeOriginal ? '취소' : '삭제'}
</Button>

{isDeleteModeOriginal && (
<Button
type="button"
size="sm"
variant="deleteSecondary"
className={`btn whitespace-nowrap rounded-md px-3 py-2 text-sm text-white-primary ${isDeleteModeOriginal ? 'bg-gray-primary' : 'bg-gray-secondary'}`}
onClick={handleDeleteOriginal}
>
완료
</Button>
)}
</div>
</div>
</div>
);
}
24 changes: 24 additions & 0 deletions src/components/desktop/AddInput/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => void;
}

export default function AddInput({ onClick, value, onChange }: AddInputProps) {
return (
<Input
Icon={
<Plus
className="h-5 w-5 cursor-pointer text-muted-foreground"
onClick={onClick}
/>
}
value={value}
onChange={onChange}
placeholder="이름을 입력해주세요."
/>
);
}
Loading