<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>프로그래밍</title>
    <link>https://noeyodeeeeeel.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 04:18:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>noeyodeeeeeel</managingEditor>
    <item>
      <title>사내 프로젝트 툴 제작기 : AI 다써보기</title>
      <link>https://noeyodeeeeeel.tistory.com/104</link>
      <description>&lt;h2 data-ke-size=&quot;size23&quot;&gt;프로젝트 소개&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  프로젝트 툴을 직접 만들게 된 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 여러 팀이 프로젝트, 업무(Task), 주간보고를 각각 다른 방식으로 관리하고 있었어요.&lt;br /&gt;현재 시장에 나와있는 프로젝트 툴은 많지만 딱 필요한 것만 있는 가벼운 프로젝트 툴이 필요했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리만의 프로젝트 툴을 만들기로 했습니다.&lt;br /&gt;업무(Task) &amp;rarr; 주간 계획 &amp;rarr; 주간 보고서가 자연스럽게 이어지는! + AI 다 써보자 ( Figma make, GPT, Cursor, copilot)&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;⏱️ 프로젝트 개요&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 기간: 약 6주 (2025.10 ~ 2025.11)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수 개발 기간은 약 10일 이후 2-3주 주니어 개발자 1인이 수정 배포를 수행함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3주는 주니어 개발자 1인이 수정&amp;middot;배포를 수행.&lt;br /&gt;AI 도입 효과: 개발 소요시간 약 40% 단축&lt;br /&gt;개발 단계: 분석 &amp;rarr; 설계 &amp;rarr; 디자인 &amp;rarr; 개발 &amp;rarr; 테스트 &amp;rarr; 배포&lt;br /&gt;현재 버전: v1.0 배포 완료&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;⚙️ 기술 스택&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Frontend: React (Vite, TypeScript, Tailwind)&lt;/li&gt;
&lt;li&gt;Backend: Spring Boot + eGovFrame + MyBatis&lt;/li&gt;
&lt;li&gt;DB: MariaDB 10.11&lt;/li&gt;
&lt;li&gt;AI Toolchain: GPT, Copilot, Cursor&lt;/li&gt;
&lt;li&gt;Design: FigmaMake&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발 단계&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ 분석&amp;middot;설계 &amp;ndash; GPT와 함께 구조부터 잡기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 제가 하고 싶은 시스템을 자연어로 GPT에게 설명했어요.&lt;br /&gt;그러면 GPT가 도메인 모델이나 데이터 흐름을 초안으로 만들어 줍니다.&lt;br /&gt;저는 그 초안을 실제 회사 업무 흐름에 맞게 다듬었고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심적인 도메인&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트&lt;/li&gt;
&lt;li&gt;업무(Task)&lt;/li&gt;
&lt;li&gt;주간 계획&lt;/li&gt;
&lt;li&gt;주간 보고서&lt;/li&gt;
&lt;li&gt;첨부파일&lt;/li&gt;
&lt;li&gt;댓글&lt;/li&gt;
&lt;li&gt;팀 멤버와 권한&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 개발 프로세스는&lt;br /&gt;&lt;b&gt;Figma &amp;rarr; GPT 명세 자동화 &amp;rarr; Cursor 코드 생성 &amp;rarr; React &amp;rarr; Spring Boot &amp;rarr; Tomcat&lt;/b&gt;&lt;br /&gt;기존 방식보다 훨씬 빠르게 구조를 잡을 수 있었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ 디자인 &amp;ndash; Figma Make로 화면 자동 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 Figma Make + Clobe Plugin 조합으로 만들었어요.&lt;br /&gt;GPT에게 &amp;ldquo;이런 시스템을 만들 거야&amp;rdquo;라고만 설명하면, 정말 필요한 화면을 거의 다 뽑아줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용한 프롬프트 (figma Make는 영어를 더 잘 인식한다고 들었음..)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Create a modern responsive web dashboard UI for an internal Project Management and Weekly Report System. The system includes: Login and member management (admin can create, edit, delete members; fields: name, email, phone, password, role, join date, status). Layout: sidebar navigation (Members / Projects / Weekly Reports / Settings)... Style: clean, modern, blue (#2563eb) and white color palette, rounded corners....&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트 하나로 전체 UI의 90%가 자동으로 만들어졌습니다.&lt;br /&gt;디자인 단계에서는 완성도를 높이기보다 빠르게 초안을 확보하는 것이 목표였는데, AI 덕분에 이 단계가 정말 빨랐어요.&lt;br /&gt;이후 색깔, 여백, 컴포넌트 조정은 Cursor에서 미세 조정을 해서 마무리했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3️⃣ DB&amp;middot;API 설계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스와 API 설계는 반복 작업에 가까웠어요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FigmaMake &amp;rarr; 구조 자동 추출&lt;/li&gt;
&lt;li&gt;GPT가 이를 표 형태&amp;middot;스키마 형태로 정리&lt;/li&gt;
&lt;li&gt;개발자가 실제 업무 규칙에 맞게 컬럼과 제약 조건을 보완&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 초안을 만들어주는 속도가 빨라서 저는 디테일한 부분만 수정했습니다.&lt;br /&gt;이 단계에서 시간 절약 효과가 매우 컸습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4️⃣ 개발 &amp;ndash; GPT &amp;rarr; Copilot &amp;rarr; Cursor로 이어지는 개발 체인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 하나를 개발할 때마다 이렇게 진행했어요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GPT에게 기능 요구사항 설명&lt;/li&gt;
&lt;li&gt;GPT가 Copilot에 넘길 프롬프트 생성&lt;/li&gt;
&lt;li&gt;Copilot이 Controller&amp;ndash;Service&amp;ndash;Mapper 코드 생성&lt;/li&gt;
&lt;li&gt;생성된 코드를 기반으로 GPT가 Cursor용 프롬프트 작성&lt;/li&gt;
&lt;li&gt;Cursor가 React UI와 API 연결까지 자동 구현&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 직접 타이핑을 하는 코드는 거의 없었고 코드를 검토하고 판단하는 시간이 더 많았어요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5️⃣ 테스트&amp;middot;보완 &amp;ndash; AI는 빠른 분석 보조 역할&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 단계에서도 GPT는 유용했어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 로그 원인 분석&lt;/li&gt;
&lt;li&gt;매핑 오류 위치 찾기&lt;/li&gt;
&lt;li&gt;의심되는 부분 추려주기&lt;/li&gt;
&lt;li&gt;Cursor는 리팩토링과 정리 작업을 빠르게 해줬습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  AI를 쓰면서 느꼈던 어려움&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;AI는 빠르지만 의사결정은 사람이 해야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문을 조금만 바꿔도 다른 답이 나오고&lt;br /&gt;장단점만 나열하고 결론을 안 내리고&lt;br /&gt;조직의 상황이나 맥락을 이해하지 못하죠&lt;br /&gt;결국 &amp;ldquo;어떤 방식이 맞는가?&amp;rdquo;는 개발자가 판단해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 그래서 개발자에게는 최종 판단을 내릴 수 있는 지식과 기준이 반드시 필요합니다.&lt;br /&gt;AI가 제안은 해줄 수 있지만 의사결정을 하는 책임은 결국 사람에게 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;새로운 기능을 만들 때보다, 기존 코드를 수정할 때 AI는 훨씬 약하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관련 파일을 제대로 찾지 못함 ( 파일 이름이 비슷할때 )&lt;/li&gt;
&lt;li&gt;엉뚱한 부분을 수정하려고 함 (아주 디테일하게 설명해야함, cursor 의 'add to chat&quot;기능 활용하면 좋음)&lt;/li&gt;
&lt;li&gt;기존 구조와 맞지 않는 코드 제안&lt;/li&gt;
&lt;li&gt;존재하는 함수를 활용하지 못하고 중복되는 기능을 다시 만듬 (코드가 너무 길어져서 수정속도가 느려짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기존 코드를 손볼 때는 두 가지 선택지가 생겼습니다.&lt;br /&gt;아예 처음부터 아주 디테일하게 범위를 잡고 AI에게 다시 요청하거나&lt;br /&gt;잘못된 방향으로 가기 시작하면 과감히 undo하고 처음부터 다시 시작하는 게 더 빠를 때도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드 수정은 AI보다 사람이 직접 구조를 파악하고 판단하는 쪽이 더 효율적인 순간이 많았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  사용해본 AI 특징과 실제 활용 모습&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPT &amp;rarr; 설계&amp;middot;명세 정리 및 정확한 프롬프트 작성&lt;/b&gt;&lt;br /&gt;요구사항을 빠르게 구조화하고 명세로 정리해 주었으며,&lt;br /&gt;Cursor와 Copilot에서 바로 활용할 수 있는 정확한 프롬프트를 만드는 데 큰 역할을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Figma Make &amp;rarr; 디자인 자동 생성 + 초기 프론트 코드 초안 생성&lt;/b&gt;&lt;br /&gt;디자인을 정말 순식간에 만들어줄 뿐 아니라,&lt;br /&gt;프론트엔드에서 바로 활용 가능한 UI 코드 초안까지 자동 생성해 주었습니다.&lt;br /&gt;초기 화면을 &amp;lsquo;바로 써도 될 정도&amp;rsquo;로 만들어 주는 점이 특히 놀라웠고,&lt;br /&gt;백엔드 개발자 입장에서는 더 흥미로운 경험이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cursor(프론트엔드) &amp;rarr; UI&amp;middot;상태 관리&amp;middot;API 연동 구현 가속화&lt;/b&gt;&lt;br /&gt;Figma Make로 만들어진 화면을 기반으로 빠르게 UI와 로직을 붙일 수 있었고,&lt;br /&gt;특히 Plan 모드와 Add to Chat 기능을 활용하면&lt;br /&gt;필요한 변경 지점을 지정해 해당 부분 위주로 정확하게 수정해 주어&lt;br /&gt;프론트 개발 속도가 크게 향상되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Copilot(백엔드) &amp;rarr; 백엔드 기본 구조 및 구현 자동 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.27 - [work] - 프론트 페이지 리팩토링&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.27 - [work] - 프론트 페이지 리팩토링&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;Controller&amp;ndash;Service&amp;ndash;Mapper 구조와 기본 로직을 빠르게 생성해 주었고,&lt;br /&gt;Eclipse 환경에서도 매끄럽게 작동하여 백엔드 개발 시간을 크게 줄였습니다.&lt;br /&gt;다만 코드 생성 속도 자체는 Cursor가 훨씬 더 빠르게 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 이 조합 덕분에 6주 예상 일정이 2주로 60%이상 단축될 만큼 효과가 컸습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  바이브 코딩 개발 회고&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI를 잘 쓰려면 오히려 더 탄탄한 설계가 필요하다&lt;/b&gt;&lt;br /&gt;요구가 조금만 모호해도 결과가 흐려지고 세부 조건을 정확하게 잡아주지 않으면 AI가 엉뚱한 방향으로 가더라고요.&lt;br /&gt;그래서 자세한 프롬프트의 중요성을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방향을 결정하는 건 개발자의 역할&lt;/b&gt;&lt;br /&gt;AI가 여러 옵션을 던져줘도, 그중에서 어떤 방식이 맞는지 고르고&lt;br /&gt;전체 구조를 보완하는 일과 책임을 지는 건 개발자라는 걸 다시 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI는 개발자를 대체제보다는 개발자의 판단력과 기준을 기반으로 개발 속도를 빠르게 만들어주는 도구&lt;/b&gt;&lt;br /&gt;잘 쓰면 엄청난 가속 장치가 되고 잘못 쓰면 오히려 돌아가는 시간이 더 늘어나는 그런 도구라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에도 앞으로 AI가 얼마나 발전되었는지 앞으로 얼마나 빠르고 똑똑하게 발전할지 기대되는 경험이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고 * &lt;br /&gt;프로젝트 진행중 과정을 남기 포스팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.13 - [work] - 업무관리 프로젝트 (Figma make)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/96&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.14 - [work] - 주간보고 페이지 수정&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.16 - [work] - 전자정부 프레임워크 4.3.0과 copilot 같이 쓰기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.20 - [work] - 백엔드(copilot) 을 사용한 후 프론트엔드 (cusor) 로 연결&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.20 - [work] - Copilot으로 만드는 댓글 관리 API&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.27 - [work] - 프론트 페이지 리팩토링&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.27 - [work] - 팀 멤버 추가 api 구현 (gpt, copilot)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://noeyodeeeeeel.tistory.com/102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.29 - [work] - 기능 추가 - 날짜 필터링&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/104</guid>
      <comments>https://noeyodeeeeeel.tistory.com/104#entry104comment</comments>
      <pubDate>Thu, 27 Nov 2025 09:32:44 +0900</pubDate>
    </item>
    <item>
      <title>기능 추가 - 날짜 필터링</title>
      <link>https://noeyodeeeeeel.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;주간 보고페이지에 있는&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYitdb/dJMcacBnGMq/DHFd113mZOr28Kub702vF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYitdb/dJMcacBnGMq/DHFd113mZOr28Kub702vF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYitdb/dJMcacBnGMq/DHFd113mZOr28Kub702vF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYitdb%2FdJMcacBnGMq%2FDHFd113mZOr28Kub702vF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;785&quot; height=&quot;343&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 요약, 내 주간 보고서에게 날짜 필터를 적용시킬 예정&lt;br /&gt;수정사항은 어떤 새로운 기능을 만들때보다 더 세세하게 들어가야한다&amp;nbsp;&lt;br /&gt;1. 프론트엔드 (cursor)에게 현재 상황을 먼저 받아옴&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;업무 요약 카드와 내 주간보고서 카드에서 호출하는 조회 api가 뭔지 알려줘 &quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[cursor 에게 받은 응답]&lt;/p&gt;
&lt;pre id=&quot;code_1761707332341&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  업무 요약 카드와 내 주간 보고서에서 호출하는 조회 API 정리
이미지에서 보이는 두 섹션에서 호출하는 API들을 정리해드리겠습니다:
  1. &quot;업무 요약&quot; 카드 (MyTasksSummary 컴포넌트)
호출하는 API:
백엔드 엔드포인트: GET /api/tasks
프론트엔드 서비스: taskService.getTasksByProject('')
위치: src/services/taskService.ts (36-63번째 줄)
API 호출 방식:
실제 요청 URL:
데이터 처리:
모든 업무를 가져온 후 프론트엔드에서 현재 사용자의 업무만 필터링
task.assignedTo === currentUserId 조건으로 필터링
  2. &quot;내 주간 보고서&quot; 카드 (MyPlanTable 컴포넌트)
호출하는 API:
백엔드 엔드포인트: GET /api/weekly-plans
프론트엔드 서비스: getWeeklyPlans()
위치: src/api/weeklyPlansApi.ts (41-47번째 줄)
API 호출 방식:
실제 요청 URL:
데이터 처리:
프로젝트 정보와 매핑하여 projectName 추가
availableProjects.find(p =&amp;gt; p.id === plan.projectId)?.name 로 프로젝트명 설정
  관련 API들
프로젝트 목록 조회:
백엔드 엔드포인트: GET /api/projects
프론트엔드 서비스: getProjects()
위치: src/services/projectService.ts (13-61번째 줄)
사용자 목록 조회:
백엔드 엔드포인트: GET /api/users
위치: src/hooks/useWeeklyReports.ts (90번째 줄)
주간 보고서 목록 조회:
백엔드 엔드포인트: GET /api/weekly-reports
쿼리 파라미터: startDate, endDate, project_id, assignee_id
위치: src/api/weeklyReportsApi.ts (39-76번째 줄)
  요약
업무 요약: /api/tasks &amp;rarr; 모든 업무 조회 후 사용자별 필터링
내 주간 보고서: /api/weekly-plans &amp;rarr; 주간 계획 데이터 조회
프로젝트 정보: /api/projects &amp;rarr; 프로젝트명 매핑용
사용자 정보: /api/users &amp;rarr; 사용자 목록 조회
이 API들이 이미지에서 보이는 테이블 데이터를 구성하는 핵심 데이터 소스입니다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2.&amp;nbsp; 프롬프트를 넘겨서 gpt에게 전달하고 내가 구현하고 싶은 기능과 함께 요청하며 백엔드 (copilot) 에게 넘길 프롬프트 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[GPT에게 받은 응답]&lt;/p&gt;
&lt;pre id=&quot;code_1761707477339&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[목표]
&quot;업무 요약&quot;과 &quot;내 주간업무 보고서&quot;에서 사용할 기간별 필터 기능을 백엔드 레벨에서 지원하도록 개선해줘.

현재 프론트 구조
----------------------------------------------------
1️⃣ 업무 요약 (MyTasksSummary)
- API: GET /api/tasks
- 호출 위치: src/services/taskService.ts (36~63)
- 필터 방식: 프론트에서 전체 업무를 가져온 후
  task.assignedTo === currentUserId 로 필터링함.

2️⃣ 내 주간업무 보고서 (MyPlanTable)
- API: GET /api/weekly-plans
- 호출 위치: src/api/weeklyPlansApi.ts (41~47)
- 필터 방식: 주간 계획 전체를 가져온 후
  availableProjects.find(p =&amp;gt; p.id === plan.projectId)?.name 으로 프로젝트명 매핑함.
----------------------------------------------------

[요구사항]
1. 두 API 모두 기간 필터(startDate, endDate)를 쿼리 파라미터로 받을 수 있도록 수정.
   예:  
   - GET /api/tasks?startDate=2025-10-20&amp;amp;endDate=2025-10-26  
   - GET /api/weekly-plans?startDate=2025-10-20&amp;amp;endDate=2025-10-26

2. startDate와 endDate가 제공된 경우,
   - `/api/tasks` &amp;rarr; task.start_date, task.due_date 기준으로 필터링  
     (`WHERE start_date &amp;gt;= :startDate AND due_date &amp;lt;= :endDate`)
   - `/api/weekly-plans` &amp;rarr; week_start_date, week_end_date 기준으로 필터링  
     (`WHERE week_start_date &amp;gt;= :startDate AND week_end_date &amp;lt;= :endDate`)
   - 파라미터가 없을 경우 기존 전체 조회 로직 유지.

3. Controller, Service, Mapper 각각 수정해줘.
   - Controller: @RequestParam(&quot;startDate&quot;) @RequestParam(&quot;endDate&quot;) optional 로 추가
   - Service: 기간이 존재하면 Mapper 쿼리 호출 시 map에 조건 추가
   - Mapper: SQL WHERE 절에 조건 추가 (동적 &amp;lt;if test&amp;gt; 문 사용)

4. 반환 데이터 포맷은 기존 ApiResponse 유지.

[추가 참고]
- WeeklyPlanController.java: list() 메서드 참고
- TaskController.java: getTasks() 또는 listTasks() 참고
- 테이블 구조:
  - tb_task(start_date, due_date)
  - tb_weekly_plan(week_start_date, week_end_date)

[출력 형태]
- 수정된 Controller / Service / Mapper 코드 전체
- 수정된 SQL 예시
- startDate, endDate 없이 호출 시 전체 조회 유지되는 부분 명시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. copilot에게 위 프롬프트를 전달&amp;nbsp;&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/102</guid>
      <comments>https://noeyodeeeeeel.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 29 Oct 2025 13:46:43 +0900</pubDate>
    </item>
    <item>
      <title>팀 멤버 추가 api 구현 (gpt, copilot)</title>
      <link>https://noeyodeeeeeel.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- GPT 에게 프롬프트 생성 요청&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bck4pn/dJMb88MFDDx/7T0h4V99mrvxTTF5W05f70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bck4pn/dJMb88MFDDx/7T0h4V99mrvxTTF5W05f70/img.png&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;584&quot; data-is-animation=&quot;false&quot; style=&quot;width: 34.7533%; margin-right: 10px;&quot; data-widthpercent=&quot;35.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bck4pn/dJMb88MFDDx/7T0h4V99mrvxTTF5W05f70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbck4pn%2FdJMb88MFDDx%2F7T0h4V99mrvxTTF5W05f70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;691&quot; height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y3uSb/dJMb9Pl7g9z/3alfdKfWkEKZCLGACgGKm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y3uSb/dJMb9Pl7g9z/3alfdKfWkEKZCLGACgGKm0/img.png&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;253&quot; data-is-animation=&quot;false&quot; style=&quot;width: 64.0839%;&quot; data-widthpercent=&quot;64.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y3uSb/dJMb9Pl7g9z/3alfdKfWkEKZCLGACgGKm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy3uSb%2FdJMb9Pl7g9z%2F3alfdKfWkEKZCLGACgGKm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1761539356829&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- weekly_report.tb_project_team_member definition

CREATE TABLE `tb_project_team_member` (
  `id` char(36) NOT NULL DEFAULT uuid(),
  `project_id` char(36) NOT NULL,
  `user_id` char(36) NOT NULL,
  `role` varchar(100) NOT NULL,
  `assigned_date` date DEFAULT curdate(),
  `is_active` tinyint(1) DEFAULT 1,
  `created_at` timestamp NULL DEFAULT current_timestamp(),
  `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `project_id` (`project_id`,`user_id`),
  KEY `user_id` (`user_id`),
  CONSTRAINT `tb_project_team_member_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `tb_project` (`id`) ON DELETE CASCADE,
  CONSTRAINT `tb_project_team_member_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;

이게 프로젝트 멤버 테이블이고 crud api만들려고해 어떻게 copilot에게 요청할 프롬프트 만들어줘 
첨부한 사진처럼 되어있는곳에 api를 추가할거야&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트 생성, copilot에게 입력&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761539404664&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Spring Boot + MyBatis 기반으로 프로젝트 팀 구성원(tb_project_team_member) CRUD API를 구현해줘.

---

  테이블 구조
CREATE TABLE tb_project_team_member (
  id char(36) PRIMARY KEY DEFAULT uuid(),
  project_id char(36) NOT NULL,
  user_id char(36) NOT NULL,
  role varchar(100) NOT NULL,
  assigned_date date DEFAULT curdate(),
  is_active tinyint(1) DEFAULT 1,
  created_at timestamp DEFAULT current_timestamp,
  updated_at timestamp DEFAULT current_timestamp ON UPDATE current_timestamp,
  UNIQUE KEY (project_id, user_id),
  FOREIGN KEY (project_id) REFERENCES tb_project(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);

---

  구현 목표

1. **팀 구성원 목록 조회**
   - `GET /api/projects/{projectId}/members`
   - tb_project_team_member + tb_user 조인
   - 응답 데이터 예시:
     ```json
     [
       {
         &quot;userId&quot;: &quot;uuid&quot;,
         &quot;name&quot;: &quot;이도연&quot;,
         &quot;email&quot;: &quot;lee@krediv.com&quot;,
         &quot;department&quot;: &quot;백엔드&quot;,
         &quot;role&quot;: &quot;홈 화면 API구현&quot;,
         &quot;avatarUrl&quot;: &quot;...&quot;,
         &quot;assignedDate&quot;: &quot;2025-10-27&quot;
       }
     ]
     ```
   - is_active = 1 인 사용자만 조회

2. **팀 구성원 추가**
   - `POST /api/projects/{projectId}/members`
   - 요청 바디: `{ &quot;userId&quot;: &quot;...&quot;, &quot;role&quot;: &quot;...&quot; }`
   - 같은 user_id 가 이미 존재하면 409 반환
   - 성공 시 201 반환

3. **팀 구성원 수정**
   - `PATCH /api/projects/{projectId}/members/{userId}`
   - 요청 바디: `{ &quot;role&quot;: &quot;...&quot; }`
   - 역할(role) 또는 활성 상태(is_active) 변경 가능

4. **팀 구성원 제거**
   - `DELETE /api/projects/{projectId}/members/{userId}`
   - 실제 삭제 대신 is_active = 0 업데이트

---

  구현 방식
- Controller: `ProjectTeamMemberController`
- Service: `ProjectTeamMemberService`
- Mapper 인터페이스: `ProjectTeamMemberMapper`
- Mapper XML: `ProjectTeamMemberMapper.xml`
- DTO: `ProjectTeamMemberRequest`, `ProjectTeamMemberResponse`
- 응답 래핑: `ApiResponse&amp;lt;T&amp;gt;` 형태 사용
- 예외 처리: 중복 추가, 존재하지 않는 사용자, FK 제약 위반 시 적절한 메시지 반환

---

  추가 설명
이 API는 프로젝트 상세 페이지의 &amp;ldquo;참여 인원 카드&amp;rdquo;에서 사용됨.  
- &amp;lsquo;추가&amp;rsquo; 버튼 &amp;rarr; POST  
- &amp;lsquo;수정&amp;rsquo; 버튼 &amp;rarr; PATCH  
- &amp;lsquo;삭제&amp;rsquo; 버튼 &amp;rarr; DELETE  
- 페이지 진입 시 &amp;rarr; GET 호출

UI 예시는 첨부된 이미지 참고.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- copilot 결과&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761539494715&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I implemented the requested Project Team Member CRUD API (no DTOs &amp;mdash; controller uses Map&amp;lt;String,Object&amp;gt; / path params) and wired MyBatis mapper + XML. Files I added/updated and what they do:

Files created
- src/main/java/egovframework/ai/managing/controller/project/ProjectTeamMemberController.java  
  - REST endpoints:
    - GET /api/projects/{projectId}/members
    - POST /api/projects/{projectId}/members
    - PATCH /api/projects/{projectId}/members/{userId}
    - DELETE /api/projects/{projectId}/members/{userId}
  - Uses ApiResponse&amp;lt;T&amp;gt; wrapper.

- src/main/java/egovframework/ai/managing/service/project/ProjectTeamMemberService.java  
  - Service interface.

- src/main/java/egovframework/ai/managing/service/project/ProjectTeamMemberServiceImpl.java  
  - Business logic: validation, duplicate detection, add / update / deactivate operations using mapper, transactional behavior.
  - addMember checks for existing active membership and throws DuplicateKeyException if already present.

- src/main/java/egovframework/ai/managing/mapper/project/ProjectTeamMemberMapper.java  
  - MyBatis mapper interface (annotated @Mapper) with:
    - selectMembersByProjectId, selectMember, countMember, insertMember, updateMember, deactivateMember.

- src/main/resources/mapper/ProjectTeamMemberMapper.xml  
  - SQL mappings for the mapper methods. Joins tb_user for listing members and supports insert/update/deactivate.

Files modified
- src/main/java/egovframework/ai/managing/common/GlobalExceptionHandler.java  
  - Added handlers for DuplicateKeyException (409) and DataIntegrityViolationException (400) to return meaningful ApiResponse.fail messages.

Behavior / validation / error responses
- List members (GET)
  - Returns ApiResponse.success(&quot;ok&quot;, List&amp;lt;Map&amp;lt;String,Object&amp;gt;&amp;gt;) with only active members (is_active = 1).
  - Each item contains: userId, name, email, department, role, avatarUrl, assignedDate.

- Add member (POST)
  - Request body (JSON): { &quot;userId&quot;: &quot;...&quot;, &quot;role&quot;: &quot;...&quot; , optional &quot;assignedDate&quot;:&quot;yyyy-MM-dd&quot; }
  - Validates projectId and userId presence.
  - If the same user is already an active member -&amp;gt; 409 CONFLICT (DuplicateKeyException) with message.
  - If FK or DB integrity issues (e.g. userId or projectId not exist) -&amp;gt; 400 BAD REQUEST with details (DataIntegrityViolationException).
  - On success -&amp;gt; 201 CREATED with ApiResponse.success(&quot;Member added&quot;, null).

- Update member (PATCH)
  - Request body can include any of: role, isActive, assignedDate (no DTO &amp;mdash; Map used).
  - If member not found -&amp;gt; 404 NOT FOUND (NoSuchElementException).
  - On success -&amp;gt; 200 OK with ApiResponse.success(&quot;Member updated&quot;, null).

- Remove member (DELETE)
  - Soft delete: sets is_active = 0.
  - If member not found -&amp;gt; 404 NOT FOUND.
  - On success -&amp;gt; 200 OK with ApiResponse.success(&quot;Member removed&quot;, null).

Notes / implementation choices
- No DTOs used: controller methods accept Map&amp;lt;String, Object&amp;gt; or path parameters as requested.
- MyBatis xml namespace = egovframework.ai.managing.mapper.project.ProjectTeamMemberMapper and placed under src/main/resources/mapper (same location as existing mappers).
- Mapper interface annotated with @Mapper so MyBatis/Spring should pick it up (consistent with other mappers).
- I added selectMember in the mapper/XML for potential reuse (e.g. re-activate earlier inactive rows) though current addMember only checks active existence &amp;mdash; if you want to automatically reactivate an inactive record instead of inserting, I can adjust the service logic to update is_active=1 when selectMember shows an inactive row.
- Exceptions are handled by the global exception handler added/updated earlier; errors are returned as ApiResponse.fail with appropriate HTTP status codes.

Example request/response outlines
- GET /api/projects/abcd-1234/members
  - 200 OK
  - body: ApiResponse.success(&quot;ok&quot;, [ { &quot;userId&quot;:&quot;...&quot;, &quot;name&quot;:&quot;...&quot;, &quot;email&quot;:&quot;...&quot;, &quot;department&quot;:&quot;...&quot;, &quot;role&quot;:&quot;...&quot;, &quot;avatarUrl&quot;:&quot;...&quot;, &quot;assignedDate&quot;:&quot;2025-10-27&quot; } ])

- POST /api/projects/abcd-1234/members  (body: { &quot;userId&quot;:&quot;u-1&quot;, &quot;role&quot;:&quot;Developer&quot; })
  - 201 Created -&amp;gt; ApiResponse.success(&quot;Member added&quot;, null)
  - 409 Conflict -&amp;gt; ApiResponse.fail(&quot;User is already a member of the project&quot;)

- PATCH /api/projects/abcd-1234/members/u-1  (body: { &quot;role&quot;:&quot;Lead&quot; })
  - 200 OK -&amp;gt; ApiResponse.success(&quot;Member updated&quot;, null)
  - 404 Not Found -&amp;gt; ApiResponse.fail(&quot;Member not found&quot;)

- DELETE /api/projects/abcd-1234/members/u-1
  - 200 OK -&amp;gt; ApiResponse.success(&quot;Member removed&quot;, null)
  - 404 Not Found -&amp;gt; ApiResponse.fail(&quot;Member not found&quot;)

If you want:
- I can change addMember to reactivate an existing inactive row instead of inserting a duplicate.
- Add unit/integration tests for controller/service.
- Provide sample curl/postman examples or add OpenAPI annotations.

Next step? (pick one)
- I&amp;rsquo;ll implement automatic reactivation on add if you prefer.
- Or I&amp;rsquo;ll add tests and example requests.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  프로젝트 팀 구성원 API 명세서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 101.163%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;구성원 목록 조회&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 5.34884%;&quot;&gt;GET&lt;/td&gt;
&lt;td style=&quot;width: 15.2326%;&quot;&gt;/api/projects/{projectId}/members&lt;/td&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;Path: projectId&lt;/td&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;200 OK&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;활성(is_active=1) 구성원 목록 조회. tb_user 조인하여 이름, 이메일, 부서, 역할, 프로필 등 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;구성원 추가&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 5.34884%;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;width: 15.2326%;&quot;&gt;/api/projects/{projectId}/members&lt;/td&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;Body:{ &quot;userId&quot;: &quot;...&quot;, &quot;role&quot;: &quot;...&quot;, &quot;assignedDate&quot;: &quot;YYYY-MM-DD&quot; }&lt;/td&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;201 Created409 Conflict400 Bad Request&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;구성원 추가. 이미 존재 시 409 반환, FK 오류 시 400 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;구성원 수정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 5.34884%;&quot;&gt;PATCH&lt;/td&gt;
&lt;td style=&quot;width: 15.2326%;&quot;&gt;/api/projects/{projectId}/members/{userId}&lt;/td&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;Body:{ &quot;role&quot;: &quot;...&quot;, &quot;isActive&quot;: 0 or 1, &quot;assignedDate&quot;: &quot;YYYY-MM-DD&quot; }&lt;/td&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;200 OK404 Not Found&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;역할, 참여일자, 활성 여부 수정. 존재하지 않으면 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;구성원 삭제 (비활성화)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 5.34884%;&quot;&gt;DELETE&lt;/td&gt;
&lt;td style=&quot;width: 15.2326%;&quot;&gt;/api/projects/{projectId}/members/{userId}&lt;/td&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;Path: projectId, userId&lt;/td&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;200 OK404 Not Found&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;실제 삭제 대신 is_active = 0 으로 처리 (소프트 삭제)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 api를 가지고 프론트에 연결할거야 cursor에게 요청할 프롬프트 만들어줘 프로젝트 목록 &amp;gt; 프로젝트 상세보기 &amp;gt; 참여인원 (수정 버튼 ) &amp;gt; 프로젝트 팀 구성원 다이얼로그 추가 버튼 - 생성 api 연결 추가된 멤버 수정 버튼 - 수정 api 연결 제거 버튼 - 제거 api 연결 카드 멤버 - 조회 api 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vxNnV/dJMb86afn2r/LKqlbh6mNRwbYCbiuAscC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vxNnV/dJMb86afn2r/LKqlbh6mNRwbYCbiuAscC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vxNnV/dJMb86afn2r/LKqlbh6mNRwbYCbiuAscC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvxNnV%2FdJMb86afn2r%2FLKqlbh6mNRwbYCbiuAscC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;214&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761543680165&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;프로젝트 상세 페이지의 &quot;참여 인원(Team Members)&quot; 섹션에 다음 API를 연동해줘.

백엔드 API 엔드포인트 (이미 구현 완료):
- GET    /api/projects/{projectId}/members
- POST   /api/projects/{projectId}/members
- PATCH  /api/projects/{projectId}/members/{userId}
- DELETE /api/projects/{projectId}/members/{userId}

요구사항:

1️⃣ **멤버 목록 조회 (카드 렌더링용)**
- 프로젝트 상세 페이지 진입 시 자동 호출.
- Axios GET 요청 &amp;rarr; 응답 data를 상태로 관리 (예: `members`, `setMembers`).
- 각 카드에는 이름, 역할, 부서, 이메일, 아바타, 참여일자 표시.
- API 응답: ApiResponse.success(&quot;ok&quot;, [ {userId, name, role, ...} ])

2️⃣ **추가 버튼 (다이얼로그 내에서 호출)**
- 다이얼로그에서 userId, role, assignedDate 입력 후 &quot;추가&quot; 버튼 클릭 시 POST 요청.
- 요청 바디: `{ userId, role, assignedDate }`
- 성공 시 다이얼로그 닫고, 목록 새로고침 (GET 다시 호출).
- 실패 시 alert로 에러 메시지 출력 (409 또는 400 처리).

3️⃣ **수정 버튼 (다이얼로그 내 기존 멤버 수정)**
- 수정 대상 멤버 선택 후 PATCH 요청.
- 요청 URL: `/api/projects/{projectId}/members/{userId}`
- 요청 바디: `{ role, isActive, assignedDate }`
- 성공 시 목록 갱신.

4️⃣ **제거 버튼 (비활성 처리)**
- 목록의 각 멤버 카드에서 &amp;ldquo;제거&amp;rdquo; 클릭 시 DELETE 요청.
- 성공 시 목록 갱신 (GET 다시 호출).
- 삭제 전 confirm 창 표시 (&amp;ldquo;이 멤버를 제거하시겠습니까?&amp;rdquo;).

5️⃣ **UI 연동**
- 상위 페이지: `ProjectDetailPage.tsx`
- 참여인원 카드 컴포넌트: `ProjectMembersCard.tsx`
- 추가/수정 다이얼로그: `ProjectMemberDialog.tsx`
- API 호출 로직: `projectMemberService.ts` 파일로 분리해서 axios 함수 정리
  ```ts
  export const projectMemberService = {
    list: (projectId) =&amp;gt; apiClient.get(`/api/projects/${projectId}/members`),
    add: (projectId, data) =&amp;gt; apiClient.post(`/api/projects/${projectId}/members`, data),
    update: (projectId, userId, data) =&amp;gt; apiClient.patch(`/api/projects/${projectId}/members/${userId}`, data),
    remove: (projectId, userId) =&amp;gt; apiClient.delete(`/api/projects/${projectId}/members/${userId}`)
  }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/101</guid>
      <comments>https://noeyodeeeeeel.tistory.com/101#entry101comment</comments>
      <pubDate>Mon, 27 Oct 2025 13:36:01 +0900</pubDate>
    </item>
    <item>
      <title>프론트 페이지 리팩토링</title>
      <link>https://noeyodeeeeeel.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;GPT 5 에게 프롬프트 생성 요청&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&quot;&amp;nbsp;CURSOR에게&amp;nbsp;요청할&amp;nbsp;프롬프트를&amp;nbsp;만들거야&amp;nbsp;REACT&amp;nbsp;로&amp;nbsp;만들어져있는&amp;nbsp;프로그램인데&amp;nbsp;한&amp;nbsp;페이지의&amp;nbsp;.TSX&amp;nbsp;코드길이가&amp;nbsp;너무&amp;nbsp;길어져서&amp;nbsp;이걸&amp;nbsp;나누려고&amp;nbsp;해&amp;nbsp;코드를&amp;nbsp;삭제하기&amp;nbsp;전에&amp;nbsp;이유를&amp;nbsp;설명하고&amp;nbsp;내&amp;nbsp;동의&amp;nbsp;후에&amp;nbsp;진행시켜줘&amp;nbsp;&quot;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; style=&quot;background: #2d2d2d; color: #f8f8f2; padding: 18px; border-radius: 10px; font-size: 14px; line-height: 1.6; overflow: auto;&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;
이 파일은 React + TypeScript (.tsx)로 작성된 페이지입니다.
현재 한 파일의 코드 길이가 너무 길어져서 유지보수가 어렵습니다.

1. 코드를 역할별로 적절히 분리해 주세요.
   - UI 컴포넌트, 훅, 유틸, 상수, 스타일 등을 별도 파일로 나누세요.
   - 파일 분리 시 import/export 구조를 자동으로 맞춰주세요.

2. 불필요한 코드(사용되지 않는 변수, import, console.log 등)는
   제거하기 전에 &quot;이 코드를 삭제해도 되는 이유&quot;를 반드시 설명하고
   내 허락을 받은 뒤 삭제 제안을 해주세요.

3. 리팩터링 후 구조를 명확히 설명해 주세요.
   - 어떤 컴포넌트로 분리했는지
   - 각 컴포넌트가 어떤 역할을 하는지
   - 삭제하거나 변경한 이유를 함께 적어주세요.

4. 코드 스타일은 현재 프로젝트 규칙(ESLint/Prettier)에 맞게 유지해주세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CURSOR&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk21T4/dJMb9P0IDhC/65W09pxfY2oEgABBXChwJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk21T4/dJMb9P0IDhC/65W09pxfY2oEgABBXChwJk/img.png&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;1382&quot; data-is-animation=&quot;false&quot; width=&quot;448&quot; height=&quot;619&quot; data-widthpercent=&quot;51.29&quot; style=&quot;width: 50.6915%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk21T4/dJMb9P0IDhC/65W09pxfY2oEgABBXChwJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk21T4%2FdJMb9P0IDhC%2F65W09pxfY2oEgABBXChwJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1001&quot; height=&quot;1382&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDLyN2/dJMb9VNi9wA/da7ivBfAS8EpRyt0yYGQkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDLyN2/dJMb9VNi9wA/da7ivBfAS8EpRyt0yYGQkk/img.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;1285&quot; data-is-animation=&quot;false&quot; width=&quot;465&quot; height=&quot;676&quot; style=&quot;width: 48.1457%;&quot; data-widthpercent=&quot;48.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDLyN2/dJMb9VNi9wA/da7ivBfAS8EpRyt0yYGQkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDLyN2%2FdJMb9VNi9wA%2Fda7ivBfAS8EpRyt0yYGQkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;884&quot; height=&quot;1285&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761529717659&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src/
├── types/
│   └── weeklyReports.ts          # 타입 정의들
├── constants/
│   └── weeklyReports.ts          # 상수 및 Mock 데이터
├── utils/
│   └── weeklyReports.ts          # 유틸리티 함수들
├── hooks/
│   ├── useWeeklyReports.ts       # 주간보고서 관련 훅들
│   └── useMeetingMinutes.ts     # 회의록 관련 훅
├── components/
│   ├── weeklyReports/
│   │   ├── MyTasksSummary.tsx           # 내 업무 요약 테이블
│   │   ├── MyPlanTable.tsx              # 내 업무 계획 테이블
│   │   ├── WeeklyReportsTable.tsx       # 주간 보고서 테이블
│   │   ├── MeetingMinutesSection.tsx    # 회의록 섹션
│   │   ├── AddMeetingDialog.tsx         # 회의록 추가 다이얼로그
│   │   ├── AddTaskDialog.tsx            # 업무 추가 다이얼로그
│   │   └── TaskDetailDialog.tsx        # 업무 상세보기 다이얼로그
│   └── WeeklyReportsPage.tsx            # 메인 컴포넌트 (300줄로 축소!)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRtGYV/dJMb9O1OsZE/7Z4B59ba1DDE8MWkFHNSN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRtGYV/dJMb9O1OsZE/7Z4B59ba1DDE8MWkFHNSN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRtGYV/dJMb9O1OsZE/7Z4B59ba1DDE8MWkFHNSN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRtGYV%2FdJMb9O1OsZE%2F7Z4B59ba1DDE8MWkFHNSN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1074&quot; height=&quot;1224&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*** 리팩토링 후 서버 돌려봤더니&amp;nbsp;&lt;br /&gt;import 경로 문제 발생&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;817&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQjI8m/dJMb9LjIuT2/Q1SKkXMKnTknaK8qwxkEGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQjI8m/dJMb9LjIuT2/Q1SKkXMKnTknaK8qwxkEGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQjI8m/dJMb9LjIuT2/Q1SKkXMKnTknaK8qwxkEGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQjI8m%2FdJMb9LjIuT2%2FQ1SKkXMKnTknaK8qwxkEGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;817&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;817&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결&amp;nbsp;&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/100</guid>
      <comments>https://noeyodeeeeeel.tistory.com/100#entry100comment</comments>
      <pubDate>Mon, 27 Oct 2025 10:58:52 +0900</pubDate>
    </item>
    <item>
      <title>Copilot으로 만드는 댓글 관리 API</title>
      <link>https://noeyodeeeeeel.tistory.com/99</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Copilot을 이용하여 댓글 CRUD 구현함.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT에게 &lt;b&gt;Copilot에 넣을 프롬프트&lt;/b&gt;를 요청함.&lt;br /&gt;요청 내용은 &amp;ldquo;댓글(Comment)에 대한 CRUD API를 만들기 위한 프롬프트 생성&amp;rdquo;이었음.&lt;br /&gt;아래는 GPT가 만들어준 프롬프트 원문임.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background: #2d2d2d; color: #f8f8f2; padding: 18px; border-radius: 10px; font-size: 14px; line-height: 1.6; overflow: auto;&quot;&gt;&lt;code&gt;게시판 또는 프로젝트 관리 시스템에서 댓글(Comment)을 생성, 조회, 수정, 삭제할 수 있는 CRUD REST API를 만들어줘.

요구사항은 다음과 같음:

1️⃣ 각 댓글(Comment)은 다음 필드를 가짐.
- id (UUID)
- task_id (UUID, tb_task의 FK)
- author_id (UUID, tb_user의 FK)
- content (text)
- created_at (timestamp)
- updated_at (timestamp)

2️⃣ Spring Boot(Java)로 Controller, Service, Repository 계층 구조를 사용해 작성할 것.

3️⃣ 아래의 API 엔드포인트를 포함할 것:
- POST /api/comments (댓글 생성)
- GET /api/comments?task_id=... (특정 업무의 댓글 목록 조회)
- PATCH /api/comments/{id} (댓글 수정)
- DELETE /api/comments/{id} (댓글 삭제)

4️⃣ 입력 검증(Validation)을 적용할 것.
- content는 비어 있을 수 없음.
- task_id, author_id는 필수임.

5️⃣ 응답은 ApiResponse(success, message, data) 형태로 반환할 것.

6️⃣ RESTful 네이밍 규칙과 HTTP 상태코드를 준수할 것.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트를 &lt;b&gt;Copilot&lt;/b&gt;에 그대로 입력함.&lt;br /&gt;Copilot이 자동으로 &lt;code&gt;CommentController&lt;/code&gt;, &lt;code&gt;CommentService&lt;/code&gt;, &lt;code&gt;CommentRepository&lt;/code&gt;, &lt;code&gt;DTO&lt;/code&gt; 구조를 생성함.&lt;br /&gt;전체적인 구조는 아래와 같음.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #272822; color: #f8f8f2; padding: 16px; border-radius: 10px; font-size: 13px; line-height: 1.6; overflow: auto;&quot;&gt;&lt;code&gt;src/
 ├─ controller/
 │   └─ CommentController.java
 ├─ service/
 │   └─ CommentService.java
 ├─ repository/
 │   └─ CommentRepository.java
 ├─ dto/
 │   ├─ CommentCreateRequest.java
 │   ├─ CommentUpdateRequest.java
 │   └─ CommentResponse.java
 ├─ common/
 │   └─ ApiResponse.java
 └─ config/
     └─ SwaggerConfig.java
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  API 명세서&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; font-size: 14px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;background: #f0f0f0; text-align: left;&quot;&gt;
&lt;th style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;기능&lt;/th&gt;
&lt;th style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;메서드&lt;/th&gt;
&lt;th style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;엔드포인트&lt;/th&gt;
&lt;th style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;댓글 생성&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;/api/comments&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;새로운 댓글 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;댓글 목록 조회&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;GET&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;/api/comments?task_id=...&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;특정 업무의 댓글 리스트 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;댓글 수정&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;PATCH&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;/api/comments/{id}&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;댓글 내용 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;댓글 삭제&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;DELETE&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;/api/comments/{id}&lt;/td&gt;
&lt;td style=&quot;padding: 8px; border: 1px solid #ddd;&quot;&gt;댓글 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결과물 기록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 Copilot이 생성한 실제 코드와 Swagger 문서 캡처를 아래에 추가할 예정임.&lt;br /&gt;오늘의 포인트는 GPT를 통해 프롬프트를 설계하고 Copilot으로 CRUD 구조를 자동 생성한 것임.&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/99</guid>
      <comments>https://noeyodeeeeeel.tistory.com/99#entry99comment</comments>
      <pubDate>Mon, 20 Oct 2025 17:34:41 +0900</pubDate>
    </item>
    <item>
      <title>백엔드(copilot) 을 사용한 후 프론트엔드 (cusor) 로 연결</title>
      <link>https://noeyodeeeeeel.tistory.com/98</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt;-&amp;nbsp; ask모드로 파일구조 파악후, 어떻게 연결할지 코드와 설명을 듣기&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt;백엔드-프론트엔드&amp;nbsp;연결&amp;nbsp;방안 &lt;br /&gt; &amp;nbsp;백엔드&amp;nbsp;API&amp;nbsp;엔드포인트들 &lt;br /&gt;GET&amp;nbsp;/api/projects&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;목록&amp;nbsp;조회&amp;nbsp;(필터링,&amp;nbsp;페이징&amp;nbsp;지원) &lt;br /&gt;GET&amp;nbsp;/api/projects/{id}&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;상세&amp;nbsp;조회 &lt;br /&gt;POST&amp;nbsp;/api/projects&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;생성 &lt;br /&gt;PATCH&amp;nbsp;/api/projects/{id}&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;수정 &lt;br /&gt;DELETE&amp;nbsp;/api/projects/{id}&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;삭제 &lt;br /&gt; ️&amp;nbsp;연결을&amp;nbsp;위한&amp;nbsp;작업 &lt;br /&gt;프로젝트&amp;nbsp;서비스&amp;nbsp;파일&amp;nbsp;생성&amp;nbsp;(src/services/projectService.ts) &lt;br /&gt;프로젝트&amp;nbsp;타입&amp;nbsp;정의&amp;nbsp;(src/types/project.ts) &lt;br /&gt;기존&amp;nbsp;ProjectsPage&amp;nbsp;수정&amp;nbsp;(API&amp;nbsp;연동) &lt;br /&gt;다음과&amp;nbsp;같이&amp;nbsp;파일들을&amp;nbsp;생성/수정하면&amp;nbsp;됩니다: &lt;br /&gt; &amp;nbsp;1.&amp;nbsp;프로젝트&amp;nbsp;타입&amp;nbsp;정의&amp;nbsp;(src/types/project.ts) &lt;br /&gt; &amp;nbsp;2.&amp;nbsp;프로젝트&amp;nbsp;서비스&amp;nbsp;(src/services/projectService.ts)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1809&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7pW1v/dJMb9L423WM/JZcdNwzkC18XOuKJXytpW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7pW1v/dJMb9L423WM/JZcdNwzkC18XOuKJXytpW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7pW1v/dJMb9L423WM/JZcdNwzkC18XOuKJXytpW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7pW1v%2FdJMb9L423WM%2FJZcdNwzkC18XOuKJXytpW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;1809&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1809&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. ask모드로 설명듣고, Agent모드로 파일 생성 및 수정 진행&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;1755&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VABZf/dJMb9NhuS6t/xMZr2iOKVnmBUB3rwfGaGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VABZf/dJMb9NhuS6t/xMZr2iOKVnmBUB3rwfGaGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VABZf/dJMb9NhuS6t/xMZr2iOKVnmBUB3rwfGaGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVABZf%2FdJMb9NhuS6t%2FxMZr2iOKVnmBUB3rwfGaGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;1755&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;1755&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;1364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxlI7d/dJMb9OnayGt/Q3Chw1L1vAtYbXUN0OJK8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxlI7d/dJMb9OnayGt/Q3Chw1L1vAtYbXUN0OJK8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxlI7d/dJMb9OnayGt/Q3Chw1L1vAtYbXUN0OJK8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxlI7d%2FdJMb9OnayGt%2FQ3Chw1L1vAtYbXUN0OJK8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1103&quot; height=&quot;1364&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;1364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&amp;nbsp;완료된&amp;nbsp;작업&amp;nbsp;요약 &lt;br /&gt; &amp;nbsp;생성된&amp;nbsp;파일들 &lt;br /&gt;src/types/project.ts&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;관련&amp;nbsp;TypeScript&amp;nbsp;타입&amp;nbsp;정의 &lt;br /&gt;src/services/projectService.ts&amp;nbsp;-&amp;nbsp;백엔드&amp;nbsp;API와&amp;nbsp;통신하는&amp;nbsp;서비스&amp;nbsp;함수들 &lt;br /&gt;src/data/projectInsertStatements.sql&amp;nbsp;-&amp;nbsp;초기&amp;nbsp;프로젝트&amp;nbsp;데이터&amp;nbsp;SQL &lt;br /&gt; &amp;nbsp;수정된&amp;nbsp;파일들 &lt;br /&gt;src/types/api.ts&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;API&amp;nbsp;응답&amp;nbsp;타입&amp;nbsp;추가 &lt;br /&gt;src/components/ProjectsPage.tsx&amp;nbsp;-&amp;nbsp;API&amp;nbsp;연동으로&amp;nbsp;완전히&amp;nbsp;재작성 &lt;br /&gt;src/components/ProjectsList.tsx&amp;nbsp;-&amp;nbsp;새로운&amp;nbsp;API&amp;nbsp;타입에&amp;nbsp;맞게&amp;nbsp;수정 &lt;br /&gt; &amp;nbsp;구현된&amp;nbsp;기능들 &lt;br /&gt;프론트엔드&amp;nbsp;기능 &lt;br /&gt;✅&amp;nbsp;프로젝트&amp;nbsp;목록&amp;nbsp;조회&amp;nbsp;(실시간&amp;nbsp;API&amp;nbsp;연동) &lt;br /&gt;✅&amp;nbsp;프로젝트&amp;nbsp;생성&amp;nbsp;(백엔드&amp;nbsp;API&amp;nbsp;호출) &lt;br /&gt;✅&amp;nbsp;프로젝트&amp;nbsp;수정&amp;nbsp;(백엔드&amp;nbsp;API&amp;nbsp;호출) &lt;br /&gt;✅&amp;nbsp;프로젝트&amp;nbsp;삭제&amp;nbsp;(백엔드&amp;nbsp;API&amp;nbsp;호출) &lt;br /&gt;✅&amp;nbsp;로딩&amp;nbsp;상태&amp;nbsp;표시 &lt;br /&gt;✅&amp;nbsp;에러&amp;nbsp;처리&amp;nbsp;및&amp;nbsp;사용자&amp;nbsp;피드백 &lt;br /&gt;✅&amp;nbsp;상태별&amp;nbsp;필터링&amp;nbsp;(계획,&amp;nbsp;진행,&amp;nbsp;완료,&amp;nbsp;지연,&amp;nbsp;보류,&amp;nbsp;취소) &lt;br /&gt;✅&amp;nbsp;클라이언트별&amp;nbsp;필터링 &lt;br /&gt;백엔드&amp;nbsp;연동 &lt;br /&gt;✅&amp;nbsp;/api/projects&amp;nbsp;GET&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;목록&amp;nbsp;조회 &lt;br /&gt;✅&amp;nbsp;/api/projects/{id}&amp;nbsp;GET&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;상세&amp;nbsp;조회 &lt;br /&gt;✅&amp;nbsp;/api/projects&amp;nbsp;POST&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;생성 &lt;br /&gt;✅&amp;nbsp;/api/projects/{id}&amp;nbsp;PATCH&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;수정 &lt;br /&gt;✅&amp;nbsp;/api/projects/{id}&amp;nbsp;DELETE&amp;nbsp;-&amp;nbsp;프로젝트&amp;nbsp;삭제 &lt;br /&gt; &amp;nbsp;다음&amp;nbsp;단계 &lt;br /&gt;이제&amp;nbsp;실제로&amp;nbsp;테스트해볼&amp;nbsp;수&amp;nbsp;있습니다: &lt;br /&gt;백엔드&amp;nbsp;서버&amp;nbsp;실행:&amp;nbsp;cd&amp;nbsp;../ai.managing&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;mvn&amp;nbsp;spring-boot:run &lt;br /&gt;프론트엔드&amp;nbsp;서버&amp;nbsp;실행:&amp;nbsp;npm&amp;nbsp;run&amp;nbsp;dev &lt;br /&gt;브라우저에서&amp;nbsp;확인:&amp;nbsp;http://localhost:3000 &lt;br /&gt;프로젝트&amp;nbsp;관리&amp;nbsp;페이지에서&amp;nbsp;실제&amp;nbsp;데이터베이스의&amp;nbsp;프로젝트들을&amp;nbsp;확인하고&amp;nbsp;관리할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;되었습니다!&amp;nbsp; &lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/98</guid>
      <comments>https://noeyodeeeeeel.tistory.com/98#entry98comment</comments>
      <pubDate>Mon, 20 Oct 2025 17:04:38 +0900</pubDate>
    </item>
    <item>
      <title>전자정부 프레임워크 4.3.0과 copilot 같이 쓰기</title>
      <link>https://noeyodeeeeeel.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프론트는 figma make로 만든 상태 react bite 형태로 빌드하고 cusor에&amp;nbsp; 프론트 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 백엔드랑 프론트 합친 프로젝트 만들어서 깃에도 올리고 프로젝트 시작&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 백엔드 개발 시작&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 까지는 이상태&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 담당한 projects 부분 시작&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT로 프론트, 기술스택, 개발환경, 데이터베이스 설명하고 api명세서 뽑음&lt;/p&gt;
&lt;h1&gt;  INFINITUM 프로젝트 관리 시스템 &amp;mdash; Projects API 명세서&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(실제 MariaDB 테이블 projects 기준)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 공통 정보&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Base URL&lt;/b&gt;: /api/projects&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 테이블&lt;/b&gt;: projects&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Primary Key&lt;/b&gt;: id (char(36))&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본 정렬 기준&lt;/b&gt;: created_at DESC&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 프로젝트 목록 조회&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GET&lt;/b&gt; /api/projects&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 프로젝트 목록을 조회합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태, 우선순위, 기간 등의 필터링 및 정렬을 지원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Query Parameters&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 타입 설명 예시&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;상태 필터 (planned, in-progress, completed, delayed, on-hold, cancelled)&lt;/td&gt;
&lt;td&gt;in-progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;priority&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;우선순위 (low, medium, high, urgent)&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;search&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;프로젝트명 또는 고객명 검색&lt;/td&gt;
&lt;td&gt;디지털&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;page&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;페이지 번호&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;limit&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;페이지 크기&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sort_by&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;정렬 기준 (created_at, start_date, progress)&lt;/td&gt;
&lt;td&gt;start_date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;order&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;정렬 순서 (asc, desc)&lt;/td&gt;
&lt;td&gt;desc&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ Response (200)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&quot;success&quot;:&amp;nbsp;true, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&quot;data&quot;:&amp;nbsp;[ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;id&quot;:&amp;nbsp;&quot;a91b5a02-8fa6-4c7a-8b24-f3174adf2ab3&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;name&quot;:&amp;nbsp;&quot;디지털&amp;nbsp;지갑&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;description&quot;:&amp;nbsp;&quot;정부24&amp;nbsp;디지털&amp;nbsp;지갑&amp;nbsp;서비스&amp;nbsp;구축&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;start_date&quot;:&amp;nbsp;&quot;2024-01-15&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;end_date&quot;:&amp;nbsp;&quot;2024-03-30&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;status&quot;:&amp;nbsp;&quot;in-progress&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;progress&quot;:&amp;nbsp;65, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;priority&quot;:&amp;nbsp;&quot;high&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;budget&quot;:&amp;nbsp;100000000, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;actual_cost&quot;:&amp;nbsp;65000000, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;project_manager_id&quot;:&amp;nbsp;&quot;user-001&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;client_name&quot;:&amp;nbsp;&quot;정부24&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;created_by&quot;:&amp;nbsp;&quot;user-admin&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;created_at&quot;:&amp;nbsp;&quot;2024-01-01T09:00:00Z&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;updated_at&quot;:&amp;nbsp;&quot;2024-03-15T10:00:00Z&quot; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;], &lt;br /&gt;&amp;nbsp;&amp;nbsp;&quot;pagination&quot;:&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;page&quot;:&amp;nbsp;1, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;limit&quot;:&amp;nbsp;10, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;total&quot;:&amp;nbsp;20 &lt;br /&gt;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회만 보자면 이렇게 나옴&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;1730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5WavR/dJMb84coQIh/aPyL5EkDK5l8N4xgmxPnJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5WavR/dJMb84coQIh/aPyL5EkDK5l8N4xgmxPnJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5WavR/dJMb84coQIh/aPyL5EkDK5l8N4xgmxPnJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5WavR%2FdJMb84coQIh%2FaPyL5EkDK5l8N4xgmxPnJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;876&quot; height=&quot;1730&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;1730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/97</guid>
      <comments>https://noeyodeeeeeel.tistory.com/97#entry97comment</comments>
      <pubDate>Thu, 16 Oct 2025 17:13:29 +0900</pubDate>
    </item>
    <item>
      <title>주간보고 페이지 수정</title>
      <link>https://noeyodeeeeeel.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;주간보고 탭 수정&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2047&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPeAV1/btsQ8YwehYf/wRFK5RzSA2WtQZ9fpK1Zm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPeAV1/btsQ8YwehYf/wRFK5RzSA2WtQZ9fpK1Zm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPeAV1/btsQ8YwehYf/wRFK5RzSA2WtQZ9fpK1Zm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPeAV1%2FbtsQ8YwehYf%2FwRFK5RzSA2WtQZ9fpK1Zm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2047&quot; height=&quot;1022&quot; data-origin-width=&quot;2047&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 공통 프롬프트를 입력했을 때 결과물이 이렇게 나왓다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 내가 넣고 싶은 내용으로 수정하기&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brJunZ/btsQ8C067SI/usKKgUfV511iLVglwMHfRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brJunZ/btsQ8C067SI/usKKgUfV511iLVglwMHfRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brJunZ/btsQ8C067SI/usKKgUfV511iLVglwMHfRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrJunZ%2FbtsQ8C067SI%2FusKKgUfV511iLVglwMHfRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;409&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pLhua/btsQ8u3eYW8/zbWxnKRAY1VUZErrGtai01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pLhua/btsQ8u3eYW8/zbWxnKRAY1VUZErrGtai01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pLhua/btsQ8u3eYW8/zbWxnKRAY1VUZErrGtai01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpLhua%2FbtsQ8u3eYW8%2FzbWxnKRAY1VUZErrGtai01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;720&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 나온 프롬프트&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'주간보고&amp;nbsp;페이지'를&amp;nbsp;만들어줘.&amp;nbsp;&amp;nbsp; &lt;br /&gt;디자인과&amp;nbsp;폰트는&amp;nbsp;현재&amp;nbsp;지정된&amp;nbsp;스타일을&amp;nbsp;그대로&amp;nbsp;유지해줘. &lt;br /&gt;&lt;br /&gt;페이지는&amp;nbsp;상단부터&amp;nbsp;아래&amp;nbsp;순서로&amp;nbsp;구성되게&amp;nbsp;해줘: &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp;1️⃣&amp;nbsp;주간&amp;nbsp;회의&amp;nbsp;요약&amp;nbsp;섹션 &lt;br /&gt;-&amp;nbsp;제목:&amp;nbsp;&quot;주간회의록&amp;nbsp;(날짜)&quot; &lt;br /&gt;-&amp;nbsp;회의일자,&amp;nbsp;작성자,&amp;nbsp;참석자&amp;nbsp;정보를&amp;nbsp;한&amp;nbsp;줄에&amp;nbsp;배치할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;영역 &lt;br /&gt;-&amp;nbsp;예시:&amp;nbsp;[회의일자:&amp;nbsp;2025.08.18]&amp;nbsp;[작성자:&amp;nbsp;김효민]&amp;nbsp;[참석자:&amp;nbsp;이도연,&amp;nbsp;남가현,&amp;nbsp;임의빈,&amp;nbsp;황두환] &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp;2️⃣&amp;nbsp;개별&amp;nbsp;회의록&amp;nbsp;본문 &lt;br /&gt;-&amp;nbsp;제목:&amp;nbsp;&amp;ldquo;YYYYMMDD&amp;nbsp;주간업무보고&amp;rdquo; &lt;br /&gt;-&amp;nbsp;문서&amp;nbsp;박스&amp;nbsp;형태로&amp;nbsp;구성&amp;nbsp;(회색&amp;nbsp;테두리,&amp;nbsp;내부&amp;nbsp;여백&amp;nbsp;있음) &lt;br /&gt;-&amp;nbsp;각&amp;nbsp;항목은&amp;nbsp;번호로&amp;nbsp;구분된&amp;nbsp;리스트&amp;nbsp;형태로&amp;nbsp;정리&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;예시&amp;nbsp;구조: &lt;br /&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;진료내역확인시스템&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;전반사항&amp;nbsp;인수인계&amp;nbsp;완료&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;일정&amp;nbsp;체크&amp;nbsp;및&amp;nbsp;코드&amp;nbsp;분석&amp;nbsp;정리&amp;nbsp;중&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;CBT&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;내부&amp;nbsp;완료&amp;nbsp;예정:&amp;nbsp;8월&amp;nbsp;31일&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;개발물&amp;nbsp;전달일:&amp;nbsp;8월&amp;nbsp;20일&amp;nbsp;&amp;amp;&amp;nbsp;테스트&amp;nbsp;예정&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;사내&amp;nbsp;서버&amp;nbsp;진단도구&amp;nbsp;완료&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;4.&amp;nbsp;진행&amp;nbsp;사업별로&amp;nbsp;차주부터&amp;nbsp;별도&amp;nbsp;진행&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;5.&amp;nbsp;상용&amp;nbsp;소프트웨어&amp;nbsp;현안&amp;nbsp;및&amp;nbsp;정책&amp;nbsp;체크&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp;3️⃣&amp;nbsp;관련&amp;nbsp;업무&amp;nbsp;요약&amp;nbsp;섹션 &lt;br /&gt;-&amp;nbsp;제목:&amp;nbsp;&quot;관련&amp;nbsp;업무&amp;nbsp;요약&quot; &lt;br /&gt;-&amp;nbsp;표&amp;nbsp;형태로&amp;nbsp;구성하고&amp;nbsp;다음&amp;nbsp;컬럼을&amp;nbsp;넣어줘: &lt;br /&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;담당자&amp;nbsp;|&amp;nbsp;관련&amp;nbsp;프로젝트&amp;nbsp;|&amp;nbsp;진행상태&amp;nbsp;|&amp;nbsp;비고&amp;nbsp;| &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp;4️⃣&amp;nbsp;팀&amp;nbsp;전체&amp;nbsp;회의&amp;nbsp;목록 &lt;br /&gt;-&amp;nbsp;제목:&amp;nbsp;&quot;팀&amp;nbsp;전체&amp;nbsp;회의&amp;nbsp;목록&quot; &lt;br /&gt;-&amp;nbsp;표&amp;nbsp;또는&amp;nbsp;카드&amp;nbsp;리스트&amp;nbsp;형태로&amp;nbsp;구성 &lt;br /&gt;-&amp;nbsp;각&amp;nbsp;행(또는&amp;nbsp;카드)에는&amp;nbsp;아래&amp;nbsp;정보가&amp;nbsp;포함되게&amp;nbsp;해줘: &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;회의일자&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;제목&amp;nbsp;(예:&amp;nbsp;8.18&amp;nbsp;주간업무보고)&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;주요&amp;nbsp;안건&amp;nbsp;요약&amp;nbsp;(예:&amp;nbsp;CBT&amp;nbsp;완료,&amp;nbsp;진료내역시스템&amp;nbsp;인수인계&amp;nbsp;등)&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;작성자&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;lsquo;자세히&amp;nbsp;보기&amp;rsquo;&amp;nbsp;버튼&amp;nbsp;또는&amp;nbsp;아이콘&amp;nbsp;(클릭&amp;nbsp;시&amp;nbsp;해당&amp;nbsp;회의록&amp;nbsp;상세로&amp;nbsp;이동하는&amp;nbsp;구조) &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp;5️⃣&amp;nbsp;하단&amp;nbsp;요약&amp;nbsp;정보 &lt;br /&gt;-&amp;nbsp;전체&amp;nbsp;회의&amp;nbsp;수,&amp;nbsp;이번&amp;nbsp;주&amp;nbsp;작성된&amp;nbsp;회의록&amp;nbsp;수,&amp;nbsp;미작성&amp;nbsp;인원&amp;nbsp;수&amp;nbsp;등을&amp;nbsp;보여주는&amp;nbsp;간단한&amp;nbsp;요약&amp;nbsp;영역을&amp;nbsp;추가해줘. &lt;br /&gt;-&amp;nbsp;요약은&amp;nbsp;작은&amp;nbsp;박스&amp;nbsp;형태로&amp;nbsp;나란히&amp;nbsp;배치&amp;nbsp;(예:&amp;nbsp;이번주&amp;nbsp;회의&amp;nbsp;3건&amp;nbsp;/&amp;nbsp;작성자&amp;nbsp;5명&amp;nbsp;/&amp;nbsp;미작성&amp;nbsp;2명) &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;전체&amp;nbsp;레이아웃은&amp;nbsp;현재&amp;nbsp;페이지&amp;nbsp;디자인&amp;nbsp;흐름과&amp;nbsp;동일하게,&amp;nbsp;&amp;nbsp; &lt;br /&gt;한&amp;nbsp;페이지&amp;nbsp;안에서&amp;nbsp;스크롤로&amp;nbsp;회의록과&amp;nbsp;목록을&amp;nbsp;모두&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;만들어줘.&amp;nbsp;&amp;nbsp; &lt;br /&gt;불필요한&amp;nbsp;색상&amp;nbsp;변경이나&amp;nbsp;폰트&amp;nbsp;변경은&amp;nbsp;하지&amp;nbsp;말고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;지금&amp;nbsp;지정된&amp;nbsp;스타일&amp;nbsp;안에서&amp;nbsp;정보&amp;nbsp;구조만&amp;nbsp;설계해줘.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3204&quot; data-origin-height=&quot;1739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXEh8n/btsQ9RDqPxR/tuTUzBhEXQcPmLWGrVqnb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXEh8n/btsQ9RDqPxR/tuTUzBhEXQcPmLWGrVqnb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXEh8n/btsQ9RDqPxR/tuTUzBhEXQcPmLWGrVqnb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXEh8n%2FbtsQ9RDqPxR%2FtuTUzBhEXQcPmLWGrVqnb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3204&quot; height=&quot;1739&quot; data-origin-width=&quot;3204&quot; data-origin-height=&quot;1739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전체 주간업무 요약 추가하기&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3143&quot; data-origin-height=&quot;1442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSLqJ3/btsQ6VulEUo/hpwbdPrgiDUyro3wAh12WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSLqJ3/btsQ6VulEUo/hpwbdPrgiDUyro3wAh12WK/img.png&quot; data-alt=&quot;before&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSLqJ3/btsQ6VulEUo/hpwbdPrgiDUyro3wAh12WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSLqJ3%2FbtsQ6VulEUo%2FhpwbdPrgiDUyro3wAh12WK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3143&quot; height=&quot;1442&quot; data-origin-width=&quot;3143&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;before&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;1706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsRiC/btsQ62M5DsR/VJVqT1PpuqJ0m11AF2ckkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsRiC/btsQ62M5DsR/VJVqT1PpuqJ0m11AF2ckkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsRiC/btsQ62M5DsR/VJVqT1PpuqJ0m11AF2ckkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsRiC%2FbtsQ62M5DsR%2FVJVqT1PpuqJ0m11AF2ckkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;873&quot; height=&quot;1706&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;1706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재&amp;nbsp;디자인&amp;nbsp;스타일과&amp;nbsp;폰트는&amp;nbsp;그대로&amp;nbsp;유지하고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;lsquo;주간보고&amp;nbsp;페이지&amp;rsquo;에&amp;nbsp;아래의&amp;nbsp;새로운&amp;nbsp;섹션을&amp;nbsp;추가해줘. &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;[전체&amp;nbsp;구성] &lt;br /&gt;주간보고&amp;nbsp;페이지는&amp;nbsp;기존처럼&amp;nbsp;&amp;nbsp; &lt;br /&gt;1)&amp;nbsp;이번주&amp;nbsp;업무&amp;nbsp;요약&amp;nbsp;&amp;nbsp; &lt;br /&gt;2)&amp;nbsp;팀&amp;nbsp;전체&amp;nbsp;회의&amp;nbsp;목록&amp;nbsp;&amp;nbsp; &lt;br /&gt;3)&amp;nbsp;하단&amp;nbsp;통계&amp;nbsp;&amp;nbsp; &lt;br /&gt;를&amp;nbsp;그대로&amp;nbsp;유지하고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;그&amp;nbsp;아래에&amp;nbsp;새로&amp;nbsp;&amp;ldquo;팀&amp;nbsp;전체&amp;nbsp;업무&amp;nbsp;계획&amp;nbsp;테이블&amp;rdquo;&amp;nbsp;섹션을&amp;nbsp;추가해줘. &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp; ️&amp;nbsp;[팀&amp;nbsp;전체&amp;nbsp;업무&amp;nbsp;계획&amp;nbsp;테이블] &lt;br /&gt;제목:&amp;nbsp;&amp;ldquo;팀&amp;nbsp;전체&amp;nbsp;업무&amp;nbsp;계획&amp;nbsp;요약&amp;nbsp;(전주&amp;nbsp;계획&amp;nbsp;/&amp;nbsp;전주&amp;nbsp;달성&amp;nbsp;/&amp;nbsp;금주&amp;nbsp;계획)&amp;rdquo; &lt;br /&gt;&lt;br /&gt;테이블&amp;nbsp;형태로&amp;nbsp;구성하고,&amp;nbsp;다음&amp;nbsp;컬럼을&amp;nbsp;포함해줘: &lt;br /&gt;&lt;br /&gt;|&amp;nbsp;담당자&amp;nbsp;|&amp;nbsp;전주&amp;nbsp;계획&amp;nbsp;|&amp;nbsp;전주&amp;nbsp;달성&amp;nbsp;(YYYY.MM.DD&amp;nbsp;~&amp;nbsp;YYYY.MM.DD)&amp;nbsp;|&amp;nbsp;금주&amp;nbsp;계획&amp;nbsp;(YYYY.MM.DD&amp;nbsp;~&amp;nbsp;YYYY.MM.DD)&amp;nbsp;| &lt;br /&gt;&lt;br /&gt;각&amp;nbsp;컬럼별&amp;nbsp;내용&amp;nbsp;예시는&amp;nbsp;다음과&amp;nbsp;같아: &lt;br /&gt;-&amp;nbsp;**전주&amp;nbsp;계획:**&amp;nbsp;전주&amp;nbsp;주간보고에&amp;nbsp;작성된&amp;nbsp;예정&amp;nbsp;업무&amp;nbsp;내용&amp;nbsp;(예:&amp;nbsp;DB&amp;nbsp;이관&amp;nbsp;로그&amp;nbsp;정리,&amp;nbsp;차세대&amp;nbsp;관련&amp;nbsp;데이터&amp;nbsp;정비) &lt;br /&gt;-&amp;nbsp;**전주&amp;nbsp;달성:**&amp;nbsp;실제&amp;nbsp;완료된&amp;nbsp;내용&amp;nbsp;요약&amp;nbsp;(예:&amp;nbsp;DB&amp;nbsp;이관&amp;nbsp;완료,&amp;nbsp;장애&amp;nbsp;매뉴얼&amp;nbsp;작성) &lt;br /&gt;-&amp;nbsp;**금주&amp;nbsp;계획:**&amp;nbsp;이번&amp;nbsp;주&amp;nbsp;예정&amp;nbsp;업무&amp;nbsp;(예:&amp;nbsp;월간&amp;nbsp;단위&amp;nbsp;정기점검,&amp;nbsp;이미지&amp;nbsp;리사이징&amp;nbsp;수정) &lt;br /&gt;&lt;br /&gt;각&amp;nbsp;셀&amp;nbsp;안의&amp;nbsp;내용은&amp;nbsp;텍스트가&amp;nbsp;여러&amp;nbsp;줄로&amp;nbsp;표시될&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;하고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;텍스트가&amp;nbsp;많을&amp;nbsp;경우&amp;nbsp;자동&amp;nbsp;줄바꿈되게&amp;nbsp;구성해줘. &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;[디자인&amp;nbsp;구조] &lt;br /&gt;-&amp;nbsp;각&amp;nbsp;담당자별&amp;nbsp;행(Row)을&amp;nbsp;하나의&amp;nbsp;구분선으로&amp;nbsp;나누고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;전주계획&amp;nbsp;&amp;rarr;&amp;nbsp;전주달성&amp;nbsp;&amp;rarr;&amp;nbsp;금주계획을&amp;nbsp;좌&amp;rarr;우로&amp;nbsp;배치. &lt;br /&gt;-&amp;nbsp;표&amp;nbsp;상단에는&amp;nbsp;날짜&amp;nbsp;범위&amp;nbsp;표기&amp;nbsp;(예:&amp;nbsp;2025.09.29&amp;nbsp;~&amp;nbsp;2025.10.10) &lt;br /&gt;-&amp;nbsp;표는&amp;nbsp;회색&amp;nbsp;테두리와&amp;nbsp;가는&amp;nbsp;구분선으로&amp;nbsp;정돈. &lt;br /&gt;-&amp;nbsp;텍스트&amp;nbsp;정렬은&amp;nbsp;왼쪽&amp;nbsp;정렬,&amp;nbsp;행&amp;nbsp;간&amp;nbsp;간격은&amp;nbsp;넉넉히&amp;nbsp;띄워서&amp;nbsp;가독성&amp;nbsp;좋게. &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;[통합&amp;nbsp;구조] &lt;br /&gt;주간보고&amp;nbsp;페이지&amp;nbsp;전체&amp;nbsp;구조는&amp;nbsp;다음&amp;nbsp;순서로&amp;nbsp;구성해줘: &lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;이번주&amp;nbsp;업무&amp;nbsp;요약&amp;nbsp;&amp;nbsp; &lt;br /&gt;2.&amp;nbsp;팀&amp;nbsp;전체&amp;nbsp;회의&amp;nbsp;목록&amp;nbsp;&amp;nbsp; &lt;br /&gt;3.&amp;nbsp;하단&amp;nbsp;통계&amp;nbsp;(이번주&amp;nbsp;회의&amp;nbsp;/&amp;nbsp;작성자&amp;nbsp;/&amp;nbsp;미작성자)&amp;nbsp;&amp;nbsp; &lt;br /&gt;4.&amp;nbsp;**팀&amp;nbsp;전체&amp;nbsp;업무&amp;nbsp;계획&amp;nbsp;요약&amp;nbsp;테이블&amp;nbsp;(전주&amp;nbsp;계획,&amp;nbsp;전주&amp;nbsp;달성,&amp;nbsp;금주&amp;nbsp;계획)**&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;페이지는&amp;nbsp;세로&amp;nbsp;스크롤로&amp;nbsp;이어지게&amp;nbsp;구성하고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;섹션&amp;nbsp;간&amp;nbsp;여백은&amp;nbsp;충분히&amp;nbsp;확보해서&amp;nbsp;구분이&amp;nbsp;명확하도록&amp;nbsp;해줘. &lt;br /&gt;&lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;[추가&amp;nbsp;조건] &lt;br /&gt;-&amp;nbsp;폰트,&amp;nbsp;컬러,&amp;nbsp;버튼&amp;nbsp;스타일&amp;nbsp;등&amp;nbsp;기존&amp;nbsp;주간보고&amp;nbsp;페이지의&amp;nbsp;디자인&amp;nbsp;규칙을&amp;nbsp;변경하지&amp;nbsp;말&amp;nbsp;것.&amp;nbsp;&amp;nbsp; &lt;br /&gt;-&amp;nbsp;테이블은&amp;nbsp;반응형으로&amp;nbsp;구성되어,&amp;nbsp;인원&amp;nbsp;수가&amp;nbsp;많아도&amp;nbsp;스크롤로&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;만들어줘.&amp;nbsp;&amp;nbsp; &lt;br /&gt;-&amp;nbsp;상단&amp;nbsp;날짜&amp;nbsp;범위는&amp;nbsp;자동으로&amp;nbsp;이번&amp;nbsp;주&amp;nbsp;기준으로&amp;nbsp;업데이트될&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;구조로&amp;nbsp;설계해줘.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AqCJ2/btsQ8rFvyoZ/ciXf9LaVK7ZtTKir31kh8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AqCJ2/btsQ8rFvyoZ/ciXf9LaVK7ZtTKir31kh8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AqCJ2/btsQ8rFvyoZ/ciXf9LaVK7ZtTKir31kh8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAqCJ2%2FbtsQ8rFvyoZ%2FciXf9LaVK7ZtTKir31kh8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1316&quot; height=&quot;645&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런것도 수정하는게 똑똑하다&amp;nbsp;&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/96</guid>
      <comments>https://noeyodeeeeeel.tistory.com/96#entry96comment</comments>
      <pubDate>Tue, 14 Oct 2025 10:09:02 +0900</pubDate>
    </item>
    <item>
      <title>업무관리 프로젝트 (Figma make)</title>
      <link>https://noeyodeeeeeel.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 템플릿으로 시작&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 작성한 기능정의서와 CRUD를 바탕으로 ChatGPT가 프로프트를 정리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 프롬프&lt;/p&gt;
&lt;pre id=&quot;code_1760336943957&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Create a modern responsive web dashboard UI for an internal Project Management and Weekly Report System.
The system includes:
Login and Member Management
Admin can create, edit, and delete members
Fields: name, email, phone, password, role, join date, status
Project Management
Create, edit, and end projects
Manage project basic info: name, goal, period, status
Assign team members by role (backend, frontend, QA, designer, PM)
Comment and reply system
View projects in both Kanban and list modes
Manage TODO folders, progress tracking, and work hours
Weekly Dashboard
Weekly project summary
Previous week&amp;rsquo;s plan/result and this week&amp;rsquo;s plan
Work progress and effort tracking
Meeting notes section
Layout
Sidebar navigation: Members / Projects / Weekly Reports / Settings
Top bar: Search, User info
Main area: Tables, modals for CRUD actions

  Page Structure (페이지 구성)

1️⃣ 회원 관리 (Member Management)
회원 목록 / 등록 / 수정 / 삭제
2️⃣ 프로젝트 관리 (Project Management)
프로젝트 목록 / 생성 / 수정 / 종료
프로젝트 상세 보기
구성원 매핑 페이지 (역할 설정)
TODO / 폴더 관리 페이지
칸반보드 / 일반보드 보기 페이지
업무 진척도 / 공수 관리 페이지
3️⃣ 주간보고 대시보드 (Weekly Dashboard)
주간 업무 현황 대시보드
날짜별 업무 (전주 계획&amp;middot;달성 / 이번 주 계획)
업무 진척도 / 공수 시각화 페이지
회의록 관리 페이지
4️⃣ 기타 페이지 (Etc.)
로그인 / 회원가입 페이지
관리자 설정 페이지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프로젝트 관리 상세페이지 추가&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LpclB/btsQ8zb2ri3/6HGWoGWkjfYoqTQ3nQfFCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LpclB/btsQ8zb2ri3/6HGWoGWkjfYoqTQ3nQfFCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LpclB/btsQ8zb2ri3/6HGWoGWkjfYoqTQ3nQfFCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLpclB%2FbtsQ8zb2ri3%2F6HGWoGWkjfYoqTQ3nQfFCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1078&quot; height=&quot;1742&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;251&quot; data-start=&quot;167&quot; data-ke-size=&quot;size16&quot;&gt;Create a responsive &lt;b&gt;Project Management Detail Page&lt;/b&gt; for a web dashboard system.&lt;/p&gt;
&lt;p data-end=&quot;367&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;When a user clicks on a project in the project list page, open a &lt;b&gt;Project Detail Page&lt;/b&gt; like a board view.&lt;/p&gt;
&lt;p data-end=&quot;415&quot; data-start=&quot;374&quot; data-ke-size=&quot;size16&quot;&gt;The Project Detail Page should display:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;625&quot; data-start=&quot;418&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;442&quot; data-start=&quot;418&quot;&gt;Project name (프로젝트명)&lt;/li&gt;
&lt;li data-end=&quot;466&quot; data-start=&quot;445&quot;&gt;Project goal (목표)&lt;/li&gt;
&lt;li data-end=&quot;499&quot; data-start=&quot;469&quot;&gt;Project description (상세설명)&lt;/li&gt;
&lt;li data-end=&quot;525&quot; data-start=&quot;502&quot;&gt;Project period (기간)&lt;/li&gt;
&lt;li data-end=&quot;567&quot; data-start=&quot;528&quot;&gt;Project status (상태: 진행중, 계획중, 완료 등)&lt;/li&gt;
&lt;li data-end=&quot;592&quot; data-start=&quot;570&quot;&gt;Progress bar (진행률)&lt;/li&gt;
&lt;li data-end=&quot;621&quot; data-start=&quot;595&quot;&gt;Team members (참여 인원 수)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;703&quot; data-start=&quot;628&quot; data-ke-size=&quot;size16&quot;&gt;Below the project information, include a &lt;b&gt;&amp;ldquo;Add Task (할 일 추가)&amp;rdquo;&lt;/b&gt; section.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;864&quot; data-start=&quot;706&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;706&quot;&gt;Each task includes: task title (할 일), due period (기간), progress status (진행상태: 예정, 진행중, 완료).&lt;/li&gt;
&lt;li data-end=&quot;860&quot; data-start=&quot;804&quot;&gt;Tasks should be displayed in a list or board format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;903&quot; data-start=&quot;867&quot; data-ke-size=&quot;size16&quot;&gt;Add a &lt;b&gt;comment feature (댓글 기능)&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1034&quot; data-start=&quot;906&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;957&quot; data-start=&quot;906&quot;&gt;Users can write comments on the project itself.&lt;/li&gt;
&lt;li data-end=&quot;1030&quot; data-start=&quot;960&quot;&gt;Each task inside the project can also have its own comment thread.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1046&quot; data-start=&quot;1037&quot; data-ke-size=&quot;size16&quot;&gt;Layout:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1218&quot; data-start=&quot;1049&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1100&quot; data-start=&quot;1049&quot;&gt;Top area: project title, goal, and progress bar&lt;/li&gt;
&lt;li data-end=&quot;1152&quot; data-start=&quot;1103&quot;&gt;Middle area: task list with &amp;ldquo;Add Task&amp;rdquo; button&lt;/li&gt;
&lt;li data-end=&quot;1218&quot; data-start=&quot;1155&quot;&gt;Bottom area: comment section for both project and each task&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Point to edit 기능으로 Figma make에서 직접 수정하기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 수정할 부분 : 참여인원 클릭하면 참여인원 보기 , 할일 목록 보드형식 말고 게시판 형식으로 바꾸기, 진행률 수정,&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2621&quot; data-origin-height=&quot;1554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0tIx/btsQ8qzwDGS/I2nWxQoOYvlE5kD1m7KJT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0tIx/btsQ8qzwDGS/I2nWxQoOYvlE5kD1m7KJT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0tIx/btsQ8qzwDGS/I2nWxQoOYvlE5kD1m7KJT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0tIx%2FbtsQ8qzwDGS%2FI2nWxQoOYvlE5kD1m7KJT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2621&quot; height=&quot;1554&quot; data-origin-width=&quot;2621&quot; data-origin-height=&quot;1554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 알람 기능 삭제&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lqPig/btsQ7u3PGCQ/I1pwkN381u1c8bBgnEq6GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lqPig/btsQ7u3PGCQ/I1pwkN381u1c8bBgnEq6GK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lqPig/btsQ7u3PGCQ/I1pwkN381u1c8bBgnEq6GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlqPig%2FbtsQ7u3PGCQ%2FI1pwkN381u1c8bBgnEq6GK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;819&quot; height=&quot;264&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 참여인원 클릭하면 인원 확인할 수 있게&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMwAOj/btsQ6SwTBbN/sYXeqZy6VFIemKnijQgMLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMwAOj/btsQ6SwTBbN/sYXeqZy6VFIemKnijQgMLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMwAOj/btsQ6SwTBbN/sYXeqZy6VFIemKnijQgMLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMwAOj%2FbtsQ6SwTBbN%2FsYXeqZy6VFIemKnijQgMLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;427&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;795&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwXq7J/btsQ8rd8AX2/opWE98HajwI0uvB8MamnS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwXq7J/btsQ8rd8AX2/opWE98HajwI0uvB8MamnS1/img.png&quot; data-alt=&quot;참여인원 클릭 기능 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwXq7J/btsQ8rd8AX2/opWE98HajwI0uvB8MamnS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwXq7J%2FbtsQ8rd8AX2%2FopWE98HajwI0uvB8MamnS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;404&quot; height=&quot;1234&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;1234&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참여인원 클릭 기능 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/95</guid>
      <comments>https://noeyodeeeeeel.tistory.com/95#entry95comment</comments>
      <pubDate>Mon, 13 Oct 2025 17:16:23 +0900</pubDate>
    </item>
    <item>
      <title>CBT 튜토리얼 구현기 &amp;mdash; 페이지 설계와 개선 과정</title>
      <link>https://noeyodeeeeeel.tistory.com/93</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 프로젝트 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CBT 시험을 보기 전 수험자가 실제 시험 환경을 경험해볼 수 있도록 &lt;b&gt;튜토리얼 기능&lt;/b&gt;을 구현.&lt;br /&gt;튜토리얼은 수험자가 기본 환경을 점검하고 시험 방식을 익힐 수 있도록 다음 단계로 구성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;접수정보&amp;nbsp;확인&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;로그인&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;화면&amp;nbsp;조정&amp;nbsp;&amp;rarr;&amp;nbsp;키보드&amp;nbsp;확인&amp;nbsp;&amp;rarr;&amp;nbsp;마우스&amp;nbsp;확인&amp;nbsp;&amp;rarr;&amp;nbsp;문항&amp;nbsp;형태&amp;nbsp;&amp;rarr;&amp;nbsp;사용법&amp;nbsp;&amp;rarr;&amp;nbsp;예제&amp;nbsp;풀이&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr;&amp;nbsp;모의시험&amp;nbsp;풀어보기&amp;nbsp;페이지로&amp;nbsp;연결&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 초기 설계 &amp;mdash; JSP 페이지 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계를 &lt;b&gt;별도의 JSP 페이지&lt;/b&gt;로 나누어 구현함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;virtualTestStandby.jsp &amp;rarr;&amp;nbsp; 접수정보 확인&lt;/li&gt;
&lt;li&gt;virtualTestLogin.jsp &amp;rarr;&amp;nbsp; 로그인 화면&lt;/li&gt;
&lt;li&gt;tutoScreen.jsp &amp;rarr; 화면 조정&lt;/li&gt;
&lt;li&gt;tutoKeyboard.jsp &amp;rarr; 키보드 확인&lt;/li&gt;
&lt;li&gt;tutoMouse.jsp &amp;rarr; 마우스 확인&lt;/li&gt;
&lt;li&gt;tutoItemTp.jsp &amp;rarr; 문항 형태 확인&lt;/li&gt;
&lt;li&gt;tutoManual.jsp &amp;rarr; 사용법 안내&lt;/li&gt;
&lt;li&gt;tutoMockTest.jsp &amp;rarr; 예제 풀이&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에서 view 파라미터를 받아 각 JSP로 분기하는 구조를 사용함.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Controller 일부
@RequestMapping(&quot;/page.do&quot;)
public Object tutorial(HttpServletRequest request,
                       HttpServletResponse response,
                       @RequestParam(value=&quot;view&quot;, required=false) String view,
                       CHashMap param,
                       BindingAwareModelMapEx model) {
    // 기본 페이지 설정
    String resolved = (view == null || view.trim().isEmpty()) ? &quot;tutoScreen&quot; : view.trim();

    // JSP 경로 매핑
    String jspPath = VIEW_TO_JSP.getOrDefault(resolved, &quot;/tutorial/&quot; + resolved);
    jspPath = this.pathInfo.getJspPath(jspPath);

    model.addJspJsonValue(&quot;contentPage&quot;, &quot;tutorial/&quot; + resolved);
    model.addJspJsonValue(&quot;currentView&quot;, resolved);

    return setData(request, param, model, jspPath);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단계별 역할을 명확히 구분&lt;/b&gt;할 수 있었음.&lt;br /&gt;파일 이름만 봐도 화면 용도를 쉽게 파악할 수 있어, 다른 개발자가 보더라도 이해하기 편리했음.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 피드백 &amp;mdash; JSP 통합 필요성, 확인값 저장 기능 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드백&amp;nbsp;1&amp;nbsp;:&amp;nbsp;&amp;nbsp;컨트롤러에서&amp;nbsp;분기하는&amp;nbsp;구조보다&amp;nbsp;JSP&amp;nbsp;자체를&amp;nbsp;합쳐서&amp;nbsp;step&amp;nbsp;전환으로&amp;nbsp;처리하는&amp;nbsp;것이&amp;nbsp;좋다 &lt;br /&gt;하나의&amp;nbsp;tutorial.jsp&amp;nbsp;안에서&amp;nbsp;화면&amp;nbsp;전환만&amp;nbsp;스크립트로&amp;nbsp;처리하는&amp;nbsp;것이&amp;nbsp;더&amp;nbsp;단순하고&amp;nbsp;효율적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP 파일 수가 많아질수록 프로젝트 구조가 무거워짐&lt;/li&gt;
&lt;li&gt;단순 안내 화면까지 모두 분리할 필요는 없음&lt;/li&gt;
&lt;li&gt;유지보수 시 여러 파일을 동시에 열어야 해서 불편할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드백 2 : 사용자가 확인한 항목은 다시 반복해서 나오지 않도록 하는 것이 UX적으로 더 좋다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;체크박스 값, 튜토리얼 확인값을 LocalStorage에 저장.&lt;/li&gt;
&lt;li&gt;체크박스를 선택하지 않으면 다음단계로 넘어가지 않게 저장.&lt;/li&gt;
&lt;li&gt;사용자가 이미 확인한 단계는 다시 보지 않아도 되므로 불필요한 반복을 줄임.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758848065883&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	$(document).ready(function() {

		j$(document).ready(function () {
		  const KEY = &quot;checkboxChecked:tutoMouse&quot;;
		  const ctx = (j$.st.context || &quot;&quot;);

		  // 체크박스 상태 복원
		  if (localStorage.getItem(KEY) === &quot;true&quot;) {
		    j$(&quot;#mouse_Check&quot;).prop(&quot;checked&quot;, true);
		  }

		  // 체크박스 상태 저장
		  j$(&quot;#mouse_Check&quot;).on(&quot;change&quot;, function () {
		    if (j$(this).is(&quot;:checked&quot;)) localStorage.setItem(KEY, &quot;true&quot;);
		    else localStorage.removeItem(KEY);
		  });

		  // 다음 버튼
		  j$(&quot;#btn_next&quot;).off(&quot;click&quot;).on(&quot;click&quot;, function (e) {
		    e.preventDefault();
		    if (!j$(&quot;#mouse_Check&quot;).is(&quot;:checked&quot;)) {
		      (j$.alert)('&quot;PC 설정을 확인하였습니다(필수)&quot; 체크해 주세요.');
		      return;
		    }
		    window.location.href = ctx + &quot;page.do?view=tutoItemTp&quot;;
		  });
		});

	 // (마우스 체크 박스)메뉴 이동 제한
	  const MUST_CHECKED = new Set([&quot;사용법&quot;, &quot;예제 풀이&quot;, &quot;문항 형태&quot;]);
	  j$(&quot;.lnb_menu a&quot;).on(&quot;click&quot;, function (e) {
	    const text = j$(this).text().trim();
	    if (MUST_CHECKED.has(text)) {
	      const checked = localStorage.getItem(&quot;checkboxChecked:tutoMouse&quot;) === &quot;true&quot;;
	      if (!checked) {
	        e.preventDefault();
	        (j$.alert)(`마우스 확인의 체크박스를 선택해야 '${text}' 메뉴로 이동할 수 있습니다.`);
	      }
	    }
	  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 회고 &amp;mdash; 설계 의도와 배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 다른 개발자가 보았을 때 용도가 명확하고, 유지보수가 쉬운 구조를 만들고자 JSP를 단계별로 모두 분리했음.&lt;br /&gt;이 접근은 직관적이었고, 화면 역할을 이해하기 쉽게 만드는 데 효과적이었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로 규모 있는 프로젝트에 적용하다 보니&amp;nbsp;&lt;br /&gt;&lt;b&gt;프로젝트 무게나 성능, 유지보수 효율성&lt;/b&gt;을 고려하지 못했다는 것을 깨닫게 됨.&lt;br /&gt;당시에는 &lt;b&gt;기능을 충분히 수행하는 것&lt;/b&gt;에만 집중했으나&lt;br /&gt;피드백을 통해 시스템 전체의 크기와 구조적 단순화까지 고려해야 한다 는 점을 배우는 계기가 되었음.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝트 구조를 어떻게 잡아야 하는가&lt;/b&gt;에 대해 생각해볼 수 있는 기회였음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작은 단위로 쪼개는 것만이 항상 좋은 것은 아님&lt;/li&gt;
&lt;li&gt;프로젝트의 규모, 무게, 유지보수성을 종합적으로 고려해야 함&lt;/li&gt;
&lt;li&gt;협업 환경에서는 가독성과 단순화의 균형이 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 &lt;b&gt;기능 구현뿐 아니라 전체 시스템 구조를 고려한 설계&lt;/b&gt;를 지향할 계획임.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758786516298&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; VIEW_WHITELIST = new HashSet&amp;lt;&amp;gt;();
        VIEW_WHITELIST.add(&quot;tutoScreen&quot;);
        VIEW_WHITELIST.add(&quot;tutoKeyboard&quot;);
        VIEW_WHITELIST.add(&quot;tutoMouse&quot;);
        VIEW_WHITELIST.add(&quot;tutoItemTp&quot;);
        VIEW_WHITELIST.add(&quot;tutoManual&quot;);
        VIEW_WHITELIST.add(&quot;tutoMockTest&quot;);
        VIEW_WHITELIST.add(&quot;mockTestPrac&quot;);

        VIEW_TO_JSP = new HashMap&amp;lt;&amp;gt;();
        VIEW_TO_JSP.put(&quot;tutoScreen&quot;  , &quot;/tutorial/tutoScreen&quot;);
        VIEW_TO_JSP.put(&quot;tutoKeyboard&quot;, &quot;/tutorial/tutoKeyboard&quot;);
        VIEW_TO_JSP.put(&quot;tutoMouse&quot;   , &quot;/tutorial/tutoMouse&quot;);
        VIEW_TO_JSP.put(&quot;tutoItemTp&quot;  , &quot;/tutorial/tutoItemTp&quot;);
        VIEW_TO_JSP.put(&quot;tutoManual&quot;  , &quot;/tutorial/tutoManual&quot;);
        VIEW_TO_JSP.put(&quot;tutoMockTest&quot;, &quot;/tutorial/tutoMockTest&quot;);
        VIEW_TO_JSP.put(&quot;mockTestPrac&quot;, &quot;/tutorial/mockTestPrac&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1758786543380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @RequestMapping(&quot;/page.do&quot;)
    public Object tutorial(HttpServletRequest request,
                           HttpServletResponse response,
                           @RequestParam(value=&quot;view&quot;, required=false) String view,
                           CHashMap param,
                           BindingAwareModelMapEx model) {

        // 좌석/로그인/상태 값 주입 제거
        request.setAttribute(&quot;layoutPage&quot;, this.pathInfo.getJspPath(&quot;/cbt/examne/index&quot;));

        // 검증
        String resolved = (view == null || view.trim().isEmpty()) ? &quot;tutoScreen&quot; : view.trim();
        if (!VIEW_WHITELIST.contains(resolved)) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            String notFound = this.pathInfo.getJspPath(&quot;/error/404&quot;);
            return setData(request, param, model, notFound);
        }

        // JSP 경로 매핑
        String jspPath = VIEW_TO_JSP.getOrDefault(resolved, &quot;/tutorial/&quot; + resolved);
        jspPath = this.pathInfo.getJspPath(jspPath);

        model.addJspJsonValue(&quot;contentPage&quot;, &quot;tutorial/&quot; + resolved);
        model.addJspJsonValue(&quot;currentView&quot;, resolved);

        return setData(request, param, model, jspPath);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 회고 &amp;mdash; 설계 의도와 배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 **&amp;ldquo;다른 개발자가 보았을 때 용도가 명확하고, 유지보수가 쉬운 구조&amp;rdquo;**를 만들고자 JSP를 단계별로 모두 분리했음.&lt;br /&gt;이 접근은 직관적이었고, 화면 역할을 이해하기 쉽게 만드는 데 효과적이었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로 규모 있는 프로젝트에 적용하다 보니,&lt;br /&gt;&lt;b&gt;프로젝트 무게나 성능, 유지보수 효율성&lt;/b&gt;을 고려하지 못했다는 한계를 깨닫게 됨.&lt;br /&gt;당시에는 &amp;ldquo;기능을 충분히 수행하는 것&amp;rdquo;에만 집중했으나,&lt;br /&gt;피드백을 통해 **&amp;ldquo;시스템 전체의 크기와 구조적 단순화까지 고려해야 한다&amp;rdquo;**는 점을 배우는 계기가 되었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h1 data-end=&quot;102&quot; data-start=&quot;85&quot;&gt;⚡ 트러블슈팅과 배운 점&lt;/h1&gt;
&lt;h2 data-end=&quot;121&quot; data-start=&quot;104&quot; data-ke-size=&quot;size26&quot;&gt;1. 로그인 오류 문제&lt;/h2&gt;
&lt;p data-end=&quot;342&quot; data-start=&quot;122&quot; data-ke-size=&quot;size16&quot;&gt;튜토리얼 개발 과정에서 &lt;b&gt;로그인 오류 현상&lt;/b&gt;이 발생함.&lt;br /&gt;처음에는 원인을 특정하기 어려웠으나, 사무실과 집을 오가며 개발 환경을 바꾸는 과정에서 &lt;b&gt;IP 주소 설정이 달라졌던 점&lt;/b&gt;이 원인으로 판단됨.&lt;br /&gt;결국 네트워크 환경에 따라 세션이나 접근 경로가 달라지면서 로그인 리다이렉트 오류가 발생한 것이었음.&lt;/p&gt;
&lt;hr data-end=&quot;347&quot; data-start=&quot;344&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;369&quot; data-start=&quot;349&quot; data-ke-size=&quot;size26&quot;&gt;2. Swiper 적용 과정&lt;/h2&gt;
&lt;p data-end=&quot;500&quot; data-start=&quot;370&quot; data-ke-size=&quot;size16&quot;&gt;마지막 예제 풀이와 모의시험 페이지를 구현하면서 &lt;b&gt;Swiper&lt;/b&gt;를 도입함.&lt;br /&gt;처음에는 기본 설정만으로 슬라이드 이동을 구현했으나, 실제 사용 시 &lt;b&gt;민감도가 높아 의도치 않은 슬라이드 전환이 자주 발생하는 문제&lt;/b&gt;가 있었음.&lt;/p&gt;
&lt;p data-end=&quot;552&quot; data-start=&quot;502&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 Swiper 옵션을 조정하여 &lt;b&gt;민감도를 낮추는 설정&lt;/b&gt;을 적용함.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1758789168860&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const swiper = new Swiper('.swiper-container', {
  nextButton: '.question_next_btn',
  prevButton: '.question_prev_btn',
  threshold: 30,       // 터치 이동 최소 거리 (민감도 낮춤)
  touchRatio: 0.5,     // 슬라이드 전환 민감도 조정
  onSlideChangeEnd() {
    updatePager(swiper);
    syncTabs(swiper);
    restoreSelectedVisualForSlide(swiper.activeIndex);
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1021&quot; data-start=&quot;915&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;970&quot; data-start=&quot;915&quot;&gt;threshold: 30 &amp;rarr; 손가락/마우스를 일정 거리 이상 움직여야만 슬라이드가 넘어감&lt;/li&gt;
&lt;li data-end=&quot;1021&quot; data-start=&quot;971&quot;&gt;touchRatio: 0.5 &amp;rarr; 터치/드래그 민감도를 낮춰서 불필요한 전환 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1091&quot; data-start=&quot;1023&quot; data-ke-size=&quot;size16&quot;&gt;이 설정을 통해 실제 시험 환경과 유사한 UX를 제공하면서도, &lt;b&gt;사용자의 실수로 인한 불편을 줄이는 방법&lt;/b&gt;을 배움.&lt;/p&gt;
&lt;hr data-end=&quot;1096&quot; data-start=&quot;1093&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1108&quot; data-start=&quot;1098&quot; data-ke-size=&quot;size26&quot;&gt;3. 회고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1241&quot; data-start=&quot;1109&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1173&quot; data-start=&quot;1109&quot;&gt;로그인 오류를 통해 &lt;b&gt;개발 환경(IP, 네트워크) 차이가 예상치 못한 문제를 유발할 수 있음&lt;/b&gt;을 경험함.&lt;/li&gt;
&lt;li data-end=&quot;1241&quot; data-start=&quot;1174&quot;&gt;Swiper 적용 과정에서 단순 기능 사용을 넘어서, &lt;b&gt;옵션 조정을 통해 UX 품질을 개선하는 방법&lt;/b&gt;을 배움.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>work</category>
      <author>noeyodeeeeeel</author>
      <guid isPermaLink="true">https://noeyodeeeeeel.tistory.com/93</guid>
      <comments>https://noeyodeeeeeel.tistory.com/93#entry93comment</comments>
      <pubDate>Thu, 25 Sep 2025 16:57:24 +0900</pubDate>
    </item>
  </channel>
</rss>