팀 위클리 스프린트 리포트 - 주간 성과 & 목표 달성률 시각화
team-weekly-sprint-report
문제 설명
프로젝트 개요
애자일 팀의 주간 스프린트 성과를 기록하고 시각화하는 풀스택 대시보드를 만듭니다. 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에게 보내면 됩니다. 복사 버튼을 눌러 바로 사용하세요.
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 기본 홈 화면이 표시됨.
주간 리포트를 저장할 데이터베이스 테이블을 설계합니다.
다음 작업을 수행해주세요:
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 모델 관계가 설정됨. 더미 팀원 데이터가 시딩되어 있으면 더욱 좋음.
주간 리포트 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 컴포넌트는 없지만 라우트는 작동 가능한 상태.
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)가 작동함. 팀원 선택, 작업 내용 입력, 완료율 설정 후 저장 가능. 목록에서 완료율에 따라 색상이 다르게 표시됨.
팀 대시보드 페이지를 생성하여 주간 리포트 통계와 완료율 추이를 시각화합니다.
다음 작업을 수행해주세요:
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주 완료율 추이가 막대 그래프로 시각화됨. 이번 주 팀원별 리포트와 블로커가 목록으로 표시됨. 요약 카드에 팀원 수, 제출률, 평균 완료율이 표시됨.
주간 리포트 수정 기능과 필터링/정렬 기능을 추가하여 프로젝트를 완성합니다.
다음 작업을 수행해주세요:
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)
로그인 후 제출아직 제출된 작품이 없습니다. 첫 번째 도전자가 되어보세요!