From 8f77ccfbf3463d14caacbea4cb2f7afd512c0e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Mon, 11 Aug 2025 02:29:32 +0900 Subject: [PATCH 01/42] =?UTF-8?q?chore/#14:=20assignment=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=EB=93=A4=20assignments=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- src/pages/admin/{ => assignments}/AssignmentSelectPage.tsx | 6 +++--- src/pages/admin/{ => assignments}/AssignmentsPage.tsx | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/pages/admin/{ => assignments}/AssignmentSelectPage.tsx (80%) rename src/pages/admin/{ => assignments}/AssignmentsPage.tsx (100%) diff --git a/src/App.tsx b/src/App.tsx index a7039c3..09ef00f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import LandingPage from './pages/common/LandingPage'; import UserIdInputPage from './pages/common/UserIdInputPage'; import Dashboard from './pages/common/Dashboard'; import AssignmentsPage from './pages/admin/AssignmentsPage'; -import AssignmentSelectPage from './pages/admin/AssignmentSelectPage'; +import AssignmentSelectPage from './pages/admin/assignments/AssignmentSelectPage'; function App() { return ( diff --git a/src/pages/admin/AssignmentSelectPage.tsx b/src/pages/admin/assignments/AssignmentSelectPage.tsx similarity index 80% rename from src/pages/admin/AssignmentSelectPage.tsx rename to src/pages/admin/assignments/AssignmentSelectPage.tsx index fb27221..f79d1ae 100644 --- a/src/pages/admin/AssignmentSelectPage.tsx +++ b/src/pages/admin/assignments/AssignmentSelectPage.tsx @@ -1,8 +1,8 @@ -import {coursesResponse} from '../../components/admin/assignments/dummy/response'; +import {coursesResponse} from '../../../components/admin/assignments/dummy/response'; import {useState} from 'react'; import {useParams} from 'react-router-dom'; -import type {Assignment} from '../../components/admin/assignments/dummy/types'; -import AssignmentPageLayout from '../../components/admin/assignments/AssignmentPageLayout'; +import type {Assignment} from '../../../components/admin/assignments/dummy/types'; +import AssignmentPageLayout from '../../../components/admin/assignments/AssignmentPageLayout'; const AssignmentSelectPage = () => { // url에서 course id 가져오기 diff --git a/src/pages/admin/AssignmentsPage.tsx b/src/pages/admin/assignments/AssignmentsPage.tsx similarity index 100% rename from src/pages/admin/AssignmentsPage.tsx rename to src/pages/admin/assignments/AssignmentsPage.tsx From 44700df2af93bb1314ab8a3509ee738c7ce8045a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Wed, 3 Sep 2025 02:22:50 +0900 Subject: [PATCH 02/42] =?UTF-8?q?#14=20chore:=20vite=EC=97=90=EC=84=9C=20@?= =?UTF-8?q?alias=20=EA=B2=BD=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 105 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 ++- tsconfig.app.json | 10 ++++- tsconfig.node.json | 8 +++- vite.config.ts | 6 +++ 5 files changed, 129 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52bd80d..0c36197 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@eslint/js": "^9.29.0", + "@types/node": "^24.2.1", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.5.2", @@ -23,10 +24,12 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.2.0", + "tsconfig-paths": "^4.2.0", "typescript": "~5.8.3", "typescript-eslint": "^8.34.1", "vite": "^7.0.0", - "vite-plugin-svgr": "^4.3.0" + "vite-plugin-svgr": "^4.3.0", + "vite-tsconfig-paths": "^5.1.4" } }, "node_modules/@ampproject/remapping": { @@ -1890,6 +1893,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", + "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, "node_modules/@types/react": { "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", @@ -3001,6 +3014,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3539,6 +3559,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4043,6 +4073,16 @@ "node": ">=0.10.0" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4185,6 +4225,42 @@ "typescript": ">=4.8.4" } }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4242,6 +4318,13 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "devOptional": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -4372,6 +4455,26 @@ "vite": ">=2.6.0" } }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", diff --git a/package.json b/package.json index df7e1d7..dfa3505 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@eslint/js": "^9.29.0", + "@types/node": "^24.2.1", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.5.2", @@ -26,9 +27,11 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.2.0", + "tsconfig-paths": "^4.2.0", "typescript": "~5.8.3", "typescript-eslint": "^8.34.1", "vite": "^7.0.0", - "vite-plugin-svgr": "^4.3.0" + "vite-plugin-svgr": "^4.3.0", + "vite-tsconfig-paths": "^5.1.4" } } diff --git a/tsconfig.app.json b/tsconfig.app.json index 7a844e2..1db5b44 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -23,7 +23,13 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - "types": ["vite-plugin-svgr/client"] + "types": ["vite-plugin-svgr/client"], + + /* Alias */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, - "include": ["src", "custom.d.ts"] + "include": ["src"] } diff --git a/tsconfig.node.json b/tsconfig.node.json index f85a399..5263d90 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -19,7 +19,13 @@ "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + + /* Alias */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "include": ["vite.config.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 08835a7..1cd313a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,7 @@ import {defineConfig} from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; import svgr from 'vite-plugin-svgr'; +import path from 'path'; // https://vite.dev/config/ export default defineConfig({ @@ -14,4 +15,9 @@ export default defineConfig({ }, }), ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, }); From c4998bbeec05f595a8337728857a8fda177cb037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Wed, 3 Sep 2025 02:56:01 +0900 Subject: [PATCH 03/42] =?UTF-8?q?#14=20feat:=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=20=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignments/AssignmentFormLayout.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/components/admin/assignments/AssignmentFormLayout.tsx diff --git a/src/components/admin/assignments/AssignmentFormLayout.tsx b/src/components/admin/assignments/AssignmentFormLayout.tsx new file mode 100644 index 0000000..d7c5697 --- /dev/null +++ b/src/components/admin/assignments/AssignmentFormLayout.tsx @@ -0,0 +1,35 @@ +import Button from '@/components/common/Button'; + +type AssignmentFormLayoutProps = { + title: string; + content: React.ReactNode; + onCancel: () => void; + onConfirm: () => void; +}; + +const AssignmentFormLayout = ({ + title, + content, + onCancel, + onConfirm, +}: AssignmentFormLayoutProps) => { + return ( +
+
+ {/* 제목 */} +

{title}

+ + {/* 본문 */} +
{content}
+ + {/* 하단 버튼 */} +
+
+
+
+ ); +}; + +export default AssignmentFormLayout; From 3c6efa1afe4b8cb9528c5daaa68250c9743176c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Wed, 3 Sep 2025 03:15:06 +0900 Subject: [PATCH 04/42] =?UTF-8?q?#14=20feat:=20chevronDown=20svg=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/svg/chevrondown.svg | 3 +++ src/assets/svg/Chevrondown.tsx | 17 +++++++++++++++++ src/assets/svg/index.ts | 1 + 3 files changed, 21 insertions(+) create mode 100644 public/svg/chevrondown.svg create mode 100644 src/assets/svg/Chevrondown.tsx diff --git a/public/svg/chevrondown.svg b/public/svg/chevrondown.svg new file mode 100644 index 0000000..1b7f8cc --- /dev/null +++ b/public/svg/chevrondown.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/Chevrondown.tsx b/src/assets/svg/Chevrondown.tsx new file mode 100644 index 0000000..e5bd8cb --- /dev/null +++ b/src/assets/svg/Chevrondown.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import type {SVGProps} from 'react'; +const SvgChevrondown = (props: SVGProps) => ( + + + +); +export default SvgChevrondown; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts index 910de97..4af2b34 100644 --- a/src/assets/svg/index.ts +++ b/src/assets/svg/index.ts @@ -3,6 +3,7 @@ export {default as ArrowdownIcon} from './ArrowdownIcon'; export {default as ArrowleftIcon} from './ArrowleftIcon'; export {default as ArrowrightIcon} from './ArrowrightIcon'; export {default as ChatIcon} from './ChatIcon'; +export {default as Chevrondown} from './Chevrondown'; export {default as DeleteIcon} from './DeleteIcon'; export {default as DragAndDropIcon} from './DragAndDropIcon'; export {default as EditIcon} from './EditIcon'; From 54371504e94db0cfd1fd9ea6b7cce112ff6ce06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Wed, 3 Sep 2025 03:30:28 +0900 Subject: [PATCH 05/42] =?UTF-8?q?#14=20refactor:=20=EC=BD=9C=EB=B0=B1=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=8B=A8=EC=88=9C=ED=99=94,=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=EC=8A=A4=ED=85=9C=EA=B3=BC=20=EC=9D=BC?= =?UTF-8?q?=EC=B9=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/common/UserIdInputPage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/common/UserIdInputPage.tsx b/src/pages/common/UserIdInputPage.tsx index dd1c62a..1b241b7 100644 --- a/src/pages/common/UserIdInputPage.tsx +++ b/src/pages/common/UserIdInputPage.tsx @@ -12,7 +12,7 @@ export default function UserIdInputPage() { const length = 7; const [userId, setUserId] = useState(new Array(length).fill('')); const [activeIndex, setActiveIndex] = useState(0); - const inputRefs = useRef>([]); + const inputRefs = useRef<(HTMLInputElement | null)[]>([]); useEffect(() => { inputRefs.current[activeIndex]?.focus(); @@ -98,7 +98,9 @@ export default function UserIdInputPage() { {userId.map((digit, i) => ( (inputRefs.current[i] = ref)} + ref={(el) => { + inputRefs.current[i] = el; + }} type='text' inputMode='numeric' maxLength={1} From 654b44b7596b9df78e2c9b3d427164474851cbc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 4 Sep 2025 08:56:24 +0900 Subject: [PATCH 06/42] =?UTF-8?q?#14=20feat:=20AssignmentCreatePage=20UI?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignments/AssignmentCreatePage.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/pages/admin/assignments/AssignmentCreatePage.tsx diff --git a/src/pages/admin/assignments/AssignmentCreatePage.tsx b/src/pages/admin/assignments/AssignmentCreatePage.tsx new file mode 100644 index 0000000..5e2fbce --- /dev/null +++ b/src/pages/admin/assignments/AssignmentCreatePage.tsx @@ -0,0 +1,58 @@ +import AssignmentFormLayout from '@/components/admin/assignments/AssignmentFormLayout'; +import LabeledInput from '@/components/admin/form/LabeledInput'; + +const AssignmentCreatePage = () => { + return ( + +
+ + +
+ + +
+ + + +
+ + {/* 추가 버튼: 내용만큼만 */} + + + } + onCancel={() => {}} + onConfirm={() => {}} + /> + ); +}; + +export default AssignmentCreatePage; From f040affe49afefc40689d0f1fc1f04e4cd605fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 4 Sep 2025 08:57:03 +0900 Subject: [PATCH 07/42] =?UTF-8?q?#14=20feat:=20LabeledInput=20UI=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/admin/form/LabeledInput.tsx | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/components/admin/form/LabeledInput.tsx diff --git a/src/components/admin/form/LabeledInput.tsx b/src/components/admin/form/LabeledInput.tsx new file mode 100644 index 0000000..f5febc9 --- /dev/null +++ b/src/components/admin/form/LabeledInput.tsx @@ -0,0 +1,73 @@ +import {useState} from 'react'; +import {Chevrondown} from '@/assets/svg'; + +type LabeledInputProps = { + label: string; + className?: string; + variant?: 'input' | 'dropdown'; + options?: {value: string; label: string}[]; +} & React.InputHTMLAttributes; + +const LabeledInput = ({ + label, + className, + variant = 'input', + options = [ + {value: 'public', label: '공개'}, + {value: 'private', label: '비공개'}, + ], + ...rest +}: LabeledInputProps) => { + const [isOpen, setIsOpen] = useState(false); + const [selectedValue, setSelectedValue] = useState(''); + return ( + + ); +}; + +export default LabeledInput; From 0d46a9384b8c35947caea925922d730d2041e6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Fri, 5 Sep 2025 03:56:26 +0900 Subject: [PATCH 08/42] =?UTF-8?q?#14=20feat:=20file.svg=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/svg/file.svg | 4 ++++ src/assets/svg/File.tsx | 22 ++++++++++++++++++++++ src/assets/svg/index.ts | 1 + 3 files changed, 27 insertions(+) create mode 100644 public/svg/file.svg create mode 100644 src/assets/svg/File.tsx diff --git a/public/svg/file.svg b/public/svg/file.svg new file mode 100644 index 0000000..9478e7f --- /dev/null +++ b/public/svg/file.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/File.tsx b/src/assets/svg/File.tsx new file mode 100644 index 0000000..ddef665 --- /dev/null +++ b/src/assets/svg/File.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import type {SVGProps} from 'react'; +const SvgFile = (props: SVGProps) => ( + + + + +); +export default SvgFile; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts index 4af2b34..f686fb5 100644 --- a/src/assets/svg/index.ts +++ b/src/assets/svg/index.ts @@ -8,6 +8,7 @@ export {default as DeleteIcon} from './DeleteIcon'; export {default as DragAndDropIcon} from './DragAndDropIcon'; export {default as EditIcon} from './EditIcon'; export {default as EllipsisIcon} from './EllipsisIcon'; +export {default as File} from './File'; export {default as NotificationIcon} from './NotificationIcon'; export {default as SignoutIcon} from './SignoutIcon'; export {default as SingleEllipsisIcon} from './SingleEllipsisIcon'; From 1f35460145f2a90979599a54c1ebb6b34e04355f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:48:20 +0900 Subject: [PATCH 09/42] =?UTF-8?q?#14=20feat:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/admin/form/FileUpload.tsx | 123 +++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/components/admin/form/FileUpload.tsx diff --git a/src/components/admin/form/FileUpload.tsx b/src/components/admin/form/FileUpload.tsx new file mode 100644 index 0000000..8b817e6 --- /dev/null +++ b/src/components/admin/form/FileUpload.tsx @@ -0,0 +1,123 @@ +import {useRef, useState} from 'react'; +import type {ChangeEvent} from 'react'; +import {File} from '@/assets/svg'; + +type FileUploadProps = { + label: string; + onFileChange: (file: File | null) => void; + description?: string; + accept?: string; + className?: string; +}; + +export default function FileUpload({ + label, + onFileChange, + description = '업로드하기', + accept = '.csv', + className, +}: FileUploadProps) { + const inputRef = useRef(null); + const [selectedFile, setSelectedFile] = useState(null); + const dragCounter = useRef(0); + + const prevent = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const handleDragEnter = (e: React.DragEvent) => { + prevent(e); + dragCounter.current += 1; + }; + + const handleDragLeave = (e: React.DragEvent) => { + prevent(e); + dragCounter.current -= 1; + }; + + const handleDragOver = (e: React.DragEvent) => prevent(e); + + const handleDrop = (e: React.DragEvent) => { + prevent(e); + dragCounter.current = 0; + + const items = e.dataTransfer?.items; + if (items && items.length === 0) return; + + const files = Array.from(e.dataTransfer.files || []); + if (files.length) { + const file = files[0]; + setSelectedFile(file); + onFileChange(file); + } + }; + + const handleChange = (e: ChangeEvent) => { + const files = Array.from(e.target.files ?? []); + if (files.length) { + const file = files[0]; + setSelectedFile(file); + onFileChange(file); + } + // 값 초기화(같은 파일 재선택 허용) + e.target.value = ''; + }; + + const handleRemoveFile = () => { + setSelectedFile(null); + onFileChange(null); + }; + + return ( +
+ + {label} + + + {selectedFile ? ( +
+ {selectedFile.name} + +
+ ) : ( +
inputRef.current?.click()} + onKeyDown={(e) => + (e.key === 'Enter' || e.key === ' ') && inputRef.current?.click() + } + onDragEnter={handleDragEnter} + onDragLeave={handleDragLeave} + onDragOver={handleDragOver} + onDrop={handleDrop} + className={[ + 'flex h-[166px] w-full cursor-pointer items-center justify-center rounded-[9px] border-1 border-dashed border-primary bg-background', + 'focus:outline-none focus:ring-2 focus:ring-primary', + ].join(' ')}> +
+ +
+ {description} +
+
+
+ )} + + +
+ ); +} From e1557901b5d576a748520dfd04bda334415734d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:14:15 +0900 Subject: [PATCH 10/42] =?UTF-8?q?#14=20feat:=20=EC=98=88=EC=A0=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EB=9E=80=20=EB=8F=99=EC=A0=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=9D=BC=EB=B2=A8=20=EA=B0=84=EA=B2=A9?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignments/AssignmentCreatePage.tsx | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/pages/admin/assignments/AssignmentCreatePage.tsx b/src/pages/admin/assignments/AssignmentCreatePage.tsx index 5e2fbce..e693ebe 100644 --- a/src/pages/admin/assignments/AssignmentCreatePage.tsx +++ b/src/pages/admin/assignments/AssignmentCreatePage.tsx @@ -1,7 +1,15 @@ import AssignmentFormLayout from '@/components/admin/assignments/AssignmentFormLayout'; import LabeledInput from '@/components/admin/form/LabeledInput'; +import FileUpload from '@/components/admin/form/FileUpload'; +import {useState} from 'react'; const AssignmentCreatePage = () => { + const [examples, setExamples] = useState([{input: '', output: '', 공개: ''}]); + + const handleAddExample = () => { + setExamples([...examples, {input: '', output: '', 공개: ''}]); + }; + return ( { placeholder='문제 설명을 입력하세요' className='w-full' /> -
- - - +
+ {examples.map((ex, idx) => ( +
+ + + +
+ ))}
- {/* 추가 버튼: 내용만큼만 */} - + {}} + className='mb-9' + />
} onCancel={() => {}} From 418a352ced963795b2d54d1a8e0a9b12a49ee59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:42:57 +0900 Subject: [PATCH 11/42] =?UTF-8?q?#14=20feat:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/admin/courses/CourseCreatePage.tsx | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/pages/admin/courses/CourseCreatePage.tsx diff --git a/src/pages/admin/courses/CourseCreatePage.tsx b/src/pages/admin/courses/CourseCreatePage.tsx new file mode 100644 index 0000000..0e23e26 --- /dev/null +++ b/src/pages/admin/courses/CourseCreatePage.tsx @@ -0,0 +1,56 @@ +import AssignmentFormLayout from '@/components/admin/assignments/AssignmentFormLayout'; +import LabeledInput from '@/components/admin/form/LabeledInput'; +import FileUpload from '@/components/admin/form/FileUpload'; +import LabeledDropdown from '@/components/admin/form/LabeledDropdown'; + +const CourseCreatePage = () => { + return ( + +
+ + + + +
+ + + + {}} + className='mb-9' + /> + + } + onCancel={() => {}} + onConfirm={() => {}} + /> + ); +}; + +export default CourseCreatePage; From 36811bfcadff8d1d36489a745d81609d816aa8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:43:14 +0900 Subject: [PATCH 12/42] =?UTF-8?q?#14=20feat:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/admin/assignments/AssignmentCreatePage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/admin/assignments/AssignmentCreatePage.tsx b/src/pages/admin/assignments/AssignmentCreatePage.tsx index e693ebe..0d4180d 100644 --- a/src/pages/admin/assignments/AssignmentCreatePage.tsx +++ b/src/pages/admin/assignments/AssignmentCreatePage.tsx @@ -2,6 +2,7 @@ import AssignmentFormLayout from '@/components/admin/assignments/AssignmentFormL import LabeledInput from '@/components/admin/form/LabeledInput'; import FileUpload from '@/components/admin/form/FileUpload'; import {useState} from 'react'; +import LabeledDropdown from '@/components/admin/form/LabeledDropdown'; const AssignmentCreatePage = () => { const [examples, setExamples] = useState([{input: '', output: '', 공개: ''}]); @@ -48,9 +49,9 @@ const AssignmentCreatePage = () => { placeholder='입력하세요' className='w-full' /> - From 62bd4d8681b945e507beeb598c59f6879d242ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:44:27 +0900 Subject: [PATCH 13/42] =?UTF-8?q?#14=20feat:=20useClickOutside=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=ED=9B=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useClickOutside.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/hooks/useClickOutside.ts diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts new file mode 100644 index 0000000..454fe61 --- /dev/null +++ b/src/hooks/useClickOutside.ts @@ -0,0 +1,32 @@ +import {useEffect} from 'react'; + +type UseClickOutsideProps = { + ref: React.RefObject; + onClickOutside: () => void; + exclude?: (target: HTMLElement) => boolean; +}; + +const useClickOutside = ({ + ref, + onClickOutside, + exclude, +}: UseClickOutsideProps) => { + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as HTMLElement; + + if (exclude?.(target)) return; + + if (ref.current?.contains(target) === false) { + onClickOutside(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref, onClickOutside, exclude]); +}; + +export default useClickOutside; From 2f50a580694bd2c84a5d5df5b3db4ca6ad838a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:51:17 +0900 Subject: [PATCH 14/42] =?UTF-8?q?#14=20feat:=20LabeledInput=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20LabeledDropdown=20var?= =?UTF-8?q?iant=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/admin/form/LabeledDropdown.tsx | 88 +++++++++++++++++++ src/components/admin/form/LabeledInput.tsx | 70 ++++----------- 2 files changed, 103 insertions(+), 55 deletions(-) create mode 100644 src/components/admin/form/LabeledDropdown.tsx diff --git a/src/components/admin/form/LabeledDropdown.tsx b/src/components/admin/form/LabeledDropdown.tsx new file mode 100644 index 0000000..680d2df --- /dev/null +++ b/src/components/admin/form/LabeledDropdown.tsx @@ -0,0 +1,88 @@ +import {useState, useRef} from 'react'; +import {Chevrondown} from '@/assets/svg'; +import useClickOutside from '@/hooks/useClickOutside'; + +interface LabeledDropdownProps + extends Omit, 'onSelect'> { + label: string; + className?: string; + variant: 'visibility' | 'year' | 'semester'; + placeholder?: string; + onSelect?: (value: string) => void; +} + +const getDefaultOptions = (variant: 'visibility' | 'year' | 'semester') => { + switch (variant) { + case 'visibility': + return ['공개', '비공개']; + case 'year': + return ['2021', '2022', '2023', '2024', '2025']; + case 'semester': + return ['1학기', '2학기', '여름학기', '겨울학기']; + default: + return []; + } +}; + +const LabeledDropdown = ({ + label, + className, + variant, + placeholder, + onSelect, + ...rest +}: LabeledDropdownProps) => { + const [selectedValue, setSelectedValue] = useState(''); + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + const optionsToUse = getDefaultOptions(variant); + + useClickOutside({ + ref: dropdownRef, + onClickOutside: () => setIsOpen(false), + }); + + const handleSelect = (option: string, event: React.MouseEvent) => { + event.stopPropagation(); + setSelectedValue(option); + setIsOpen(false); + onSelect?.(option); + }; + + return ( + + ); +}; + +export default LabeledDropdown; diff --git a/src/components/admin/form/LabeledInput.tsx b/src/components/admin/form/LabeledInput.tsx index f5febc9..8951b74 100644 --- a/src/components/admin/form/LabeledInput.tsx +++ b/src/components/admin/form/LabeledInput.tsx @@ -1,71 +1,31 @@ -import {useState} from 'react'; -import {Chevrondown} from '@/assets/svg'; - -type LabeledInputProps = { +interface LabeledInputProps + extends React.InputHTMLAttributes { label: string; className?: string; - variant?: 'input' | 'dropdown'; - options?: {value: string; label: string}[]; -} & React.InputHTMLAttributes; + showLabel?: boolean; +} const LabeledInput = ({ label, className, - variant = 'input', - options = [ - {value: 'public', label: '공개'}, - {value: 'private', label: '비공개'}, - ], + showLabel = true, ...rest }: LabeledInputProps) => { - const [isOpen, setIsOpen] = useState(false); - const [selectedValue, setSelectedValue] = useState(''); return ( ); }; From 2789f8401cbf85e9a8ebae6091f859f2c01b122b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:51:59 +0900 Subject: [PATCH 15/42] =?UTF-8?q?#14=20style:=20placeholder=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=20=EC=83=89=EC=83=81=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.css b/src/index.css index f2a112e..9dd07ce 100644 --- a/src/index.css +++ b/src/index.css @@ -74,3 +74,7 @@ .text-btn { @apply text-base font-medium; } + +input::placeholder { + color: var(--color-light-black); +} From d85f49ec6d2de077d592e91835dbb6a3e4df5316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:52:53 +0900 Subject: [PATCH 16/42] =?UTF-8?q?#14=20feat:=20=EB=9D=BC=EC=9A=B0=ED=8A=B8?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 09ef00f..c639b44 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,8 +3,10 @@ import Layout from './layout/Layout'; import LandingPage from './pages/common/LandingPage'; import UserIdInputPage from './pages/common/UserIdInputPage'; import Dashboard from './pages/common/Dashboard'; -import AssignmentsPage from './pages/admin/AssignmentsPage'; -import AssignmentSelectPage from './pages/admin/assignments/AssignmentSelectPage'; +// import AssignmentsPage from './pages/admin/assignments/AssignmentsPage'; +// import AssignmentSelectPage from './pages/admin/assignments/AssignmentSelectPage'; +import AssignmentCreatePage from './pages/admin/assignments/AssignmentCreatePage'; +import CourseCreatePage from './pages/admin/courses/CourseCreatePage'; function App() { return ( @@ -26,8 +28,13 @@ function App() { }> {/* 추가 페이지들 */} } /> - } /> - } /> + {/* } /> + } /> */} + } + /> + } /> From 8a57dfa0a6088f316892e56e41767af24668663f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 18 Sep 2025 08:57:43 +0900 Subject: [PATCH 17/42] =?UTF-8?q?#13=20chore:=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=BB=AC=EB=9F=AC=20=ED=8C=94=EB=A0=88=ED=8A=B8=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.css b/src/index.css index 9dd07ce..26c7fe3 100644 --- a/src/index.css +++ b/src/index.css @@ -48,6 +48,9 @@ --color-primary-black: #2c2a36; --color-purple-stroke: #dfdbf0; --color-hover: #b4a5ff; + --color-status-green: #c4ffa4; + --color-status-yellow: #ffe292; + --color-status-red: #ffb3b3; --font-coolvetica: 'Coolvetica', sans-serif; --shadow-card: 0px 0px 14px 0px rgba(223, 219, 240, 0.4); From 07d7fddb845fdb74ed5ef5ba9153b158964941fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Thu, 18 Sep 2025 09:15:44 +0900 Subject: [PATCH 18/42] =?UTF-8?q?#13=20chore:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/svg/profileImage.svg | 80 ++++++++++++++ src/assets/svg/ProfileImage.tsx | 183 ++++++++++++++++++++++++++++++++ src/assets/svg/index.ts | 1 + 3 files changed, 264 insertions(+) create mode 100644 public/svg/profileImage.svg create mode 100644 src/assets/svg/ProfileImage.tsx diff --git a/public/svg/profileImage.svg b/public/svg/profileImage.svg new file mode 100644 index 0000000..213c774 --- /dev/null +++ b/public/svg/profileImage.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/ProfileImage.tsx b/src/assets/svg/ProfileImage.tsx new file mode 100644 index 0000000..6fb793f --- /dev/null +++ b/src/assets/svg/ProfileImage.tsx @@ -0,0 +1,183 @@ +import * as React from 'react'; +import type {SVGProps} from 'react'; +const SvgProfileImage = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default SvgProfileImage; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts index f686fb5..7a2edb8 100644 --- a/src/assets/svg/index.ts +++ b/src/assets/svg/index.ts @@ -10,6 +10,7 @@ export {default as EditIcon} from './EditIcon'; export {default as EllipsisIcon} from './EllipsisIcon'; export {default as File} from './File'; export {default as NotificationIcon} from './NotificationIcon'; +export {default as ProfileImage} from './ProfileImage'; export {default as SignoutIcon} from './SignoutIcon'; export {default as SingleEllipsisIcon} from './SingleEllipsisIcon'; export {default as UserIcon} from './UserIcon'; From 70d273df6ce6bba90a7533c14bcd1045fd0deb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:57:57 +0900 Subject: [PATCH 19/42] =?UTF-8?q?#13=20feat:=20Search=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/svg/search.svg | 3 +++ src/assets/svg/Search.tsx | 18 ++++++++++++++++++ src/assets/svg/index.ts | 1 + 3 files changed, 22 insertions(+) create mode 100644 public/svg/search.svg create mode 100644 src/assets/svg/Search.tsx diff --git a/public/svg/search.svg b/public/svg/search.svg new file mode 100644 index 0000000..f0bf38e --- /dev/null +++ b/public/svg/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/Search.tsx b/src/assets/svg/Search.tsx new file mode 100644 index 0000000..216d65f --- /dev/null +++ b/src/assets/svg/Search.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import type {SVGProps} from 'react'; +const SvgSearch = (props: SVGProps) => ( + + + +); +export default SvgSearch; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts index 7a2edb8..fa8243b 100644 --- a/src/assets/svg/index.ts +++ b/src/assets/svg/index.ts @@ -11,6 +11,7 @@ export {default as EllipsisIcon} from './EllipsisIcon'; export {default as File} from './File'; export {default as NotificationIcon} from './NotificationIcon'; export {default as ProfileImage} from './ProfileImage'; +export {default as Search} from './Search'; export {default as SignoutIcon} from './SignoutIcon'; export {default as SingleEllipsisIcon} from './SingleEllipsisIcon'; export {default as UserIcon} from './UserIcon'; From 45b0b972ae30b4cc59d050c7dac85947899efb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:14:01 +0900 Subject: [PATCH 20/42] =?UTF-8?q?#13=20feat:=20StudentManagementPage=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B0=8F=20UI=20=EC=9D=BC=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/admin/assignments/AssignmentFormLayout.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/admin/assignments/AssignmentFormLayout.tsx b/src/components/admin/assignments/AssignmentFormLayout.tsx index d7c5697..3ddd194 100644 --- a/src/components/admin/assignments/AssignmentFormLayout.tsx +++ b/src/components/admin/assignments/AssignmentFormLayout.tsx @@ -2,6 +2,7 @@ import Button from '@/components/common/Button'; type AssignmentFormLayoutProps = { title: string; + titleExtra?: React.ReactNode; content: React.ReactNode; onCancel: () => void; onConfirm: () => void; @@ -9,6 +10,7 @@ type AssignmentFormLayoutProps = { const AssignmentFormLayout = ({ title, + titleExtra, content, onCancel, onConfirm, @@ -17,7 +19,10 @@ const AssignmentFormLayout = ({
{/* 제목 */} -

{title}

+
+

{title}

+ {titleExtra} +
{/* 본문 */}
{content}
From 6e703f21ca75e728b6b8f2f506658b0150b987c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:16:12 +0900 Subject: [PATCH 21/42] =?UTF-8?q?#13=20feat:=20StudentManagementPage=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=83=89=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/student/studentManagementPage.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/pages/admin/student/studentManagementPage.tsx diff --git a/src/pages/admin/student/studentManagementPage.tsx b/src/pages/admin/student/studentManagementPage.tsx new file mode 100644 index 0000000..520304d --- /dev/null +++ b/src/pages/admin/student/studentManagementPage.tsx @@ -0,0 +1,30 @@ +import AssignmentFormLayout from '@/components/admin/assignments/AssignmentFormLayout'; +import {Search} from '@/assets/svg'; + +export default function StudentManagementPage() { + const titleExtra = ( +
+ +
+ + +
+
+ ); + + return ( + 학생 관리 기능이 여기에 들어갑니다.
} + onCancel={() => {}} + onConfirm={() => {}} + /> + ); +} From ec6d43210ec3f454fa5c64d59b12ae3a56546009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:55:06 +0900 Subject: [PATCH 22/42] =?UTF-8?q?#13=20chore:=20prettier-plugin-tailwindcs?= =?UTF-8?q?s=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 105 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 2 files changed, 107 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0c36197..109a10b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.2.0", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", "tsconfig-paths": "^4.2.0", "typescript": "~5.8.3", "typescript-eslint": "^8.34.1", @@ -3824,6 +3826,109 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index dfa3505..a52ebdf 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.2.0", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", "tsconfig-paths": "^4.2.0", "typescript": "~5.8.3", "typescript-eslint": "^8.34.1", From 0927964f90ae608b2d6072f8c1069309873dca49 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 13:00:11 +0900 Subject: [PATCH 23/42] =?UTF-8?q?#21=20chore:=20tailwind-variants=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 20 ++++++++++++++++++++ package.json | 1 + 2 files changed, 21 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0c36197..1d6b733 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.3", + "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.11" }, "devDependencies": { @@ -4116,6 +4117,25 @@ "dev": true, "license": "MIT" }, + "node_modules/tailwind-variants": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz", + "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==", + "license": "MIT", + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwind-merge": ">=3.0.0", + "tailwindcss": "*" + }, + "peerDependenciesMeta": { + "tailwind-merge": { + "optional": true + } + } + }, "node_modules/tailwindcss": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", diff --git a/package.json b/package.json index dfa3505..ccbe2de 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.3", + "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.11" }, "devDependencies": { From 77c081efcb703a65c7b293f762e7dc951e0f4378 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 14:06:09 +0900 Subject: [PATCH 24/42] =?UTF-8?q?#21=20refactor:=20Button=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20tailwind-variants=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Button.tsx | 49 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 4be7e34..5225644 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -1,27 +1,36 @@ -interface ButtonProps { - theme: string; - text: string; - icon?: React.ReactElement; - onClick?: () => void; -} +import {tv, type VariantProps} from 'tailwind-variants/lite'; -interface ButtonTheme { - [key: string]: string; -} +const button = tv({ + base: 'flex-center px-3 py-1.5 text-center text-base font-medium whitespace-nowrap rounded-[10px] border', + variants: { + color: { + primary: 'bg-primary text-white border-primary', + secondary: 'bg-white text-primary-black border-purple-stroke', + outlinePurple: 'bg-white text-primary border-primary', + outlineWhite: 'bg-transparent text-white border-white', + tonal: 'bg-purple-stroke text-secondary-black border-purple-stroke', + }, + isIcon: { + true: 'rounded-full', + }, + }, + defaultVariants: { + color: 'primary', + isIcon: false, + }, +}); -const buttonTheme: ButtonTheme = { - primaryPurple: 'primary-btn bg-primary text-white', - primaryWhite: 'primary-btn bg-white text-primary border', - primaryTransparent: 'primary-btn text-white border border-white', - secondaryPurpleStroke: 'secondary-btn bg-purple-stroke text-secondary-black', -}; +type ButtonVariants = VariantProps; + +interface ButtonProps extends ButtonVariants { + children: React.ReactNode; + onClick?: () => void; +} -const Button = ({theme, text, icon}: ButtonProps) => { +const Button = ({children, onClick, ...props}: ButtonProps) => { return ( - ); }; From 6aa8fb19eb2906f44643f11e34f136301401b4e5 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 20:37:37 +0900 Subject: [PATCH 25/42] =?UTF-8?q?(#21)=20feat:=20Button=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20props=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20variants=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Button.tsx | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 5225644..92f64f0 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -1,14 +1,23 @@ import {tv, type VariantProps} from 'tailwind-variants/lite'; const button = tv({ - base: 'flex-center px-3 py-1.5 text-center text-base font-medium whitespace-nowrap rounded-[10px] border', + base: 'cursor-pointer flex-center text-center gap-2 text-base font-medium whitespace-nowrap rounded-[10px] border', variants: { color: { primary: 'bg-primary text-white border-primary', - secondary: 'bg-white text-primary-black border-purple-stroke', + secondary: + 'bg-white text-primary-black border-purple-stroke hover:bg-hover hover:text-white', outlinePurple: 'bg-white text-primary border-primary', outlineWhite: 'bg-transparent text-white border-white', tonal: 'bg-purple-stroke text-secondary-black border-purple-stroke', + ghost: 'bg-transparent text-black border-none', + ghostWhite: 'bg-white text-secondary-black border-none', + }, + size: { + default: 'w-24 h-10 px-3 py-1.5', + compact: 'w-fit leading-5 px-3 py-1.5', + wide: 'w-40 py-[15px]', + none: 'w-fit p-0', }, isIcon: { true: 'rounded-full', @@ -16,6 +25,8 @@ const button = tv({ }, defaultVariants: { color: 'primary', + size: 'default', + disabled: false, isIcon: false, }, }); @@ -24,12 +35,23 @@ type ButtonVariants = VariantProps; interface ButtonProps extends ButtonVariants { children: React.ReactNode; + className?: string; + type?: 'button' | 'submit'; + disabled?: boolean; onClick?: () => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; } -const Button = ({children, onClick, ...props}: ButtonProps) => { +const Button = ({ + children, + onClick, + type = 'button', + disabled = false, + ...props +}: ButtonProps) => { return ( - ); From e81750a4aed2ccdb28ee73c16c13ad485fb41d2d Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 20:40:38 +0900 Subject: [PATCH 26/42] =?UTF-8?q?#21=20feat:=20=EB=A6=AC=ED=8E=99=ED=86=A0?= =?UTF-8?q?=EB=A7=81=ED=95=9C=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=84=EC=97=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignments/AssignmentFormLayout.tsx | 8 +++-- .../assignments/AssignmentPageLayout.tsx | 13 ++++---- .../CourseOverview/CourseActionsBar.tsx | 10 ++---- .../common/Dashboard/CourseList.tsx | 10 ++++-- .../assignments/AssignmentCreatePage.tsx | 11 ++++--- src/pages/common/LandingPage.tsx | 32 +++++++++++-------- src/pages/common/UserIdInputPage.tsx | 22 +++++++------ 7 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/components/admin/assignments/AssignmentFormLayout.tsx b/src/components/admin/assignments/AssignmentFormLayout.tsx index d7c5697..86e18db 100644 --- a/src/components/admin/assignments/AssignmentFormLayout.tsx +++ b/src/components/admin/assignments/AssignmentFormLayout.tsx @@ -24,8 +24,12 @@ const AssignmentFormLayout = ({ {/* 하단 버튼 */}
- +
diff --git a/src/components/admin/assignments/AssignmentPageLayout.tsx b/src/components/admin/assignments/AssignmentPageLayout.tsx index e72355a..325c153 100644 --- a/src/components/admin/assignments/AssignmentPageLayout.tsx +++ b/src/components/admin/assignments/AssignmentPageLayout.tsx @@ -32,15 +32,14 @@ const AssignmentPageLayout = ({
{!selectMode && ( - )}
- +
diff --git a/src/components/common/CourseOverview/CourseActionsBar.tsx b/src/components/common/CourseOverview/CourseActionsBar.tsx index 1c2bc9c..b7344ef 100644 --- a/src/components/common/CourseOverview/CourseActionsBar.tsx +++ b/src/components/common/CourseOverview/CourseActionsBar.tsx @@ -3,14 +3,8 @@ import Button from '../Button'; const CourseActionsBar = ({isActive}: {isActive: boolean}) => { return (
- +
); }; diff --git a/src/components/common/Dashboard/CourseList.tsx b/src/components/common/Dashboard/CourseList.tsx index 403e373..e1c9577 100644 --- a/src/components/common/Dashboard/CourseList.tsx +++ b/src/components/common/Dashboard/CourseList.tsx @@ -2,6 +2,7 @@ import logo from '../../../assets/images/snowCode_logo_mini.svg'; import CourseCard from './CourseCard'; import {AddIcon} from '../../../assets/svg'; import type {Course, UserType} from './types'; +import Button from '../Button'; const courses: Course[] = [ { @@ -45,10 +46,13 @@ const CourseList = ({userType}: CourseListProps) => { 강의 목록 {userType === 'admin' && ( - + 추가 + )}
diff --git a/src/pages/admin/assignments/AssignmentCreatePage.tsx b/src/pages/admin/assignments/AssignmentCreatePage.tsx index 0d4180d..1b27a2b 100644 --- a/src/pages/admin/assignments/AssignmentCreatePage.tsx +++ b/src/pages/admin/assignments/AssignmentCreatePage.tsx @@ -3,6 +3,8 @@ import LabeledInput from '@/components/admin/form/LabeledInput'; import FileUpload from '@/components/admin/form/FileUpload'; import {useState} from 'react'; import LabeledDropdown from '@/components/admin/form/LabeledDropdown'; +import Button from '@/components/common/Button'; +import {AddIcon} from '@/assets/svg'; const AssignmentCreatePage = () => { const [examples, setExamples] = useState([{input: '', output: '', 공개: ''}]); @@ -58,11 +60,10 @@ const AssignmentCreatePage = () => { ))}
- + {}} diff --git a/src/pages/common/LandingPage.tsx b/src/pages/common/LandingPage.tsx index 539aba3..fac5388 100644 --- a/src/pages/common/LandingPage.tsx +++ b/src/pages/common/LandingPage.tsx @@ -6,7 +6,7 @@ import snowCodeStudent from '/src/assets/images/snowCode_student.svg'; import snowCodeAdmin from '/src/assets/images/snowCode_admin.svg'; import googleLogo from '/src/assets/images/google_logo.svg'; import {ArrowrightIcon} from '../../assets/svg'; -import ActionButton from '../../components/common/ActionButton'; +import Button from '@/components/common/Button'; type HoverState = 'none' | 'student' | 'admin'; @@ -39,11 +39,13 @@ export default function LandingPage() { {/* 상단 오른쪽 "다음으로" 버튼 */}
- +
{/* 로고 이미지 (선택/호버에 따라 이미지 변경) */} @@ -68,20 +70,22 @@ export default function LandingPage() {
- setSelected('student')} onMouseEnter={() => setHover('student')} - onMouseLeave={() => selected === 'none' && setHover('none')} - selected={selected === 'student'} - /> - selected === 'none' && setHover('none')}> + 학생 + +
diff --git a/src/pages/common/UserIdInputPage.tsx b/src/pages/common/UserIdInputPage.tsx index 1b241b7..d14d8fe 100644 --- a/src/pages/common/UserIdInputPage.tsx +++ b/src/pages/common/UserIdInputPage.tsx @@ -1,8 +1,8 @@ import {useState, useRef, useEffect} from 'react'; import {useNavigate, useLocation} from 'react-router-dom'; -import ActionButton from '../../components/common/ActionButton'; import SnowCodeEntryMini from '../../assets/images/snowCode_entry_mini.svg'; import {ArrowleftIcon} from '../../assets/svg'; +import Button from '@/components/common/Button'; export default function UserIdInputPage() { const navigate = useNavigate(); @@ -78,13 +78,14 @@ export default function UserIdInputPage() {
- +
-
SnowCode Entry Mini
@@ -121,13 +122,14 @@ export default function UserIdInputPage() { /> ))}
- - + className='disabled:cursor-not-allowed'> + 확인 + ); From 2993ec36e8f0eeab09d7d6a9063f94c5685ad4f8 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 22:38:26 +0900 Subject: [PATCH 27/42] =?UTF-8?q?#21=20feat:=20Header=EC=97=90=20IconButto?= =?UTF-8?q?n=20=EB=8C=80=EC=8B=A0=20Button=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/Layout.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx index ea5f579..1abf2c6 100644 --- a/src/layout/Layout.tsx +++ b/src/layout/Layout.tsx @@ -2,7 +2,7 @@ import {useLocation, Outlet} from 'react-router-dom'; import BaseHeader from '../components/common/BaseHeader'; import {NotificationIcon, SignoutIcon, UserIcon, ChatIcon} from '../assets/svg'; -import IconButton from '../components/common/IconButton'; +import Button from '@/components/common/Button'; const Layout = () => { const location = useLocation(); @@ -42,7 +42,9 @@ const Layout = () => { ); From 27fbea93f008805cc2dfbaa0406f8e3ac25d588b Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 22:39:38 +0900 Subject: [PATCH 28/42] =?UTF-8?q?#21=20feat:=20Button=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20variant=20=EC=86=8D=EC=84=B1=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Button.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 92f64f0..1812ea9 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -17,10 +17,10 @@ const button = tv({ default: 'w-24 h-10 px-3 py-1.5', compact: 'w-fit leading-5 px-3 py-1.5', wide: 'w-40 py-[15px]', - none: 'w-fit p-0', + none: 'p-0', }, isIcon: { - true: 'rounded-full', + true: 'rounded-full w-16 h-16', }, }, defaultVariants: { From 194434eaf352ee78deedb2e7571ccc23e12d7001 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 23:26:03 +0900 Subject: [PATCH 29/42] =?UTF-8?q?#21=20refactor:=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20color=20/=20size=20/=20c?= =?UTF-8?q?ontent=20variant=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=20=EB=B0=8F=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Button.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 1812ea9..34bdec6 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -1,7 +1,7 @@ import {tv, type VariantProps} from 'tailwind-variants/lite'; const button = tv({ - base: 'cursor-pointer flex-center text-center gap-2 text-base font-medium whitespace-nowrap rounded-[10px] border', + base: 'cursor-pointer rounded-[10px] border', variants: { color: { primary: 'bg-primary text-white border-primary', @@ -15,19 +15,22 @@ const button = tv({ }, size: { default: 'w-24 h-10 px-3 py-1.5', - compact: 'w-fit leading-5 px-3 py-1.5', + compact: 'w-fit px-3 py-1.5 leading-5', wide: 'w-40 py-[15px]', - none: 'p-0', + none: 'w-fit p-0', + icon: 'w-16 h-16 p-0 rounded-full', // 아이콘 버튼 rounded 속성 적용 }, - isIcon: { - true: 'rounded-full w-16 h-16', + content: { + text: 'text-center text-base font-medium whitespace-nowrap', + icon: 'flex-center', + mixed: + 'flex-center gap-2 text-center text-base font-medium whitespace-nowrap', }, }, defaultVariants: { color: 'primary', size: 'default', - disabled: false, - isIcon: false, + content: 'text', }, }); From 39a5da2b139dfb75b13fbe2dfdc1486496440441 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 30 Dec 2025 23:26:32 +0900 Subject: [PATCH 30/42] =?UTF-8?q?##21=20feat:=20content=20=EC=86=8D?= =?UTF-8?q?=EC=84=B1=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/admin/assignments/AssignmentPageLayout.tsx | 2 +- src/layout/Layout.tsx | 6 +++++- src/pages/admin/assignments/AssignmentCreatePage.tsx | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/admin/assignments/AssignmentPageLayout.tsx b/src/components/admin/assignments/AssignmentPageLayout.tsx index 325c153..2df8863 100644 --- a/src/components/admin/assignments/AssignmentPageLayout.tsx +++ b/src/components/admin/assignments/AssignmentPageLayout.tsx @@ -32,7 +32,7 @@ const AssignmentPageLayout = ({
{!selectMode && ( - diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx index 1abf2c6..ad1aa54 100644 --- a/src/layout/Layout.tsx +++ b/src/layout/Layout.tsx @@ -42,7 +42,11 @@ const Layout = () => {
- From fd763bb42feaa2d7fa61976ad0e2fe24baf1ea28 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Thu, 1 Jan 2026 21:32:25 +0900 Subject: [PATCH 31/42] =?UTF-8?q?#21=20feat:=20tailwind-variants=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20Badge=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Badge.tsx | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/components/common/Badge.tsx diff --git a/src/components/common/Badge.tsx b/src/components/common/Badge.tsx new file mode 100644 index 0000000..19d4e4a --- /dev/null +++ b/src/components/common/Badge.tsx @@ -0,0 +1,84 @@ +import {tv, type VariantProps} from 'tailwind-variants/lite'; + +const badgeStyles = tv({ + base: 'rounded-[35px] px-3.5 py-1.5 text-center text-base font-medium border whitespace-nowrap', +}); + +const scheduleBadgeStyles = tv({ + extend: badgeStyles, + variants: { + schedule: { + upcoming: + 'bg-radial-[50%_50%_at_50%_50%] from-[#7D63FF] from-38% to-[#AB9AFF] to-100% border-0 text-white', + later: 'bg-[#403D4D] border-[#5C5B7F] text-white', + }, + }, +}); + +const submissionBadgeStyles = tv({ + extend: badgeStyles, + base: 'bg-transparent flex-center', + variants: { + status: { + correct: 'border-primary text-primary', + incorrect: 'border-[#FF6F6F] text-[#FF6F6F]', + pending: 'border-light-black text-light-black', + }, + onlyIcon: { + true: 'rounded-full', + false: 'gap-2', + }, + }, + defaultVariants: { + onlyIcon: false, + }, +}); + +const indexBadgeStyles = tv({ + extend: badgeStyles, + variants: { + kind: { + unit: 'bg-secondary-black border-secondary-black text-white', + problem: 'bg-light-black border-light-black text-white', + }, + }, +}); + +type ScheduleBadgeProps = { + variant: 'schedule'; +} & VariantProps; + +type SubmissionBadgeProps = { + variant: 'submission'; +} & VariantProps; + +type IndexBadgeProps = { + variant: 'index'; +} & VariantProps; + +type BadgeProps = ( + | ScheduleBadgeProps + | SubmissionBadgeProps + | IndexBadgeProps +) & { + children: React.ReactNode; + className?: string; +}; + +const Badge = ({children, variant, className, ...props}: BadgeProps) => { + const renderBadgeVariant = () => { + if (variant === 'schedule') { + // 일정 배지 + return scheduleBadgeStyles({className, ...props}); + } else if (variant === 'submission') { + // 제출 상태 배지 + return submissionBadgeStyles({className, ...props}); + } else if (variant === 'index') { + // 인덱스 배지 (단원, 문제 등) + return indexBadgeStyles({className, ...props}); + } + }; + return {children}; +}; + +export default Badge; From 381dc0f98018cc4782c77223812e85eb081e101b Mon Sep 17 00:00:00 2001 From: suminb99 Date: Thu, 1 Jan 2026 21:48:36 +0900 Subject: [PATCH 32/42] =?UTF-8?q?#21=20style:=20Badge=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20leading=20=EC=86=8D=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Badge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/Badge.tsx b/src/components/common/Badge.tsx index 19d4e4a..b45d542 100644 --- a/src/components/common/Badge.tsx +++ b/src/components/common/Badge.tsx @@ -1,7 +1,7 @@ import {tv, type VariantProps} from 'tailwind-variants/lite'; const badgeStyles = tv({ - base: 'rounded-[35px] px-3.5 py-1.5 text-center text-base font-medium border whitespace-nowrap', + base: 'rounded-full px-3.5 py-1.5 leading-[19px] text-center text-base font-medium border whitespace-nowrap', }); const scheduleBadgeStyles = tv({ From 9c4624db18e58c050e68f1590ce51f9c0a62f215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:55:42 +0900 Subject: [PATCH 33/42] =?UTF-8?q?#13=20fix:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/CourseOverview/EmptyCourse.tsx | 2 +- .../assignments/AssignmentCreatePage.tsx | 2 +- .../assignments/AssignmentSelectPage.tsx | 19 ------------------- .../admin/assignments/AssignmentsPage.tsx | 4 ++-- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/components/common/CourseOverview/EmptyCourse.tsx b/src/components/common/CourseOverview/EmptyCourse.tsx index e2aca67..613e118 100644 --- a/src/components/common/CourseOverview/EmptyCourse.tsx +++ b/src/components/common/CourseOverview/EmptyCourse.tsx @@ -11,7 +11,7 @@ const EmptyCourse = () => { 아직 생성된 단원이 없어요 - + ); }; diff --git a/src/pages/admin/assignments/AssignmentCreatePage.tsx b/src/pages/admin/assignments/AssignmentCreatePage.tsx index 0d4180d..b8cb713 100644 --- a/src/pages/admin/assignments/AssignmentCreatePage.tsx +++ b/src/pages/admin/assignments/AssignmentCreatePage.tsx @@ -35,7 +35,7 @@ const AssignmentCreatePage = () => { className='w-full' />
- {examples.map((ex, idx) => ( + {examples.map((_, idx) => (
diff --git a/src/pages/admin/assignments/AssignmentSelectPage.tsx b/src/pages/admin/assignments/AssignmentSelectPage.tsx index f79d1ae..32b9304 100644 --- a/src/pages/admin/assignments/AssignmentSelectPage.tsx +++ b/src/pages/admin/assignments/AssignmentSelectPage.tsx @@ -1,7 +1,5 @@ import {coursesResponse} from '../../../components/admin/assignments/dummy/response'; -import {useState} from 'react'; import {useParams} from 'react-router-dom'; -import type {Assignment} from '../../../components/admin/assignments/dummy/types'; import AssignmentPageLayout from '../../../components/admin/assignments/AssignmentPageLayout'; const AssignmentSelectPage = () => { @@ -18,28 +16,11 @@ const AssignmentSelectPage = () => { (course) => course.title === courseTitle ); - // 선택된 문제 업데이트 - const [linkedAssignments, setLinkedAssignments] = useState([]); - - const onLinkAssignments = ( - id: number, - title: string, - isSelected: boolean - ) => { - setLinkedAssignments((prev) => { - if (isSelected) { - return [...prev, {id, title}]; - } - return prev.filter((a) => a.id !== id); - }); - }; - return ( ); }; diff --git a/src/pages/admin/assignments/AssignmentsPage.tsx b/src/pages/admin/assignments/AssignmentsPage.tsx index 4f3b0a7..965388b 100644 --- a/src/pages/admin/assignments/AssignmentsPage.tsx +++ b/src/pages/admin/assignments/AssignmentsPage.tsx @@ -1,5 +1,5 @@ -import {coursesResponse} from '../../components/admin/assignments/dummy/response'; -import AssignmentPageLayout from '../../components/admin/assignments/AssignmentPageLayout'; +import {coursesResponse} from '../../../components/admin/assignments/dummy/response'; +import AssignmentPageLayout from '../../../components/admin/assignments/AssignmentPageLayout'; const AssignmentsPage = () => { // 전체 강의 가져오기 From 631b4ba24ed2403f89706a2a19763e3435fc46d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:56:45 +0900 Subject: [PATCH 34/42] =?UTF-8?q?#13=20refactor:=20svgr=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20SVG=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=A7=81=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 15 +- src/assets/svg/AddIcon.tsx | 17 -- src/assets/svg/ArrowdownIcon.tsx | 17 -- src/assets/svg/ArrowleftIcon.tsx | 15 -- src/assets/svg/ArrowrightIcon.tsx | 15 -- src/assets/svg/ChatIcon.tsx | 18 -- src/assets/svg/Chevrondown.tsx | 17 -- src/assets/svg/DeleteIcon.tsx | 17 -- src/assets/svg/DragAndDropIcon.tsx | 17 -- src/assets/svg/EditIcon.tsx | 18 -- src/assets/svg/EllipsisIcon.tsx | 14 -- src/assets/svg/File.tsx | 22 --- src/assets/svg/NotificationIcon.tsx | 15 -- src/assets/svg/ProfileImage.tsx | 183 ------------------ src/assets/svg/Search.tsx | 18 -- src/assets/svg/SignoutIcon.tsx | 19 -- src/assets/svg/SingleEllipsisIcon.tsx | 11 -- src/assets/svg/UserIcon.tsx | 18 -- src/assets/svg/index.ts | 17 -- .../admin/assignments/AssignmentCard.tsx | 10 +- .../assignments/AssignmentPageLayout.tsx | 2 +- .../admin/assignments/CourseSelector.tsx | 2 +- src/components/admin/form/FileUpload.tsx | 4 +- src/components/admin/form/LabeledDropdown.tsx | 2 +- src/components/common/Button.tsx | 3 +- .../CourseOverview/CourseActionsBar.tsx | 16 +- .../common/CourseOverview/CourseHero.tsx | 6 +- .../common/CourseOverview/CourseStat.tsx | 2 +- .../common/Dashboard/CourseCard.tsx | 2 +- .../common/Dashboard/CourseList.tsx | 2 +- src/components/common/IconButton.tsx | 2 +- .../admin/student/studentManagementPage.tsx | 8 +- src/pages/common/LandingPage.tsx | 2 +- src/pages/common/UserIdInputPage.tsx | 2 +- 34 files changed, 50 insertions(+), 498 deletions(-) delete mode 100644 src/assets/svg/AddIcon.tsx delete mode 100644 src/assets/svg/ArrowdownIcon.tsx delete mode 100644 src/assets/svg/ArrowleftIcon.tsx delete mode 100644 src/assets/svg/ArrowrightIcon.tsx delete mode 100644 src/assets/svg/ChatIcon.tsx delete mode 100644 src/assets/svg/Chevrondown.tsx delete mode 100644 src/assets/svg/DeleteIcon.tsx delete mode 100644 src/assets/svg/DragAndDropIcon.tsx delete mode 100644 src/assets/svg/EditIcon.tsx delete mode 100644 src/assets/svg/EllipsisIcon.tsx delete mode 100644 src/assets/svg/File.tsx delete mode 100644 src/assets/svg/NotificationIcon.tsx delete mode 100644 src/assets/svg/ProfileImage.tsx delete mode 100644 src/assets/svg/Search.tsx delete mode 100644 src/assets/svg/SignoutIcon.tsx delete mode 100644 src/assets/svg/SingleEllipsisIcon.tsx delete mode 100644 src/assets/svg/UserIcon.tsx delete mode 100644 src/assets/svg/index.ts diff --git a/src/App.tsx b/src/App.tsx index 0830cfa..313d1b5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,11 +3,12 @@ import Layout from './layout/Layout'; import LandingPage from './pages/common/LandingPage'; import UserIdInputPage from './pages/common/UserIdInputPage'; import Dashboard from './pages/common/Dashboard'; -import AssignmentsPage from './pages/admin/AssignmentsPage'; -import AssignmentSelectPage from './pages/admin/AssignmentSelectPage'; +import AssignmentsPage from './pages/admin/assignments/AssignmentsPage'; +import AssignmentSelectPage from './pages/admin/assignments/AssignmentSelectPage'; import CourseOverviewPage from './pages/common/CourseOverviewPage'; import AssignmentCreatePage from './pages/admin/assignments/AssignmentCreatePage'; import CourseCreatePage from './pages/admin/courses/CourseCreatePage'; +import StudentManagementPage from './pages/admin/student/studentManagementPage'; function App() { return ( @@ -16,7 +17,7 @@ function App() { {/* 공통 영역 */} }> } /> - } /> + } /> {/* 학생 전용 영역 */} @@ -31,13 +32,11 @@ function App() { {/* 추가 페이지들 */} } /> } /> + } /> } /> } /> - } - /> - } /> + } /> + } /> diff --git a/src/assets/svg/AddIcon.tsx b/src/assets/svg/AddIcon.tsx deleted file mode 100644 index 71637e0..0000000 --- a/src/assets/svg/AddIcon.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgAddIcon = (props: SVGProps) => ( - - - -); -export default SvgAddIcon; diff --git a/src/assets/svg/ArrowdownIcon.tsx b/src/assets/svg/ArrowdownIcon.tsx deleted file mode 100644 index 6f96561..0000000 --- a/src/assets/svg/ArrowdownIcon.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgArrowdownIcon = (props: SVGProps) => ( - - - -); -export default SvgArrowdownIcon; diff --git a/src/assets/svg/ArrowleftIcon.tsx b/src/assets/svg/ArrowleftIcon.tsx deleted file mode 100644 index 4793458..0000000 --- a/src/assets/svg/ArrowleftIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgArrowleftIcon = (props: SVGProps) => ( - - - -); -export default SvgArrowleftIcon; diff --git a/src/assets/svg/ArrowrightIcon.tsx b/src/assets/svg/ArrowrightIcon.tsx deleted file mode 100644 index c0a119e..0000000 --- a/src/assets/svg/ArrowrightIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgArrowrightIcon = (props: SVGProps) => ( - - - -); -export default SvgArrowrightIcon; diff --git a/src/assets/svg/ChatIcon.tsx b/src/assets/svg/ChatIcon.tsx deleted file mode 100644 index e3f8a0f..0000000 --- a/src/assets/svg/ChatIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgChatIcon = (props: SVGProps) => ( - - - -); -export default SvgChatIcon; diff --git a/src/assets/svg/Chevrondown.tsx b/src/assets/svg/Chevrondown.tsx deleted file mode 100644 index e5bd8cb..0000000 --- a/src/assets/svg/Chevrondown.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgChevrondown = (props: SVGProps) => ( - - - -); -export default SvgChevrondown; diff --git a/src/assets/svg/DeleteIcon.tsx b/src/assets/svg/DeleteIcon.tsx deleted file mode 100644 index fce305c..0000000 --- a/src/assets/svg/DeleteIcon.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgDeleteIcon = (props: SVGProps) => ( - - - -); -export default SvgDeleteIcon; diff --git a/src/assets/svg/DragAndDropIcon.tsx b/src/assets/svg/DragAndDropIcon.tsx deleted file mode 100644 index 6fda321..0000000 --- a/src/assets/svg/DragAndDropIcon.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgDragAndDropIcon = (props: SVGProps) => ( - - - - - - - - -); -export default SvgDragAndDropIcon; diff --git a/src/assets/svg/EditIcon.tsx b/src/assets/svg/EditIcon.tsx deleted file mode 100644 index 1bd6f31..0000000 --- a/src/assets/svg/EditIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgEditIcon = (props: SVGProps) => ( - - - -); -export default SvgEditIcon; diff --git a/src/assets/svg/EllipsisIcon.tsx b/src/assets/svg/EllipsisIcon.tsx deleted file mode 100644 index e679841..0000000 --- a/src/assets/svg/EllipsisIcon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgEllipsisIcon = (props: SVGProps) => ( - - - - - -); -export default SvgEllipsisIcon; diff --git a/src/assets/svg/File.tsx b/src/assets/svg/File.tsx deleted file mode 100644 index ddef665..0000000 --- a/src/assets/svg/File.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgFile = (props: SVGProps) => ( - - - - -); -export default SvgFile; diff --git a/src/assets/svg/NotificationIcon.tsx b/src/assets/svg/NotificationIcon.tsx deleted file mode 100644 index 3b7f13e..0000000 --- a/src/assets/svg/NotificationIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgNotificationIcon = (props: SVGProps) => ( - - - -); -export default SvgNotificationIcon; diff --git a/src/assets/svg/ProfileImage.tsx b/src/assets/svg/ProfileImage.tsx deleted file mode 100644 index 6fb793f..0000000 --- a/src/assets/svg/ProfileImage.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgProfileImage = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgProfileImage; diff --git a/src/assets/svg/Search.tsx b/src/assets/svg/Search.tsx deleted file mode 100644 index 216d65f..0000000 --- a/src/assets/svg/Search.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgSearch = (props: SVGProps) => ( - - - -); -export default SvgSearch; diff --git a/src/assets/svg/SignoutIcon.tsx b/src/assets/svg/SignoutIcon.tsx deleted file mode 100644 index feba1ab..0000000 --- a/src/assets/svg/SignoutIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgSignoutIcon = (props: SVGProps) => ( - - - - -); -export default SvgSignoutIcon; diff --git a/src/assets/svg/SingleEllipsisIcon.tsx b/src/assets/svg/SingleEllipsisIcon.tsx deleted file mode 100644 index 2194480..0000000 --- a/src/assets/svg/SingleEllipsisIcon.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type {SVGProps} from 'react'; -const SvgSingleEllipsisIcon = (props: SVGProps) => ( - - - -); -export default SvgSingleEllipsisIcon; diff --git a/src/assets/svg/UserIcon.tsx b/src/assets/svg/UserIcon.tsx deleted file mode 100644 index e2cc120..0000000 --- a/src/assets/svg/UserIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import type {SVGProps} from 'react'; -const SvgUserIcon = (props: SVGProps) => ( - - - - -); -export default SvgUserIcon; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts deleted file mode 100644 index fa8243b..0000000 --- a/src/assets/svg/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -export {default as AddIcon} from './AddIcon'; -export {default as ArrowdownIcon} from './ArrowdownIcon'; -export {default as ArrowleftIcon} from './ArrowleftIcon'; -export {default as ArrowrightIcon} from './ArrowrightIcon'; -export {default as ChatIcon} from './ChatIcon'; -export {default as Chevrondown} from './Chevrondown'; -export {default as DeleteIcon} from './DeleteIcon'; -export {default as DragAndDropIcon} from './DragAndDropIcon'; -export {default as EditIcon} from './EditIcon'; -export {default as EllipsisIcon} from './EllipsisIcon'; -export {default as File} from './File'; -export {default as NotificationIcon} from './NotificationIcon'; -export {default as ProfileImage} from './ProfileImage'; -export {default as Search} from './Search'; -export {default as SignoutIcon} from './SignoutIcon'; -export {default as SingleEllipsisIcon} from './SingleEllipsisIcon'; -export {default as UserIcon} from './UserIcon'; diff --git a/src/components/admin/assignments/AssignmentCard.tsx b/src/components/admin/assignments/AssignmentCard.tsx index 75b3727..264cb48 100644 --- a/src/components/admin/assignments/AssignmentCard.tsx +++ b/src/components/admin/assignments/AssignmentCard.tsx @@ -1,10 +1,8 @@ import {useState} from 'react'; -import { - SingleEllipsisIcon, - DragAndDropIcon, - DeleteIcon, - EditIcon, -} from '../../../assets/svg'; +import SingleEllipsisIcon from '/public/svg/singleEllipsisIcon.svg?react'; +import DragAndDropIcon from '/public/svg/dragAndDropIcon.svg?react'; +import DeleteIcon from '/public/svg/deleteIcon.svg?react'; +import EditIcon from '/public/svg/editIcon.svg?react'; import type {Assignment} from './dummy/types'; interface AssignmentCardProps extends Assignment { diff --git a/src/components/admin/assignments/AssignmentPageLayout.tsx b/src/components/admin/assignments/AssignmentPageLayout.tsx index e72355a..be99646 100644 --- a/src/components/admin/assignments/AssignmentPageLayout.tsx +++ b/src/components/admin/assignments/AssignmentPageLayout.tsx @@ -2,7 +2,7 @@ import CourseSelector from './CourseSelector'; import AssignmentList from './AssignmentList'; import Button from '../../common/Button'; import type {Course} from './dummy/types'; -import {AddIcon} from '../../../assets/svg'; +import AddIcon from '/public/svg/addIcon.svg?react'; import {useState} from 'react'; interface AssignmentPageLayoutProps { diff --git a/src/components/admin/assignments/CourseSelector.tsx b/src/components/admin/assignments/CourseSelector.tsx index db13060..c1fdb46 100644 --- a/src/components/admin/assignments/CourseSelector.tsx +++ b/src/components/admin/assignments/CourseSelector.tsx @@ -1,5 +1,5 @@ import {useState} from 'react'; -import {ArrowdownIcon} from '../../../assets/svg'; +import ArrowdownIcon from '/public/svg/arrowdownIcon.svg?react'; import type {Course} from './dummy/types'; interface CourseSelectorProps { diff --git a/src/components/admin/form/FileUpload.tsx b/src/components/admin/form/FileUpload.tsx index 8b817e6..c119837 100644 --- a/src/components/admin/form/FileUpload.tsx +++ b/src/components/admin/form/FileUpload.tsx @@ -1,6 +1,6 @@ import {useRef, useState} from 'react'; import type {ChangeEvent} from 'react'; -import {File} from '@/assets/svg'; +import FileIcon from '/public/svg/file.svg?react'; type FileUploadProps = { label: string; @@ -103,7 +103,7 @@ export default function FileUpload({ 'focus:outline-none focus:ring-2 focus:ring-primary', ].join(' ')}>
- +
{description}
diff --git a/src/components/admin/form/LabeledDropdown.tsx b/src/components/admin/form/LabeledDropdown.tsx index 680d2df..de05fef 100644 --- a/src/components/admin/form/LabeledDropdown.tsx +++ b/src/components/admin/form/LabeledDropdown.tsx @@ -1,5 +1,5 @@ import {useState, useRef} from 'react'; -import {Chevrondown} from '@/assets/svg'; +import Chevrondown from '/public/svg/chevrondown.svg?react'; import useClickOutside from '@/hooks/useClickOutside'; interface LabeledDropdownProps diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 4be7e34..9ff4b1c 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -16,9 +16,10 @@ const buttonTheme: ButtonTheme = { secondaryPurpleStroke: 'secondary-btn bg-purple-stroke text-secondary-black', }; -const Button = ({theme, text, icon}: ButtonProps) => { +const Button = ({theme, text, icon, onClick}: ButtonProps) => { return ( ; }; -export default IconButton; +export default IconButton; \ No newline at end of file diff --git a/src/pages/admin/student/studentManagementPage.tsx b/src/pages/admin/student/studentManagementPage.tsx index 520304d..3659dfa 100644 --- a/src/pages/admin/student/studentManagementPage.tsx +++ b/src/pages/admin/student/studentManagementPage.tsx @@ -1,11 +1,15 @@ import AssignmentFormLayout from '@/components/admin/assignments/AssignmentFormLayout'; -import {Search} from '@/assets/svg'; +import Search from '/public/svg/search.svg?react'; +import {useSearchParams} from 'react-router-dom'; export default function StudentManagementPage() { + const [searchParams] = useSearchParams(); + const course = searchParams.get('course'); + const titleExtra = (
Date: Tue, 6 Jan 2026 14:57:32 +0900 Subject: [PATCH 35/42] =?UTF-8?q?refactor:=20Layout=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20SVG=20import=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/Layout.tsx | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx index ea5f579..d55051e 100644 --- a/src/layout/Layout.tsx +++ b/src/layout/Layout.tsx @@ -1,8 +1,19 @@ // Layout.js import {useLocation, Outlet} from 'react-router-dom'; import BaseHeader from '../components/common/BaseHeader'; -import {NotificationIcon, SignoutIcon, UserIcon, ChatIcon} from '../assets/svg'; +import NotificationIcon from '/public/svg/notificationIcon.svg?react'; +import SignoutIcon from '/public/svg/signoutIcon.svg?react'; +import UserIcon from '/public/svg/userIcon.svg?react'; +import ChatIcon from '/public/svg/chatIcon.svg?react'; import IconButton from '../components/common/IconButton'; +import React from 'react'; + +type UserType = 'admin' | 'student'; + +interface NavButton { + icon: React.ReactElement; + label: string; +} const Layout = () => { const location = useLocation(); @@ -13,8 +24,8 @@ const Layout = () => { const showHeader = !noHeaderPages.includes(pathname); // 네비게이션 아이콘 버튼들 - const getNavigationButtons = (userType) => { - const commonButtons = [ + const getNavigationButtons = (userType: UserType): NavButton[] => { + const commonButtons: NavButton[] = [ {icon: , label: '알림'}, {icon: , label: '프로필'}, {icon: , label: '로그아웃'}, @@ -31,14 +42,20 @@ const Layout = () => { }; // 사용자 환영 메시지 - const getWelcomeMessage = (userName) => ( + const getWelcomeMessage = (userName: string) => ( {userName}님 환영합니다! ); // 네비게이션 컴포넌트 - const NavigationBar = ({buttons, width}) => ( + const NavigationBar = ({ + buttons, + width, + }: { + buttons: NavButton[]; + width: string; + }) => (
); }; diff --git a/src/components/common/CourseOverview/UnitHeader.tsx b/src/components/common/CourseOverview/UnitHeader.tsx index e46f5ef..3f3bf47 100644 --- a/src/components/common/CourseOverview/UnitHeader.tsx +++ b/src/components/common/CourseOverview/UnitHeader.tsx @@ -1,5 +1,5 @@ -import UnitLabel from './UnitLabel'; -import lock from '../../../assets/images/lock.svg'; +import lock from '@/assets/images/lock.svg'; +import Badge from '../Badge'; interface UnitHeaderProps { index: number; @@ -19,7 +19,9 @@ const UnitHeader = ({ return (
- + + {index} + {title} diff --git a/src/components/common/CourseOverview/UnitLabel.tsx b/src/components/common/CourseOverview/UnitLabel.tsx deleted file mode 100644 index 71ba934..0000000 --- a/src/components/common/CourseOverview/UnitLabel.tsx +++ /dev/null @@ -1,13 +0,0 @@ -interface UnitLabelProps { - unitNo: number; -} - -const UnitLabel = ({unitNo}: UnitLabelProps) => { - return ( -
- {`${unitNo}단원`} -
- ); -}; - -export default UnitLabel; diff --git a/src/components/common/Dashboard/ScheduleCard.tsx b/src/components/common/Dashboard/ScheduleCard.tsx index 5d9ff04..17b2de1 100644 --- a/src/components/common/Dashboard/ScheduleCard.tsx +++ b/src/components/common/Dashboard/ScheduleCard.tsx @@ -1,3 +1,4 @@ +import Badge from '../Badge'; import type {Assignment} from './types'; interface ScheduleCardProps extends Assignment { remainingDays: number; @@ -11,14 +12,11 @@ const ScheduleCard = ({ }: ScheduleCardProps) => { return (
-
- {`${remainingDays}일 전`} -
+ + {remainingDays} +

{`${course} (${section})`}

From 204867d1c4caebc8e8749790ba8cacf0c129133a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=98=EC=A7=80=EB=AF=BC?= <163178666+JiiminHa@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:04:57 +0900 Subject: [PATCH 42/42] =?UTF-8?q?#13=20feat:=20coderabbit=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .coderabbit.yaml | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..c4c61b3 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,123 @@ +language: 'ko-KR' +early_access: true + +tone_instructions: | + 1. 피드백은 명확하고 구체적으로 작성하고, 문제 원인과 개선 방법을 함께 제시하세요. + 2. 리뷰는 교육적인 방향을 지향하며, 관련 공식 문서 링크를 함께 추천하세요. + 3. 비판보다는 개선 제안을 우선하세요. + 4. 칭찬은 짧고 위트 있게 작성하세요. + +reviews: + profile: educational + request_changes_workflow: true + high_level_summary: true + high_level_summary_placeholder: | + @coderabbitai summary + (졸업작품 평가 기준: 구조 / 타입 안정성 / 접근성 관점 요약) + poem: false + review_status: true + suggested_labels: true + commit_status: true + fail_commit_status: false + abort_on_close: true + high_level_summary_in_walkthrough: true + high_level_summary_placeholder: '@coderabbitai summary' + collapse_walkthrough: true + + path_instructions: + - path: 'src/**/components/**/*.tsx' + instructions: | + React 컴포넌트 리뷰 시: + - 파일명은 PascalCase인지 확인 + - Props 타입은 컴포넌트명Props 형식인지 확인 + - 단일 export는 default export 권장 (팀 규칙 우선) + - 이벤트 핸들러는 handle 접두사 사용 + - 접근성 고려 (label 연결, aria-* 속성) + - 접근성 관련 지적 시, 사용자 영향 예시 포함 + - 불필요한 re-render 방지를 위한 memo/useCallback 검토 + - premature optimization 지양 + + - path: 'src/**/hooks/**/*.ts' + instructions: | + 커스텀 훅 리뷰 시: + - 파일명과 함수명은 use* 접두사 사용 + - 반환 타입의 일관성 유지 (object 또는 tuple) + - 에러/로딩 상태 명확히 분리 + - TanStack Query 훅 네이밍 규칙 준수 + (use + Action + Target + Query/Mutation) + + - path: 'src/**/*.ts' + instructions: | + TypeScript 코드 리뷰 시: + - 타입/인터페이스는 PascalCase 사용 + - interface는 객체 타입, type은 union/alias에 사용 + - API 응답 타입은 XXXResponse 네이밍 + - var 사용 금지, const 우선 + - type-only import 적극 사용 + - any 사용 시 사유 명확히 작성 + + - path: 'src/**' + instructions: | + 프로젝트 전반 리뷰 기준: + + # 기본 기술 스택 + - React + TypeScript 사용 + - 스타일링은 Tailwind CSS v4 사용 + - 절대 경로 임포트(@/...) 사용 + - Client / Server 컴포넌트 구분 명확 + + # 네이밍 컨벤션 + - 컴포넌트: PascalCase + - 폴더명: kebab-case + - 일반 파일명: kebab-case + - 변수/함수: camelCase + - 상수: BIG_SNAKE_CASE + - Props 타입: 컴포넌트명Props + + # 환경 변수 + - 환경 변수는 process.env.NEXT_PUBLIC_* 사용 + - 민감 정보 하드코딩 금지 + + # 성능 + - 불필요한 클라이언트 컴포넌트 사용 지양 + - 리스트 렌더링 시 key 안정성 확인 + + # Git 규칙 + - 커밋 메시지: #issue type: subject + (예: #12 feat: 로그인 기능 추가) + - 브랜치 패턴: type/issuenumber-issue-short-description + (예: feat/1-auth) + + auto_review: + enabled: true + drafts: false + base_branches: + - 'main' + - 'develop' + labels: + - '!wip' + - '!draft' + ignore_usernames: + - 'dependabot' + - 'renovate' + + tools: + eslint: + enabled: true + markdownlint: + enabled: true + gitleaks: + enabled: true + yamllint: + enabled: true + actionlint: + enabled: true + +chat: + auto_reply: true + +knowledge_base: + code_guidelines: + enabled: true + filePatterns: + - 'README.md'