From d3548cf7ffa25d596dfd1626c0de2dd2af7cde3d Mon Sep 17 00:00:00 2001 From: EENE Dashboard Date: Thu, 18 Jun 2026 12:05:08 +0900 Subject: [PATCH] EENE Dashboard upload to Gitea --- backend/prisma/mapHrProjects.ts | 7 +- .../migration.sql | 15 + backend/prisma/schema.prisma | 1 + .../scripts/cleanup-legacy-task-details.ts | 54 ++ backend/scripts/normalize-task-sections.ts | 77 ++ backend/src/lib/taskIssues.ts | 11 +- backend/src/lib/taskQuery.ts | 12 + backend/src/routes/tasks.ts | 6 +- data/postgres/base/16384/1249 | Bin 532480 -> 532480 bytes data/postgres/base/16384/1259 | Bin 131072 -> 131072 bytes data/postgres/base/16384/24576 | Bin 8192 -> 8192 bytes data/postgres/base/16384/24583 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24625 | Bin 8192 -> 16384 bytes data/postgres/base/16384/24625_fsm | Bin 0 -> 24576 bytes data/postgres/base/16384/24633 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24635 | Bin 8192 -> 8192 bytes data/postgres/base/16384/24669 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24670 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24671 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24718 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24719 | Bin 24576 -> 24576 bytes data/postgres/base/16384/24754 | Bin 8192 -> 8192 bytes data/postgres/base/16384/24764 | Bin 8192 -> 8192 bytes data/postgres/base/16384/24769 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24788 | Bin 16384 -> 16384 bytes data/postgres/base/16384/24789 | Bin 16384 -> 16384 bytes data/postgres/base/16384/2658 | Bin 147456 -> 147456 bytes data/postgres/base/16384/2659 | Bin 98304 -> 98304 bytes data/postgres/base/16384/pg_internal.init | Bin 162312 -> 162312 bytes data/postgres/global/pg_control | Bin 8192 -> 8192 bytes data/postgres/global/pg_internal.init | Bin 28676 -> 28676 bytes data/postgres/pg_wal/000000010000000000000001 | Bin 16777216 -> 16777216 bytes data/postgres/pg_xact/0000 | Bin 8192 -> 8192 bytes .../src/components/common/StageFormFields.tsx | 262 ++++++ .../src/components/common/TaskFormFields.tsx | 333 ++++++++ frontend/src/components/common/TaskModal.tsx | 384 +-------- .../dashboard/BoardCalendarPopover.tsx | 88 +- .../components/dashboard/DashboardHeader.tsx | 38 +- .../components/dashboard/DepartmentColumn.tsx | 9 +- .../src/components/dashboard/HubColumn.tsx | 5 +- .../dashboard/HubRoutineFocusPanel.tsx | 31 - .../dashboard/HubScheduleCarousel.tsx | 185 +++-- .../src/components/dashboard/TaskCard.tsx | 89 +- .../dashboard/TaskFeedbackSection.tsx | 231 ++++++ .../src/components/dashboard/TaskManager.tsx | 644 ++++++++++----- .../dashboard/TaskMilestoneSection.tsx | 291 +++++++ .../components/detail/MilestoneTimeline.tsx | 649 +++++++++++---- .../components/detail/OverviewEditModal.tsx | 80 ++ .../components/detail/RoutineDetailView.tsx | 106 ++- .../src/components/detail/stageFormTypes.ts | 12 + frontend/src/hooks/useBoardReferenceDate.ts | 88 +- frontend/src/index.css | 39 + frontend/src/lib/boardCalendar.ts | 60 +- frontend/src/lib/dualMonitor.ts | 43 +- frontend/src/lib/milestoneTimeline.ts | 415 +++++++++- frontend/src/lib/milestoneWeekFocus.ts | 221 +++++ frontend/src/lib/sections.ts | 15 +- frontend/src/lib/stageFormState.ts | 51 ++ frontend/src/lib/taskFormPayload.ts | 7 +- frontend/src/lib/taskFormState.ts | 72 ++ frontend/src/lib/taskIssues.ts | 49 +- frontend/src/lib/taskStatusVisual.ts | 5 +- frontend/src/lib/teamStatus.ts | 16 +- frontend/src/main.tsx | 1 + frontend/src/pages/DashboardPage.tsx | 44 +- frontend/src/pages/DetailPage.tsx | 228 ++++-- frontend/src/pages/DummyDashboardPage.tsx | 28 +- frontend/src/styles/detail-theme.css | 473 ++++++++++- frontend/src/styles/dummy-board.css | 132 ++- frontend/src/styles/quarter-board.css | 275 +++---- frontend/src/styles/routine-detail.css | 15 +- frontend/src/styles/task-manager.css | 770 ++++++++++++++++++ frontend/src/types/index.ts | 2 + scripts/gitea-upload.ps1 | 47 +- 74 files changed, 5455 insertions(+), 1261 deletions(-) create mode 100644 backend/prisma/migrations/20260611120000_task_detail_description/migration.sql create mode 100644 backend/scripts/cleanup-legacy-task-details.ts create mode 100644 backend/scripts/normalize-task-sections.ts create mode 100644 data/postgres/base/16384/24625_fsm create mode 100644 frontend/src/components/common/StageFormFields.tsx create mode 100644 frontend/src/components/common/TaskFormFields.tsx create mode 100644 frontend/src/components/dashboard/TaskFeedbackSection.tsx create mode 100644 frontend/src/components/dashboard/TaskMilestoneSection.tsx create mode 100644 frontend/src/components/detail/OverviewEditModal.tsx create mode 100644 frontend/src/components/detail/stageFormTypes.ts create mode 100644 frontend/src/lib/milestoneWeekFocus.ts create mode 100644 frontend/src/lib/stageFormState.ts create mode 100644 frontend/src/lib/taskFormState.ts create mode 100644 frontend/src/styles/task-manager.css diff --git a/backend/prisma/mapHrProjects.ts b/backend/prisma/mapHrProjects.ts index d7d522f..8a985dc 100644 --- a/backend/prisma/mapHrProjects.ts +++ b/backend/prisma/mapHrProjects.ts @@ -261,10 +261,9 @@ function buildMilestones(p: HrProject): MappedTask['milestones'] { return milestones; } -function buildDetailContent(p: HrProject): string | null { - const content = p.progressStatus?.trim() || p.progressLog?.trim(); - if (!content || content === '이슈사항' || content === '12') return null; - return content; +/** @deprecated progressStatus는 milestone·periodEntries로 이관 — TaskDetail(피드백)에 넣지 않음 */ +function buildDetailContent(_p: HrProject): string | null { + return null; } export function mapHrProjectToTask(p: HrProject, quarter = '2026-Q2'): MappedTask { diff --git a/backend/prisma/migrations/20260611120000_task_detail_description/migration.sql b/backend/prisma/migrations/20260611120000_task_detail_description/migration.sql new file mode 100644 index 0000000..a110217 --- /dev/null +++ b/backend/prisma/migrations/20260611120000_task_detail_description/migration.sql @@ -0,0 +1,15 @@ +-- AlterTable +ALTER TABLE "tasks" ADD COLUMN "detailDescription" TEXT; + +-- 기존 description(첫 줄=개요, 이후=상세) → 분리 (실행과제·프로젝트만) +UPDATE "tasks" +SET + "detailDescription" = CASE + WHEN position(E'\n' in "description") > 0 + THEN NULLIF(trim(substring("description" from position(E'\n' in "description") + 1)), '') + ELSE NULL + END, + "description" = NULLIF(trim(split_part("description", E'\n', 1)), '') +WHERE "description" IS NOT NULL + AND trim("description") != '' + AND ("taskType" = '실행과제' OR "taskType" = '프로젝트'); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 8285141..88d1c48 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -66,6 +66,7 @@ model Task { id String @id @default(cuid()) title String description String? + detailDescription String? status TaskStatus @default(TODO) priority Priority @default(MEDIUM) quarter String // 예: "2026-Q2" diff --git a/backend/scripts/cleanup-legacy-task-details.ts b/backend/scripts/cleanup-legacy-task-details.ts new file mode 100644 index 0000000..741f884 --- /dev/null +++ b/backend/scripts/cleanup-legacy-task-details.ts @@ -0,0 +1,54 @@ +/** + * HR import 시 TaskDetail(피드백)에 잘못 들어간 progressStatus 레거시 삭제 + * — milestoneId 없고 authorName 없는 행 (seed 자동 생성분) + * + * npx tsx scripts/cleanup-legacy-task-details.ts + * npx tsx scripts/cleanup-legacy-task-details.ts --dry-run + */ +import 'dotenv/config'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); +const dryRun = process.argv.includes('--dry-run'); + +async function main() { + const legacy = await prisma.taskDetail.findMany({ + where: { + milestoneId: null, + OR: [{ authorName: null }, { authorName: '' }], + }, + include: { task: { select: { title: true } } }, + orderBy: { createdAt: 'asc' }, + }); + + if (legacy.length === 0) { + console.log('✅ 삭제할 레거시 피드백 없음'); + return; + } + + console.log(`레거시 TaskDetail ${legacy.length}건 (일정 미연결 · 작성자 없음):`); + for (const row of legacy) { + const preview = row.content.replace(/\s+/g, ' ').slice(0, 72); + console.log(` · [${row.task.title}] ${preview}${row.content.length > 72 ? '…' : ''}`); + } + + if (dryRun) { + console.log('\n(dry-run — 삭제하지 않음. 적용: npx tsx scripts/cleanup-legacy-task-details.ts)'); + return; + } + + const result = await prisma.taskDetail.deleteMany({ + where: { + id: { in: legacy.map((r) => r.id) }, + }, + }); + + console.log(`\n✅ ${result.count}건 삭제 완료`); +} + +main() + .catch((err) => { + console.error(err); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/backend/scripts/normalize-task-sections.ts b/backend/scripts/normalize-task-sections.ts new file mode 100644 index 0000000..0bdbb09 --- /dev/null +++ b/backend/scripts/normalize-task-sections.ts @@ -0,0 +1,77 @@ +/** + * task.section 레거시 값 정규화 + 조직문화(EX) 재배치 + * npx tsx scripts/normalize-task-sections.ts + */ +import 'dotenv/config'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +const EX_TITLE = /회사생활|C\.E\.L|조직문화|복리후생|문화\s*진단|직원\s*소통/i; + +const SECTION_MAP: Record = { + 성장지원: '학습성장', + HR: '인사관리', + 운영지원: '운영관리', + 전산관리: '운영관리', +}; + +async function main() { + const tasks = await prisma.task.findMany({ + select: { id: true, title: true, section: true, category: true, taskType: true }, + }); + + let renamed = 0; + let exMoved = 0; + + for (const task of tasks) { + const section = task.section?.trim() ?? ''; + let nextSection = SECTION_MAP[section] ?? section; + + if (nextSection !== '조직문화' && EX_TITLE.test(task.title.trim())) { + nextSection = '조직문화'; + exMoved += 1; + } + + const patch: { section?: string; category?: string | null } = {}; + if (nextSection && nextSection !== section) { + patch.section = nextSection; + renamed += 1; + } + if (nextSection === '조직문화' && task.category !== '조직문화') { + patch.category = '조직문화'; + } + + if (Object.keys(patch).length > 0) { + await prisma.task.update({ where: { id: task.id }, data: patch }); + console.log(` section: ${section || '(empty)'} → ${nextSection} | ${task.title}`); + } + } + + const col = await prisma.columnConfig.findUnique({ where: { key: '운영관리' } }); + if (col && (col.title === '운영관리' || col.title === '운영관리 부문' || col.titleEn === 'Operations')) { + await prisma.columnConfig.update({ + where: { key: '운영관리' }, + data: { title: '총무관리', titleEn: 'GA' }, + }); + console.log(' columnConfig 운영관리 → 총무관리'); + } + + const hrd = await prisma.columnConfig.findUnique({ where: { key: '학습성장' } }); + if (hrd && (hrd.title === '학습성장' || hrd.title === '성장지원' || hrd.titleEn === 'Learning & Growth')) { + await prisma.columnConfig.update({ + where: { key: '학습성장' }, + data: { title: '인재육성', titleEn: 'HRD' }, + }); + console.log(' columnConfig 학습성장 → 인재육성'); + } + + console.log(`\n✅ normalize-task-sections complete (${renamed} renamed, ${exMoved} → 조직문화)`); +} + +main() + .catch((err) => { + console.error(err); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/backend/src/lib/taskIssues.ts b/backend/src/lib/taskIssues.ts index 89c5539..0937c9c 100644 --- a/backend/src/lib/taskIssues.ts +++ b/backend/src/lib/taskIssues.ts @@ -2,12 +2,20 @@ export interface TaskIssueEntry { id: string; text: string; showOnCard: boolean; + occurredOn?: string | null; } function newIssueId() { return `issue-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; } +function normalizeOccurredOn(raw: unknown): string | null { + if (typeof raw !== 'string' || !raw.trim()) return null; + const iso = raw.trim(); + if (!/^\d{4}-\d{2}-\d{2}$/.test(iso)) return null; + return iso; +} + export function normalizeIssueEntries(raw: unknown): TaskIssueEntry[] { if (!Array.isArray(raw)) return []; const entries: TaskIssueEntry[] = []; @@ -20,6 +28,7 @@ export function normalizeIssueEntries(raw: unknown): TaskIssueEntry[] { id: typeof row.id === 'string' && row.id ? row.id : newIssueId(), text, showOnCard: row.showOnCard !== false, + occurredOn: normalizeOccurredOn(row.occurredOn), }); } return entries; @@ -34,7 +43,7 @@ export function parseIssueEntriesFromTask(task: { if (fromJson.length > 0) return fromJson; const legacy = task.issueNote?.trim(); if (!legacy) return []; - return [{ id: 'legacy', text: legacy, showOnCard: task.showIssue !== false }]; + return [{ id: 'legacy', text: legacy, showOnCard: task.showIssue !== false, occurredOn: null }]; } export function deriveIssueFields(entries: TaskIssueEntry[]) { diff --git a/backend/src/lib/taskQuery.ts b/backend/src/lib/taskQuery.ts index a920d4b..3500d6f 100644 --- a/backend/src/lib/taskQuery.ts +++ b/backend/src/lib/taskQuery.ts @@ -25,6 +25,18 @@ export const taskInclude = { taskAssignees: { include: { member: { select: teamMemberSelect } }, }, + milestones: { + orderBy: { order: 'asc' as const }, + select: { + id: true, + title: true, + progress: true, + startDate: true, + dueDate: true, + periodEntries: true, + order: true, + }, + }, _count: { select: { files: true, details: true } }, } as const; diff --git a/backend/src/routes/tasks.ts b/backend/src/routes/tasks.ts index e7a8e78..a938d2b 100644 --- a/backend/src/routes/tasks.ts +++ b/backend/src/routes/tasks.ts @@ -72,7 +72,7 @@ router.get('/:id', async (req, res, next) => { router.post('/', async (req, res, next) => { try { const body = req.body as Record; - const { title, description, status, priority, quarter, category, + const { title, description, detailDescription, status, priority, quarter, category, section, tag, taskType, progress, issueNote, startDate, dueDate, assigneeId, showDate, showDescription, showStatus, showIssue, showProgress, pmMemberId } = body; @@ -88,6 +88,7 @@ router.post('/', async (req, res, next) => { data: { title, description, + detailDescription: detailDescription ?? null, status: (status as any) || 'TODO', priority: (priority as any) || 'MEDIUM', quarter, @@ -135,7 +136,7 @@ router.patch('/:id', async (req, res, next) => { if (!existing) throw new AppError(404, '업무를 찾을 수 없습니다.'); const body = req.body as Record; - const { title, description, status, priority, quarter, category, + const { title, description, detailDescription, status, priority, quarter, category, section, tag, taskType, progress, issueNote, startDate, dueDate, assigneeId, showDate, showDescription, showStatus, showIssue, showProgress, pmMemberId } = body; @@ -147,6 +148,7 @@ router.patch('/:id', async (req, res, next) => { data: { ...(title && { title }), ...(description !== undefined && { description }), + ...(detailDescription !== undefined && { detailDescription: detailDescription || null }), ...(status && { status: status as any }), ...(priority && { priority: priority as any }), ...(quarter && { quarter }), diff --git a/data/postgres/base/16384/1249 b/data/postgres/base/16384/1249 index 500e30d8afad92f54a6f31a0ccf85ffa8bd9d701..4b885d5c4acd9d48c0f42c9c3726ed738c0817b6 100644 GIT binary patch delta 880 zcmZoTpwIwBEsQNpEzB(}EvzkUE$mY`Sa>wL4l^?_>}O;E0t1ExG7|+9wwiFsN+OlLp9=sDTN!GMFGfssKMh^O})U{o+*=!#&}2l4_sBN!KS zL@+7=*&o^?7#)C^p@ETMLR$pmgVqQ}10ZGqg6TI7Fj@#!u!7hyz{kKp-SH@6vyc=> w1f+~H1uXY~Y5TT=j8)9j1r9Q{ZBIMK$R&hBD;q@XbgxB>a@#9zFm|W|05AnA(f|Me delta 171 zcmZo@;AjA%EsQJ&m=jzMO=myA=*hwW1d}Hy=&~~~GUx*FXt>=9}3ov>x)mO2C<>7=114je1ro?$~i&TRYbAz-b-82IW zQ(Y5t(`4PmrR7>+z!^EVdW@B?RL!)FzXXRSj?OqBvCoIZP2wWnkFc#G1xDNE3n#vZK)x m!sHIt1Cw{K9{{H045rNscoSGRuMpnAG5H4Pj?ED=E0_U{IpgR6 delta 475 zcmZp0XmHSB0D>Eu2N*&09EJ{71_c%cJr)KAW(GzE4j>MgAHm25q$hNFFizA_2)NOi z!8l`f1)~s9?7`FwZiT58jG{nx!juZe6_Ya70anO(&Hw-a diff --git a/data/postgres/base/16384/24583 b/data/postgres/base/16384/24583 index ae1b19d4a6f626c29925157e2beb3154d2f90eda..536a552bbdb672d3fbd22fc3be40279354125d64 100644 GIT binary patch delta 77 zcmZo@U~Fh$+`yv1UC?@%kpT!|7!FM4SFqdcsZcJXsl;I5;BAp=kYa9-mZY0zU}36j fVs4tOo0y!Es%vIpVw`Geo@$twl(hM+jW9C+E~XV~ delta 38 scmZo@U~Fh$+`yv1op576BLfgbFnpNIuVA-1P@!06Vt~o!FE%{P0OT(V+yDRo diff --git a/data/postgres/base/16384/24625 b/data/postgres/base/16384/24625 index d07c132f9cfc268223bb785649cf1e40c32cfcc5..796fa39d93b4df45047423c1a8180f67543cd148 100644 GIT binary patch delta 1774 zcmd^9Ur19?7(eHpJ2Opmb8~Lf?P?hMP|it98_b1&5H!(5>2U>#FQOoZY2;P2f2OFP z=}fbtha@Zq+rvPl1!?xS#~|pbf8+!oq@V~p=iX-SE(@x+KKQuY?>pb`cfNl|bcrI? zBPyPYPccFmX>mY<1YYP@p;1f2s5SmTF|{J`hn6V*o%Fv5jhfAt+S`rz&TJ!of2#>w zsfsg=cy+o72gcg417T3(Okr-y&aX}e2}2Yo`Z&hTB=?4wYlG8-9-23Gs$?T2m81k2 zMUp3MwQdoZPOZZ|u#xJ7R&6#ks7oQFb>n`>*Sa!nR-64`olP0;WZ}MYT`7)1cF4$& zTfPvumsW|-K{(B!Mrm$@vZ<=SBZ4a~8?Naqa6RPdb2HYbhSq$3LCI6hz~GI(7GDK> zdMP~7>3qZK*|Drek$k=qAm(HE6jYk?p_b2rGnQ$-nji$7=8%!)ew3Di@yMmX^lK1F zj_**yEYa+=t7QADq(&TN=Vg0EvcgVe`=gYz(dRkI4t7nSfsR0wdu}dcuY+QiNSycxd?rR|hFkb5Rp{nQO*bs@i?{=p;vE)t{Q(-yP-Kx1f+S}*1nXcd zf=HtoN(3PR8KV#`vpeJ#{8ns*1vXpZ_GLFt=$>~eF55$C7+r*F@4uj`=Br|v&Qlh~ z7Ga84FtxL!%)ibFYWV%Y>b|h(jFVdNv^}1D)}j83CV&{95T9g9U`zM2IT?d+4*mk=aAe0 delta 887 zcmZuuOK1~e5dLQ$F$r1Qq^8XhrJD-UT4Oh9^QcX-|F3DF4RMtVoKtj;5?n7opO%H=%N=Bs>!>Zj*QoO-FkX_(^!=PSH&vC1u!T6y(CP3DaC3`-lr zWXrQP?%Pa-$BeQvRuCE-JG1J|h_L}CYkZGVpQ@Wpv5Dfb#KfUf`9W1xkDn-Gv{|&Kn|`nxwOQ{L%GtHkI(XKAc=J{CaRDVsZ>P^=LpA~9F)&1fuk-&O zng!o?+tm{0GNUcT&)Il-BxA|jttY|TZPSk#y#69J2EhYVe%RL1-)K?lN9%xZAR~C_ zJ2A{{2RBCeVLZZhVVS$(e?~3OZEewp;w(P`!5q=|Y^jK;_@MX`@Qbhqp5TBu#3^q4 zEOvJ&jJ4+&>mqogYBV0sM2){dO`Puwx(0;918AZFDfbQUBm*16&T? z_Zxkdg_+EFmvTCcPklS^<@zFkKDyy97yQaI_Jd$qhzij~*ZmFH=G-ZGXdaF``$&A3 NtA%e tjJ^mwFW~!s-dF_!1PBlyP))%9zuH9fLx2DQ0t5&UAV7cs0RjXr3QSVF0KNbK literal 0 HcmV?d00001 diff --git a/data/postgres/base/16384/24633 b/data/postgres/base/16384/24633 index f3473962eee8b521b10fa7b94323e28d4b69de91..9f5f54cdff360cb35b29c1e104f6869daff3b61a 100644 GIT binary patch delta 172 zcmZo@U~Fh$+`yv1;Pml%nD$T0E(}eK6##kn9>iRat)x+hM55j9YCA_#1=p-0K_kVS{kNr zKBq8Gj8T8`TMJcYMh4c&veqD)W%5%n%{)2O3dCoc%xeRp87Jpjt8Xr`=wSx{s-82{ delta 86 zcmZo@U~Fh$+`yv1!{NDykpT#9Fce5k7Es7hdN4bHVaM!1h8r^j872VP89-bxBY?pH lD8?`=fI(vR0D=z>j%;K<%PG{t3gSV4C0D=Y2_iSW8%P9n6!+jn9b0-~7QwfEfVnXCk=( delta 42 xcmZo@U~Fh$+`yv1o#4BNkpT#9FnpNIuW*o^VXgwhhdBxx9c5)Uf3e|V1^^Lq4I}^n diff --git a/data/postgres/base/16384/24670 b/data/postgres/base/16384/24670 index 9dde9de48739d620a1753fe0989bed7b68a033dc..52a0633c756a569adf5ae141670f77c65c7673a5 100644 GIT binary patch delta 172 zcmZo@U~Fh$+`yv1T`>I!BLfh8V3;tOUqO^3Ca0rEu>z>;8jW;A&w QG(IDcFO{(Qm5mrP04V++;s5{u delta 62 zcmZo@U~Fh$+`yv1-4M8kkpT#9FnpNIuOK>ELt&0M!(0W13G)>g0)SXyo&v)IApgT0 Pg^iA~GMm5H@Gt`aKNAxC diff --git a/data/postgres/base/16384/24671 b/data/postgres/base/16384/24671 index 35215e538dff12499850ec237ed3caf2027ad82b..6e7b4b77161c9f62e1584767748f7cd73b51991c 100644 GIT binary patch delta 116 zcmZo@U~Fh$+`yv1J!Ae6Mg}1Gz`!t>U!hN6W&lG65GMe!1rQ5No~dBNHdBFtVV1&1 rM^OnueFlLBka`GUWMG|q*HV?4k%0wDGec=6D9s3@)i>X<{9 diff --git a/data/postgres/base/16384/24718 b/data/postgres/base/16384/24718 index f0af234444ff1fad37c40679a031c9293e504432..a7d40eefe64deaf18e98aaca6e34bcd63de25c07 100644 GIT binary patch delta 197 zcmZo@U~Fh$+`yv1-LUisBLfh8V2GH^ub|4dV7386!R*OK3i^z*C&wwsu|)s{I%ZF< zQcz;Nuz9A!YN>jC1_=l0Hw!nsS>E`1&5pM-r-1Y`Ffy=$#b<4KGo$g5Y#3lfdBvi delta 335 zcmZoTz}RqraRZBj-wU_Hj0_Bn3<8V>Yzzu43Z<+J3=9p731_qBQ6>JuhCI>0ZQW2OKz;vO%f(a!2 zpzjt~`a#b(<`3OB7&mk^Fcoyvv1}Gl3}BqR*DP8Lq>6!I!PU}@-+($2e)UQJU}7+z z+-PDi`J4qJSpsqs956E2ZFW?1XJj%Eo!q7Vl+j{xh5n7r25JsWVjzWJ4}cXjGB7dl zA7ED9?55^~P`$Z8LxFX3g}w)$pc9(8ERzeBGE^Kos-nMw9Dgr<1<)yyP>WX>Pu0E) bbm9q5)0x?u|0=mMvTfM#wttP`A_oNkH@;Uv diff --git a/data/postgres/base/16384/24754 b/data/postgres/base/16384/24754 index 317ca52e239596c0f3f9e965c02adb7653c0484f..721117f7663580d5d18fa8eecdf0cf1047321f3b 100644 GIT binary patch delta 23 dcmZp0XmDU>00IlGqZ`>(MHo9a>xx|F1^_~@1^WO1 delta 23 dcmZp0XmDU>0D=a?gB#gZMHsC&>xx|F1^`6-1^EB~ diff --git a/data/postgres/base/16384/24764 b/data/postgres/base/16384/24764 index 8f283ee246d426f136696f07712ac0dffc84bd38..caa93ddd6661a4e69d451c187585d430fc870a34 100644 GIT binary patch delta 144 zcmZp0XmAi<0D=g%ql_T>34?&ZM1d3B8z#(PNC0Ai2{Sf6)a4iKUP_=*nzM}ieV delta 59 zcmZp0XmAi<0D>QU2N^;14F(3mi2^4!ev;$g#K^@cXw3=|MF4}ziURhV0|Ynl0RVYc B3y%N* diff --git a/data/postgres/base/16384/24769 b/data/postgres/base/16384/24769 index 8a0888e7c104b8ad7472379b608a481535af4df9..a7cd722bde156bba4a96c71dd879817e3dd80e87 100644 GIT binary patch delta 65 zcmZo@U~Fh$+`yv1?ICiMkpT#vFc?hcSIFmXWY{pdk)Z&H4K~**tQKN4p8VC=p3w+Q M8iL5pUyONp0C`&!(*OVf delta 38 tcmZo@U~Fh$+`yv1Eg^G|kpT#9FnpNIuaLjFOJSAJ#DFuKzZmoI007-n42b{$ diff --git a/data/postgres/base/16384/24788 b/data/postgres/base/16384/24788 index 0036af90a2307daff681ef2b96c60b5aecdc851e..8386148d50570bbc001cbc85b7a591ba70e6be08 100644 GIT binary patch delta 65 zcmZo@U~Fh$+`yv1Qy_bkkpT#vF#HgoETC|L+haxmg8~r$m>#gvQBs`Ic(SauI-?Po MGz5{GODuZW0d!Lmxc~qF delta 35 pcmZo@U~Fh$+`yv1b3x@GBLfiJU?`B7ETC{=qthJm%_SB+>;S++3mgCd diff --git a/data/postgres/base/16384/24789 b/data/postgres/base/16384/24789 index 0b1dde2d737e946ee02c4aaff8aecae3b0aef98e..377e6cc495520e16b604976efcafcfed7b5d7a24 100644 GIT binary patch delta 164 zcmZo@U~Fh$+`yv1^I-K6Mg}1Gz;HrpvVek!k;Uu)1_mJBF*kr=0uW~aam1Veh7O=u z0uX-yvQGf<0w8t(%H5bX*-fF3`@}2-h7Ge77&K-pY;?RUDX7mN&;T+Q0vH)sC%f9J iGBYx;OqR7(6=q~$Mw4d(i38;sC-+*bZ|<=!VF3Wdx+(bp delta 65 zcmZo@U~Fh$+`yv1(~!J}kpT#9Fmy;y7Ello+%Y$RVFD0m0CB{e0EUjqjtat)BNTd= RH_TSp=(tsCbB}ci3jl|+6zBi| diff --git a/data/postgres/base/16384/2658 b/data/postgres/base/16384/2658 index f48449c225a34b7d80a491569f1adbfb38e16a49..2bc17872faaa5505f85d3f160449852afec68a2a 100644 GIT binary patch delta 71 zcmZo@;A{Y*7RD(|EZi0=4l^@~ delta 42 vcmZo@;A{Y*7RD(|EZh~p_A@d7K@7`*sr*bcx2|FO#yT-TW&0sECOb<2BMS|w diff --git a/data/postgres/base/16384/2659 b/data/postgres/base/16384/2659 index f2b1cce091db2159876f91d1446bd58cde700949..5454d28d163533548219e3068d4c214e1de6448c 100644 GIT binary patch delta 55 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 diff --git a/data/postgres/global/pg_control b/data/postgres/global/pg_control index 7b85ce3bb4c8090217b2b84e86d17a5fa022d2c6..0eebbcdc9e44ec5ac04ee44ed0aaa609bf0ba595 100644 GIT binary patch delta 86 zcmZp0XmFSyq4~?#IEw)UypA%0s1u$L8pMHNka+h*M|DBCVvq(|=xD{Gg*9XtN-`OXhe)oRo z-gEApbMBq91^sM6UpQb|kq>%L)@WL|i?}#&zjo*M58S5uC9$CgYfWd@q_U=V6WumL znMG55ccbZyoq*|eqC4&8N2iq`hEwwuLI?)0d&vqX2QwVJ>4#`0@7drpeRHn+hX zYIT%DJ4kfD2vy?#V^A{8*p~>I`3lTl26IfGY5z`6>1gRci0ZIOIU>cnLOVpLy}QQn zq*F1x?V@g4Z>3b^aFXbnPgdfY2`DzRbQ<;V{|HQ+0d1zk%rLzfva85hIikpUrN}gS zVMv$UWu0)q2!q9pLxCt=6?Zw zW`UW$4t?$hQ5`PzE0l~>gdVx38@_ZZe&{-(o9Qm}%G?gwJ$>C&kY}J|nAyW9z{(M@ za1<;twPTovnXwU?tH=>KqO>GfX&1M5hIPY}jz!}xO3Nh&Y4N6HS_XDk;@106iql}} zDo)@39hkTV+DwO;VS0J8qbPDDi=q^Db9$&KnhYS?_E7Y|8GLDQMt=Kfv=X;Jh7x8j z4B`uxUj_Xku*lSgF^}Cs>)Cgz&DbwT6y0H^UHsb{s4-&Fv1r$U`Mdl#6k8+Tcj>V_@qk2ok z#jPzrh3D;Vou=Kba~GfgtwE#8-Kf5SspnQ|jlck3_LlHDY|C5Nwnp35{zBeI9GV7A! z-oY1iV?{a@eTS&t#Sf9!(rKbQKS=eSff8o=Ut@p6H(=mf&|*fILrf<}b`{x{BZ{1s z6#5WFj!CCtU^uEbms?=fg5YYarmeb*+t=gH7AQST`$x<(H^J;J&|~JAMW*%>*-`7; zSQ(ItXgW$KNNkbA(PMN1|6O$U%6s4}Wq(#>-%3F#o&v2s=$TX$O!tBrrdQ=Tbh!Un zwdZqkL^;r&r5z$<(*vwY=VEw_9^gn9TBT=){DGQpRG+}lU909{cTH~)-kTR{%fp{G*yOwstYprYP=l;Bw;c_OCGJmnqHT5 zi|)>$E-gFvA-PTi&BPtenEf^>1*Va{KySQA%pWBdnHICgtRDlvx$GI#9JHV)&Wn0?rI;RAVv zSYg`C2GjE@_+@4VEoh66m4MjTA5>ix3@p0tgSO-rs4e+LY)fLe8B^XRrMg6PxV4cD z+L!(-(O{;SIcD)U@XPFZv>>}B75U{v;7}Y4EW8IGyYC~~Y48H>eLC8VWmZXvFpE6@ zwGrBh^8m5I^gKw<*&2;raicYiIkYf4D*^HEh^NhEU|`Yx&?U83U-($o7hfbE8BmlX z=5lg7R$d|{%e20soos$ftlLE8JEDgfVH)3qE_h^?hoq{Q=ONPc~!vc~acW%$HP1=?c-R5GzcZ*>|eZ=eiigMjt-n$($?j>b_8UB-=({*Cz7BS1rGs{f- zFYwC>Dri9k4pMYK0~LfZsF;2hDsbMT!QM29XQBCLW%I)YQX1!oDg4nuobpS=$_ZkP zS!eo6G~oK>*2o9Zf|WX67{Z@|``3fP#nf|f|LO^vl=wmXe`?RSZoqS}0C;JWBJQCI&1v*E>| diff --git a/data/postgres/pg_wal/000000010000000000000001 b/data/postgres/pg_wal/000000010000000000000001 index 4b9e08aeda8990d79aae8ae61aa7af46c5b4ddca..e58a1401279525947f0f906b08bae833f37260eb 100644 GIT binary patch delta 444490 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 delta 16 XcmZp0XmHpN%eXm-ah}}d21Zr@GuQ=s diff --git a/frontend/src/components/common/StageFormFields.tsx b/frontend/src/components/common/StageFormFields.tsx new file mode 100644 index 0000000..d7f17d7 --- /dev/null +++ b/frontend/src/components/common/StageFormFields.tsx @@ -0,0 +1,262 @@ +import type { StageFormData } from '../detail/stageFormTypes'; +import { newPeriodEntry } from '../../lib/milestonePeriods'; +import type { TeamMember } from '../../types'; + +interface StageFormFieldsProps { + variant: 'project' | 'routine'; + form: StageFormData; + onChange: (next: StageFormData) => void; + teamMembers?: TeamMember[]; + idPrefix?: string; +} + +export function StageFormFields({ + variant, + form, + onChange, + teamMembers = [], + idPrefix = 'stage-form', +}: StageFormFieldsProps) { + const isRoutine = variant === 'routine'; + + const set = (field: K, value: StageFormData[K]) => + onChange({ ...form, [field]: value }); + + const updatePeriodEntry = (id: string, patch: Partial<(typeof form.periodEntries)[0]>) => { + onChange({ + ...form, + periodEntries: form.periodEntries.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry)), + }); + }; + + const toggleAssignee = (memberId: string) => { + const has = form.assigneeMemberIds.includes(memberId); + onChange({ + ...form, + assigneeMemberIds: has + ? form.assigneeMemberIds.filter((id) => id !== memberId) + : [...form.assigneeMemberIds, memberId], + }); + }; + + return ( +
+
+ + set('title', e.target.value)} + className="task-form-input task-form-input--title" + placeholder="업무 일정 제목" + /> +
+ + {isRoutine && ( +
+ + set('subtitle', e.target.value)} + className="task-form-input" + placeholder="업무명 아래 표시 (선택)" + /> +
+ )} + +
+ + set('progress', Number(e.target.value))} + className="task-form-range" + /> +
+ +
+
+ 수행 기간 + +
+ {form.periodEntries.length === 0 ? ( +

등록된 기간이 없습니다. 보류 후 재개·분기별 수행 등 기간을 추가하세요.

+ ) : ( +
+ {form.periodEntries.map((entry, index) => ( +
+
+ 기간 {index + 1} + +
+
+ updatePeriodEntry(entry.id, { startDate: e.target.value })} + className="task-form-input" + aria-label={`기간 ${index + 1} 시작일`} + /> + updatePeriodEntry(entry.id, { dueDate: e.target.value })} + className="task-form-input" + aria-label={`기간 ${index + 1} 종료일`} + /> +
+