팀 위클리 스프린트 리포트 - 주간 성과 & 목표 달성률 시각화 - 바이브코딩 레시피
중급 PHP + JavaScript Laravel + React + Inertia.js AI: Claude

팀 위클리 스프린트 리포트 - 주간 성과 & 목표 달성률 시각화

리포지토리명 team-weekly-sprint-report
조회수 56 0건 제출

문제 설명

프로젝트 개요

애자일 팀의 주간 스프린트 성과를 기록하고 시각화하는 풀스택 대시보드를 만듭니다. Laravel 13.x + Inertia.js + React 19 모노리스 아키텍처로 구성하며, 팀원들이 매주 자신의 작업 완료율, 목표, 블로커를 입력하고 팀 리더가 전체 현황을 한눈에 볼 수 있습니다.

필수 기능

  • 주간 리포트 작성: 팀원별로 이번 주 완료한 작업, 다음 주 목표, 블로커(장애물) 입력
  • 완료율 계산: 계획 대비 실제 완료율을 퍼센트로 자동 계산
  • 팀 대시보드: 모든 팀원의 리포트를 주차별로 조회, 완료율 평균 표시
  • 필터링: 주차 선택, 팀원 필터, 완료율 기준 정렬
  • 간단한 통계: 최근 4주간 완료율 추이를 막대 그래프로 표시 (Chart.js 활용)
  • 반응형 UI: Tailwind CSS로 모바일/데스크톱 대응

선택 추가 기능 (보너스)

  • 리포트 작성 시 마크다운 지원 (작업 내용 상세 기록)
  • 블로커에 우선순위 태그 추가 (긴급/보통/낮음)
  • 팀 리더가 각 리포트에 간단한 코멘트 남기기
  • 주간 리포트 PDF 내보내기
  • 이메일 알림: 금요일마다 리포트 작성 리마인더 발송

기술 스택

  • 백엔드: Laravel 13.x (컨트롤러, Eloquent ORM, SQLite)
  • 프론트엔드: React 19 (함수형 컴포넌트, useState/useEffect)
  • 연결: Inertia.js (Laravel 라우트 ↔ React 컴포넌트)
  • 스타일링: Tailwind CSS
  • 차트: Chart.js + react-chartjs-2
  • DB: SQLite (개발 단계에서 간편하게)

제약 조건

  • • Laravel 13.x + Inertia.js + React 19 모노리스 구조 (별도 API 라우트 없음)
  • • Laravel Breeze (React + Inertia) starter kit으로 프로젝트 생성
  • • SQLite 데이터베이스 사용 (config/database.php 기본 설정)
  • • Inertia::render()로 Laravel 컨트롤러에서 React 컴포넌트에 데이터 전달
  • • React 19의 함수형 컴포넌트와 Hooks (useState, useEffect) 사용
  • • Tailwind CSS로 반응형 디자인 구현
  • • Chart.js로 완료율 추이 시각화
  • • 마이그레이션으로 DB 스키마 관리 (팀원, 주간 리포트 테이블)
  • • 복잡한 상태관리 라이브러리(Redux, Zustand 등) 사용 금지 - React Hooks만 사용
  • • 주차(week number) 계산은 PHP의 date('W') 함수 활용

프롬프트 레시피

아래 프롬프트를 순서대로 AI에게 보내면 됩니다. 복사 버튼을 눌러 바로 사용하세요.

1
Step 1
mkdir team-weekly-sprint-report && cd team-weekly-sprint-report

이 폴더에서 Laravel 13.x + Inertia.js + React 19 프로젝트를 시작합니다.

다음 작업을 수행해주세요:

1. Laravel Breeze (React + Inertia) starter kit으로 프로젝트 생성:
   - `npx create laravel@latest . --breeze --stack=react --typescript=no`
   - 또는 `composer create-project laravel/laravel . && php artisan breeze:install react --inertia`
   - Breeze는 인증(로그인/회원가입)과 Inertia.js + React 설정을 자동으로 구성합니다.

2. SQLite 데이터베이스 설정:
   - `.env` 파일에서 `DB_CONNECTION=sqlite` 확인
   - `database/database.sqlite` 파일 생성 (`touch database/database.sqlite` 또는 빈 파일 생성)

3. 기본 마이그레이션 실행:
   - `php artisan migrate` (users 테이블 등 기본 테이블 생성)

4. 개발 서버 실행 확인:
   - `php artisan serve` (백엔드 서버 http://localhost:8000)
   - 별도 터미널에서 `npm install && npm run dev` (Vite 프론트엔드 빌드)
   - 브라우저에서 http://localhost:8000 접속하여 Laravel Breeze 기본 화면 확인

5. 프로젝트 구조 설명:
   - `app/Http/Controllers`: Laravel 컨트롤러 (Inertia::render()로 React 컴포넌트 호출)
   - `resources/js/Pages`: React 페이지 컴포넌트
   - `resources/js/Components`: 재사용 가능한 React 컴포넌트
   - `routes/web.php`: 웹 라우트 정의 (Inertia 라우트)

완료되면 "Laravel + Inertia + React 프로젝트 초기 설정 완료" 메시지를 출력하고, 다음 단계로 넘어갈 준비가 되었음을 알려주세요.

기대 결과: Laravel 13.x + Breeze + Inertia + React 19 프로젝트가 생성되고, SQLite DB가 설정되며, 기본 인증 기능이 작동하는 상태. 개발 서버 실행 시 Laravel Breeze 기본 홈 화면이 표시됨.

2
Step 2
주간 리포트를 저장할 데이터베이스 테이블을 설계합니다.

다음 작업을 수행해주세요:

1. **TeamMember 모델 및 마이그레이션 생성**:
   - `php artisan make:model TeamMember -m`
   - 마이그레이션 파일 (`database/migrations/xxxx_create_team_members_table.php`)에 다음 컬럼 추가:
     - `id` (자동 생성)
     - `name` (string, 팀원 이름)
     - `email` (string, unique, 이메일)
     - `role` (string, nullable, 역할: Developer, Designer, PM 등)
     - `timestamps` (created_at, updated_at)

2. **WeeklyReport 모델 및 마이그레이션 생성**:
   - `php artisan make:model WeeklyReport -m`
   - 마이그레이션 파일에 다음 컬럼 추가:
     - `id`
     - `team_member_id` (unsignedBigInteger, foreign key → team_members.id)
     - `year` (integer, 연도: 2025)
     - `week_number` (integer, 주차: 1~53)
     - `completed_tasks` (text, 완료한 작업 목록)
     - `next_week_goals` (text, 다음 주 목표)
     - `blockers` (text, nullable, 블로커/장애물)
     - `completion_rate` (integer, 0~100, 완료율 퍼센트)
     - `timestamps`
     - 외래 키 제약: `foreign('team_member_id')->references('id')->on('team_members')->onDelete('cascade')`

3. **Eloquent 관계 설정**:
   - `TeamMember` 모델에 `weeklyReports()` 관계 추가:
     ```php
     public function weeklyReports() {
         return $this->hasMany(WeeklyReport::class);
     }
     ```
   - `WeeklyReport` 모델에 `teamMember()` 관계 추가:
     ```php
     public function teamMember() {
         return $this->belongsTo(TeamMember::class);
     }
     ```

4. **마이그레이션 실행**:
   - `php artisan migrate`

5. **시더(Seeder) 생성 및 더미 데이터 추가** (선택 사항이지만 권장):
   - `php artisan make:seeder TeamMemberSeeder`
   - `database/seeders/TeamMemberSeeder.php`에서 3~5명의 팀원 더미 데이터 생성
   - `DatabaseSeeder.php`에서 `$this->call(TeamMemberSeeder::class);` 호출
   - `php artisan db:seed`

완료되면 "데이터베이스 스키마 및 모델 설정 완료" 메시지를 출력하고, TeamMember와 WeeklyReport 테이블이 생성되었음을 확인해주세요.

기대 결과: team_members 및 weekly_reports 테이블이 생성되고, Eloquent 모델 관계가 설정됨. 더미 팀원 데이터가 시딩되어 있으면 더욱 좋음.

3
Step 3
주간 리포트 CRUD 기능을 위한 Laravel 컨트롤러와 라우트를 생성합니다.

다음 작업을 수행해주세요:

1. **WeeklyReportController 생성**:
   - `php artisan make:controller WeeklyReportController`
   - 다음 메서드 구현:
     - `index()`: 모든 주간 리포트 목록 조회 (최신순 정렬, 팀원 정보 eager loading)
       - `WeeklyReport::with('teamMember')->orderBy('year', 'desc')->orderBy('week_number', 'desc')->get()`
       - `Inertia::render('WeeklyReports/Index', ['reports' => $reports])`
     - `create()`: 리포트 작성 폼 표시
       - 모든 팀원 목록을 가져와서 선택 가능하게 함
       - 현재 연도와 주차를 자동으로 계산하여 전달 (`date('Y')`, `date('W')`)
       - `Inertia::render('WeeklyReports/Create', ['teamMembers' => $teamMembers, 'currentYear' => $year, 'currentWeek' => $week])`
     - `store(Request $request)`: 새 리포트 저장
       - 유효성 검사: team_member_id, year, week_number, completed_tasks, next_week_goals, completion_rate (0~100) 필수
       - `WeeklyReport::create($validated)`
       - 저장 후 `redirect()->route('weekly-reports.index')`
     - `edit($id)`: 리포트 수정 폼 표시
       - `Inertia::render('WeeklyReports/Edit', ['report' => $report, 'teamMembers' => $teamMembers])`
     - `update(Request $request, $id)`: 리포트 수정 저장
     - `destroy($id)`: 리포트 삭제

2. **라우트 등록** (`routes/web.php`):
   - 인증된 사용자만 접근 가능하도록 `auth` 미들웨어 그룹 내에 추가:
     ```php
     Route::middleware('auth')->group(function () {
         Route::resource('weekly-reports', WeeklyReportController::class);
     });
     ```
   - 또는 개별 라우트로 정의:
     ```php
     Route::get('/weekly-reports', [WeeklyReportController::class, 'index'])->name('weekly-reports.index');
     Route::get('/weekly-reports/create', [WeeklyReportController::class, 'create'])->name('weekly-reports.create');
     Route::post('/weekly-reports', [WeeklyReportController::class, 'store'])->name('weekly-reports.store');
     Route::get('/weekly-reports/{id}/edit', [WeeklyReportController::class, 'edit'])->name('weekly-reports.edit');
     Route::put('/weekly-reports/{id}', [WeeklyReportController::class, 'update'])->name('weekly-reports.update');
     Route::delete('/weekly-reports/{id}', [WeeklyReportController::class, 'destroy'])->name('weekly-reports.destroy');
     ```

3. **네비게이션 메뉴 추가**:
   - `resources/js/Layouts/AuthenticatedLayout.jsx` (또는 Breeze의 기본 레이아웃)에서 네비게이션 링크 추가:
     ```jsx
     <Link href={route('weekly-reports.index')}>주간 리포트</Link>
     ```

완료되면 "WeeklyReportController 및 라우트 설정 완료" 메시지를 출력하고, `/weekly-reports` 경로에 접근 시 Inertia가 React 컴포넌트를 렌더링하도록 준비되었음을 알려주세요.

기대 결과: WeeklyReportController가 생성되고, CRUD 라우트가 등록됨. 네비게이션 메뉴에 "주간 리포트" 링크가 추가됨. 아직 React 컴포넌트는 없지만 라우트는 작동 가능한 상태.

4
Step 4
React 페이지 컴포넌트를 생성하여 주간 리포트 목록과 작성 폼을 구현합니다.

다음 작업을 수행해주세요:

1. **리포트 목록 페이지** (`resources/js/Pages/WeeklyReports/Index.jsx`):
   - Inertia의 `usePage` 훅으로 `reports` 데이터를 받아옴
   - 테이블 형태로 리포트 목록 표시:
     - 컬럼: 주차(Year-Week), 팀원 이름, 완료율(%), 작성일, 액션(수정/삭제 버튼)
   - 완료율에 따라 색상 구분:
     - 80% 이상: 초록색 배지
     - 50~79%: 노란색 배지
     - 50% 미만: 빨간색 배지
   - "새 리포트 작성" 버튼 추가 (Link to `weekly-reports.create`)
   - Tailwind CSS로 반응형 테이블 스타일링
   - 예시 구조:
     ```jsx
     import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
     import { Head, Link } from '@inertiajs/react';

     export default function Index({ auth, reports }) {
         return (
             <AuthenticatedLayout user={auth.user}>
                 <Head title="주간 리포트" />
                 <div className="py-12">
                     <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                         <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                             <div className="p-6">
                                 <div className="flex justify-between items-center mb-4">
                                     <h2 className="text-2xl font-bold">팀 주간 리포트</h2>
                                     <Link href={route('weekly-reports.create')} className="bg-blue-500 text-white px-4 py-2 rounded">새 리포트 작성</Link>
                                 </div>
                                 <table className="w-full">
                                     <thead>
                                         <tr className="border-b">
                                             <th className="text-left p-2">주차</th>
                                             <th className="text-left p-2">팀원</th>
                                             <th className="text-left p-2">완료율</th>
                                             <th className="text-left p-2">작성일</th>
                                             <th className="text-left p-2">액션</th>
                                         </tr>
                                     </thead>
                                     <tbody>
                                         {reports.map(report => (
                                             <tr key={report.id} className="border-b">
                                                 <td className="p-2">{report.year}-W{report.week_number}</td>
                                                 <td className="p-2">{report.team_member.name}</td>
                                                 <td className="p-2">
                                                     <span className={`px-2 py-1 rounded ${
                                                         report.completion_rate >= 80 ? 'bg-green-200 text-green-800' :
                                                         report.completion_rate >= 50 ? 'bg-yellow-200 text-yellow-800' :
                                                         'bg-red-200 text-red-800'
                                                     }`}>
                                                         {report.completion_rate}%
                                                     </span>
                                                 </td>
                                                 <td className="p-2">{new Date(report.created_at).toLocaleDateString()}</td>
                                                 <td className="p-2">
                                                     <Link href={route('weekly-reports.edit', report.id)} className="text-blue-500 mr-2">수정</Link>
                                                     <button onClick={() => handleDelete(report.id)} className="text-red-500">삭제</button>
                                                 </td>
                                             </tr>
                                         ))}
                                     </tbody>
                                 </table>
                             </div>
                         </div>
                     </div>
                 </div>
             </AuthenticatedLayout>
         );
     }
     ```

2. **리포트 작성 폼 페이지** (`resources/js/Pages/WeeklyReports/Create.jsx`):
   - `usePage`로 `teamMembers`, `currentYear`, `currentWeek` 데이터를 받아옴
   - `useState`로 폼 데이터 관리 (team_member_id, year, week_number, completed_tasks, next_week_goals, blockers, completion_rate)
   - Inertia의 `useForm` 훅 또는 수동 `router.post()` 사용하여 제출
   - 폼 필드:
     - 팀원 선택 (select 드롭다운)
     - 연도/주차 (기본값으로 현재 값 표시, 수정 가능)
     - 완료한 작업 (textarea)
     - 다음 주 목표 (textarea)
     - 블로커 (textarea, 선택 사항)
     - 완료율 (number input, 0~100, 슬라이더로 변경 가능)
   - 제출 후 `weekly-reports.index`로 리다이렉트
   - Tailwind CSS로 폼 스타일링

3. **삭제 기능 구현**:
   - `Index.jsx`에서 `handleDelete` 함수 추가:
     ```jsx
     import { router } from '@inertiajs/react';

     const handleDelete = (id) => {
         if (confirm('정말 삭제하시겠습니까?')) {
             router.delete(route('weekly-reports.destroy', id));
         }
     };
     ```

완료되면 "주간 리포트 목록 및 작성 폼 React 컴포넌트 생성 완료" 메시지를 출력하고, 브라우저에서 리포트 작성 및 목록 조회가 가능한지 확인해주세요.

기대 결과: 주간 리포트 목록 페이지(Index.jsx)와 작성 폼 페이지(Create.jsx)가 작동함. 팀원 선택, 작업 내용 입력, 완료율 설정 후 저장 가능. 목록에서 완료율에 따라 색상이 다르게 표시됨.

5
Step 5
팀 대시보드 페이지를 생성하여 주간 리포트 통계와 완료율 추이를 시각화합니다.

다음 작업을 수행해주세요:

1. **DashboardController 생성 또는 기존 컨트롤러에 메서드 추가**:
   - `php artisan make:controller DashboardController` (또는 기존 컨트롤러 활용)
   - `index()` 메서드에서 다음 데이터 계산:
     - 최근 4주간의 평균 완료율 (주차별로 그룹화)
     - 현재 주차의 모든 팀원 리포트
     - 전체 팀원 수 및 이번 주 리포트 제출 인원
   - 예시 쿼리:
     ```php
     $currentYear = date('Y');
     $currentWeek = date('W');
     
     // 최근 4주 데이터
     $recentWeeks = WeeklyReport::selectRaw('year, week_number, AVG(completion_rate) as avg_rate')
         ->where('year', $currentYear)
         ->where('week_number', '>=', $currentWeek - 3)
         ->groupBy('year', 'week_number')
         ->orderBy('week_number', 'asc')
         ->get();
     
     // 현재 주 리포트
     $thisWeekReports = WeeklyReport::with('teamMember')
         ->where('year', $currentYear)
         ->where('week_number', $currentWeek)
         ->get();
     
     return Inertia::render('Dashboard', [
         'recentWeeks' => $recentWeeks,
         'thisWeekReports' => $thisWeekReports,
         'teamMemberCount' => TeamMember::count(),
         'submittedCount' => $thisWeekReports->count(),
     ]);
     ```

2. **라우트 등록**:
   - `routes/web.php`에서 대시보드 라우트를 `/dashboard` 또는 `/` (홈)으로 설정
   - Breeze 기본 설정에서 `/dashboard` 라우트가 이미 있을 수 있으므로 해당 라우트를 수정

3. **Chart.js 설치**:
   - `npm install chart.js react-chartjs-2`

4. **Dashboard React 컴포넌트** (`resources/js/Pages/Dashboard.jsx`):
   - `usePage`로 `recentWeeks`, `thisWeekReports`, `teamMemberCount`, `submittedCount` 데이터를 받아옴
   - **상단 요약 카드**:
     - 전체 팀원 수
     - 이번 주 리포트 제출 인원 (제출률 % 표시)
     - 이번 주 평균 완료율
   - **완료율 추이 차트** (막대 그래프):
     - Chart.js의 Bar 차트 사용
     - X축: 주차 (예: W10, W11, W12, W13)
     - Y축: 평균 완료율 (%)
     - `react-chartjs-2`의 `Bar` 컴포넌트 사용
   - **이번 주 팀원별 리포트 목록**:
     - 각 팀원의 완료율, 완료한 작업 요약, 블로커 표시
     - 블로커가 있는 경우 빨간색 배지로 강조
   - Tailwind CSS로 대시보드 레이아웃 구성 (그리드 또는 플렉스 박스)
   - 예시 차트 코드:
     ```jsx
     import { Bar } from 'react-chartjs-2';
     import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';

     ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);

     export default function Dashboard({ auth, recentWeeks, thisWeekReports, teamMemberCount, submittedCount }) {
         const chartData = {
             labels: recentWeeks.map(w => `W${w.week_number}`),
             datasets: [
                 {
                     label: '평균 완료율 (%)',
                     data: recentWeeks.map(w => w.avg_rate),
                     backgroundColor: 'rgba(59, 130, 246, 0.5)',
                     borderColor: 'rgba(59, 130, 246, 1)',
                     borderWidth: 1,
                 },
             ],
         };

         const chartOptions = {
             responsive: true,
             plugins: {
                 legend: { position: 'top' },
                 title: { display: true, text: '최근 4주 완료율 추이' },
             },
             scales: {
                 y: { beginAtZero: true, max: 100 },
             },
         };

         return (
             <AuthenticatedLayout user={auth.user}>
                 <Head title="대시보드" />
                 <div className="py-12">
                     <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                         {/* 요약 카드 */}
                         <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
                             <div className="bg-white p-6 rounded-lg shadow">
                                 <h3 className="text-lg font-semibold">전체 팀원</h3>
                                 <p className="text-3xl font-bold">{teamMemberCount}명</p>
                             </div>
                             <div className="bg-white p-6 rounded-lg shadow">
                                 <h3 className="text-lg font-semibold">이번 주 제출률</h3>
                                 <p className="text-3xl font-bold">{Math.round((submittedCount / teamMemberCount) * 100)}%</p>
                             </div>
                             <div className="bg-white p-6 rounded-lg shadow">
                                 <h3 className="text-lg font-semibold">이번 주 평균 완료율</h3>
                                 <p className="text-3xl font-bold">
                                     {thisWeekReports.length > 0 ? Math.round(thisWeekReports.reduce((sum, r) => sum + r.completion_rate, 0) / thisWeekReports.length) : 0}%
                                 </p>
                             </div>
                         </div>

                         {/* 차트 */}
                         <div className="bg-white p-6 rounded-lg shadow mb-6">
                             <Bar data={chartData} options={chartOptions} />
                         </div>

                         {/* 이번 주 리포트 목록 */}
                         <div className="bg-white p-6 rounded-lg shadow">
                             <h2 className="text-2xl font-bold mb-4">이번 주 팀원 리포트</h2>
                             {thisWeekReports.map(report => (
                                 <div key={report.id} className="border-b py-4">
                                     <div className="flex justify-between items-center">
                                         <h3 className="font-semibold">{report.team_member.name}</h3>
                                         <span className={`px-2 py-1 rounded ${
                                             report.completion_rate >= 80 ? 'bg-green-200 text-green-800' :
                                             report.completion_rate >= 50 ? 'bg-yellow-200 text-yellow-800' :
                                             'bg-red-200 text-red-800'
                                         }`}>
                                             {report.completion_rate}%
                                         </span>
                                     </div>
                                     <p className="text-sm text-gray-600 mt-2">완료: {report.completed_tasks.substring(0, 100)}...</p>
                                     {report.blockers && (
                                         <p className="text-sm text-red-600 mt-1">⚠️ 블로커: {report.blockers}</p>
                                     )}
                                 </div>
                             ))}
                         </div>
                     </div>
                 </div>
             </AuthenticatedLayout>
         );
     }
     ```

완료되면 "팀 대시보드 페이지 생성 완료" 메시지를 출력하고, 브라우저에서 대시보드에 접속하여 차트와 리포트 목록이 정상적으로 표시되는지 확인해주세요.

기대 결과: 대시보드 페이지가 생성되고, 최근 4주 완료율 추이가 막대 그래프로 시각화됨. 이번 주 팀원별 리포트와 블로커가 목록으로 표시됨. 요약 카드에 팀원 수, 제출률, 평균 완료율이 표시됨.

6
Step 6
주간 리포트 수정 기능과 필터링/정렬 기능을 추가하여 프로젝트를 완성합니다.

다음 작업을 수행해주세요:

1. **리포트 수정 페이지** (`resources/js/Pages/WeeklyReports/Edit.jsx`):
   - `Create.jsx`와 유사하지만, 기존 데이터를 폼에 미리 채워넣음
   - `usePage`로 `report`, `teamMembers` 데이터를 받아옴
   - `useState`로 폼 데이터 초기화 (기존 리포트 데이터로)
   - `router.put(route('weekly-reports.update', report.id), formData)` 형태로 제출
   - 제출 후 `weekly-reports.index`로 리다이렉트
   - Tailwind CSS로 폼 스타일링 (Create와 동일)

2. **필터링 및 정렬 기능** (Index.jsx 개선):
   - **주차 필터**: 드롭다운으로 최근 8주 중 선택 가능
   - **팀원 필터**: 드롭다운으로 특정 팀원의 리포트만 표시
   - **완료율 정렬**: 높은 순/낮은 순 버튼
   - `useState`로 필터 상태 관리
   - 필터 변경 시 `router.get(route('weekly-reports.index'), { week: selectedWeek, member: selectedMember, sort: sortOrder })` 형태로 쿼리 파라미터 전달
   - Laravel 컨트롤러에서 쿼리 파라미터를 받아 필터링:
     ```php
     public function index(Request $request) {
         $query = WeeklyReport::with('teamMember');
         
         if ($request->has('week')) {
             $query->where('week_number', $request->week);
         }
         
         if ($request->has('member')) {
             $query->where('team_member_id', $request->member);
         }
         
         if ($request->has('sort')) {
             $query->orderBy('completion_rate', $request->sort === 'asc' ? 'asc' : 'desc');
         } else {
             $query->orderBy('year', 'desc')->orderBy('week_number', 'desc');
         }
         
         $reports = $query->get();
         $teamMembers = TeamMember::all();
         
         return Inertia::render('WeeklyReports/Index', [
             'reports' => $reports,
             'teamMembers' => $teamMembers,
             'filters' => $request->only(['week', 'member', 'sort']),
         ]);
     }
     ```
   - Index.jsx에서 필터 UI 추가:
     ```jsx
     const [filters, setFilters] = useState({
         week: filters?.week || '',
         member: filters?.member || '',
         sort: filters?.sort || '',
     });

     const applyFilters = () => {
         router.get(route('weekly-reports.index'), filters);
     };

     // 필터 UI (드롭다운과 버튼)
     <div className="flex gap-4 mb-4">
         <select value={filters.week} onChange={(e) => setFilters({...filters, week: e.target.value})} className="border rounded px-2 py-1">
             <option value="">전체 주차</option>
             {/* 최근 8주 옵션 생성 */}
         </select>
         <select value={filters.member} onChange={(e) => setFilters({...filters, member: e.target.value})} className="border rounded px-2 py-1">
             <option value="">전체 팀원</option>
             {teamMembers.map(m => <option key={m.id} value={m.id}>{m.name}</option>)}
         </select>
         <button onClick={() => setFilters({...filters, sort: 'desc'})} className="bg-gray-200 px-3 py-1 rounded">완료율 높은 순</button>
         <button onClick={() => setFilters({...filters, sort: 'asc'})} className="bg-gray-200 px-3 py-1 rounded">완료율 낮은 순</button>
         <button onClick={applyFilters} className="bg-blue-500 text-white px-4 py-1 rounded">필터 적용</button>
     </div>
     ```

3. **반응형 디자인 최종 점검**:
   - 모바일에서 테이블이 스크롤 가능하도록 `overflow-x-auto` 추가
   - 작은 화면에서 필터 UI가 세로로 배치되도록 조정
   - Tailwind의 반응형 유틸리티 클래스 활용 (sm:, md:, lg:)

4. **최종 테스트**:
   - 리포트 작성, 수정, 삭제 기능 확인
   - 필터링 및 정렬 기능 확인
   - 대시보드 차트 및 통계 확인
   - 모바일/데스크톱 반응형 확인

완료되면 "프로젝트 완성! 모든 기능이 정상 작동합니다" 메시지를 출력하고, README.md 파일에 프로젝트 설명과 실행 방법을 작성해주세요.

기대 결과: 주간 리포트 수정 페이지가 작동하고, 목록 페이지에서 주차/팀원 필터 및 완료율 정렬이 가능함. 모든 CRUD 기능과 대시보드가 정상 작동하며, 반응형 디자인이 적용됨. README.md에 프로젝트 설명이 포함됨.

제출된 작품 (0)

로그인 후 제출

아직 제출된 작품이 없습니다. 첫 번째 도전자가 되어보세요!

댓글 (0)

로그인 후 댓글을 남길 수 있습니다.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!