- 개발 단계별 문서 추가 (00_OVERVIEW ~ 06_PHASE) - 기술 표준 문서 추가 (99_TECHNICAL_STANDARDS) - 개발 프로세스 및 패턴 문서 추가 - API_FLOW_TESTER_DESIGN, DEV_PROCESS - HTMX_API_PATTERN, LAYOUT_PATTERN - SETUP_GUIDE, MNG_PROJECT_PLAN - 프로젝트 관리 문서 추가 (project-management/) - INDEX.md, MNG_CRITICAL_RULES.md 업데이트
838 lines
27 KiB
Markdown
838 lines
27 KiB
Markdown
# MNG 프로젝트 개발 계획서
|
|
|
|
## 📋 프로젝트 개요
|
|
|
|
### 목적
|
|
- **문제점**: 기존 admin/ (Filament v4)은 AI 없이 수정이 어려움
|
|
- **목표**: 수정 용이한 Plain Laravel 기반 관리자 패널 구축
|
|
- **도메인**: mng.sam.kr
|
|
- **철학**: **단순함 > 복잡함**, AI 없이도 수정 가능한 직관적 코드
|
|
|
|
### 핵심 전략
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ MNG (mng.sam.kr) │
|
|
│ ┌──────────────┐ ┌─────────────────┐ │
|
|
│ │ Web Routes │────▶│ Blade + HTMX │ │ ← DaisyUI (심플)
|
|
│ │ (세션 인증) │ │ (수정 용이) │ │
|
|
│ └──────────────┘ └─────────────────┘ │
|
|
│ ┌──────────────┐ ┌─────────────────┐ │
|
|
│ │ API Routes │────▶│ Admin API │ │ ← 처음부터 분리
|
|
│ │ (토큰 인증) │ │ (관리자 전용) │ │
|
|
│ └──────────────┘ └─────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌──────────────────────────────────────┐ │
|
|
│ │ Service Layer (비즈니스 로직) │ │ ← admin/ 복사
|
|
│ └──────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌──────────────────────────────────────┐ │
|
|
│ │ Models (admin/ 복사, Filament 제거) │ │
|
|
│ └──────────────────────────────────────┘ │
|
|
└─────────────────┬───────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────┐
|
|
│ MySQL 8.0 (공유 DB) │
|
|
│ - admin/ (점차 deprecated) │
|
|
│ - api/ (외부 API) │
|
|
│ - mng/ (새 관리자) ← 최종 │
|
|
└─────────────────────────────┘
|
|
```
|
|
|
|
### 설계 원칙
|
|
1. **단순성**: 복잡한 추상화 금지, 인라인 코드 허용
|
|
2. **수정 용이성**: AI 없이도 Blade 템플릿 수정 가능
|
|
3. **코드 재사용**: admin/ 모델/서비스 복사 후 간소화
|
|
4. **DB 공유**: 기존 테이블 최대한 활용
|
|
|
|
---
|
|
|
|
## 🏗️ 아키텍처 설계
|
|
|
|
### 1. 디렉토리 구조
|
|
```
|
|
SAM/
|
|
├── admin/ # Filament (점차 deprecated)
|
|
├── api/ # 외부 클라이언트 API
|
|
├── mng/ # ⭐ 운영 관리자 패널 (NEW)
|
|
│ ├── app/
|
|
│ │ ├── Http/
|
|
│ │ │ ├── Controllers/
|
|
│ │ │ │ ├── Web/ # Blade 컨트롤러 (단순)
|
|
│ │ │ │ │ ├── Auth/
|
|
│ │ │ │ │ ├── Dashboard/
|
|
│ │ │ │ │ ├── User/
|
|
│ │ │ │ │ └── Product/
|
|
│ │ │ │ └── Api/ # Admin API (향후)
|
|
│ │ │ │ └── Admin/
|
|
│ │ │ ├── Requests/ # FormRequest (필수)
|
|
│ │ │ └── Middleware/
|
|
│ │ ├── Services/ # admin/ 복사 후 간소화
|
|
│ │ ├── Models/ # admin/ 복사, Filament 코드 제거
|
|
│ │ └── Traits/
|
|
│ │ ├── BelongsToTenant.php # admin/에서 복사
|
|
│ │ └── HasAuditLog.php # admin/에서 복사
|
|
│ ├── routes/
|
|
│ │ ├── web.php # Blade 라우트
|
|
│ │ └── api.php # Admin API (/api/admin/*)
|
|
│ ├── resources/
|
|
│ │ └── views/
|
|
│ │ ├── layouts/
|
|
│ │ │ ├── app.blade.php # 단순 레이아웃
|
|
│ │ │ └── guest.blade.php
|
|
│ │ ├── auth/ # 로그인 화면
|
|
│ │ ├── dashboard/ # 대시보드
|
|
│ │ ├── users/ # 사용자 관리
|
|
│ │ └── products/ # 제품 관리
|
|
│ ├── database/
|
|
│ │ └── migrations/
|
|
│ │ └── # 관리자 전용: admin_*
|
|
│ │ └── # 통계 전용: stat_*
|
|
│ ├── tests/
|
|
│ │ └── Feature/
|
|
│ └── .env
|
|
├── docker/
|
|
│ └── nginx/
|
|
│ └── mng.sam.kr.conf
|
|
└── claudedocs/
|
|
└── mng/
|
|
├── MNG_PROJECT_PLAN.md # 이 문서
|
|
├── API_SPEC.md # API 명세
|
|
└── PROGRESS.md # 진행 상황
|
|
```
|
|
|
|
### 2. 기술 스택 (확정)
|
|
|
|
| 레이어 | 기술 | 버전 | 비고 |
|
|
|--------|------|------|------|
|
|
| **백엔드** | Laravel | 12.x | PHP 8.4+ |
|
|
| **인증** | Sanctum | 4.x | 세션 + 토큰 |
|
|
| **DB** | **MySQL** | **8.0** | **admin, api와 공유** |
|
|
| **프론트엔드** | **Blade + HTMX** | **1.x** | **단순, 수정 용이** |
|
|
| **CSS** | **Tailwind CSS** | **3.x** | 기존과 통일 |
|
|
| **UI 컴포넌트** | **DaisyUI** | **4.x** | **심플, 클래스 기반** |
|
|
| **아이콘** | Heroicons | - | Tailwind 친화적 |
|
|
| **문서화** | L5-Swagger | - | Admin API 전용 |
|
|
| **테스트** | PHPUnit | - | Feature Test |
|
|
|
|
### 3. DB 테이블 명명 규칙 (변경)
|
|
|
|
#### 기존 테이블 재사용 (마이그레이션 없음)
|
|
```
|
|
✅ users, roles, departments
|
|
✅ products, materials, bom_items
|
|
✅ menus, menu_role
|
|
✅ audit_logs, categories, files
|
|
✅ tenants
|
|
```
|
|
|
|
#### 관리자 전용 테이블 (admin_* 접두사)
|
|
```php
|
|
// database/migrations/2025_01_20_create_admin_settings_table.php
|
|
Schema::create('admin_settings', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('key')->unique();
|
|
$table->text('value')->nullable();
|
|
$table->string('type')->default('string'); // string, json, boolean
|
|
$table->timestamps();
|
|
});
|
|
|
|
// 예시 테이블
|
|
- admin_settings # 관리자 설정
|
|
- admin_logs # 관리자 작업 로그
|
|
- admin_preferences # 관리자 개인 설정
|
|
```
|
|
|
|
#### 통계 테이블 (stat_* 접두사)
|
|
```php
|
|
// database/migrations/2025_01_20_create_stat_daily_sales_table.php
|
|
Schema::create('stat_daily_sales', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->date('date');
|
|
$table->decimal('total_amount', 15, 2);
|
|
$table->integer('order_count');
|
|
$table->timestamps();
|
|
|
|
$table->unique('date');
|
|
});
|
|
|
|
// 예시 테이블
|
|
- stat_daily_sales # 일별 매출 통계
|
|
- stat_inventory # 재고 통계
|
|
- stat_user_activity # 사용자 활동 통계
|
|
```
|
|
|
|
### 4. 모델/서비스 복사 전략
|
|
|
|
#### admin/ → mng/ 복사 프로세스
|
|
```bash
|
|
# 1. 모델 복사 (Filament 의존성 제거)
|
|
cp -r admin/app/Models/* mng/app/Models/
|
|
# Filament 관련 코드 제거 (getNavigationLabel, form, table 등)
|
|
|
|
# 2. Traits 복사 (그대로 사용)
|
|
cp admin/app/Traits/BelongsToTenant.php mng/app/Traits/
|
|
cp admin/app/Traits/HasAuditLog.php mng/app/Traits/
|
|
|
|
# 3. Services 복사 (있다면)
|
|
cp -r admin/app/Services/* mng/app/Services/
|
|
# 또는 신규 작성 (Service-First 원칙)
|
|
```
|
|
|
|
#### 모델 예시 (Filament 제거)
|
|
```php
|
|
// admin/app/Models/User.php (Before)
|
|
class User extends Authenticatable implements FilamentUser
|
|
{
|
|
use BelongsToTenant, HasAuditLog;
|
|
|
|
public static function form(Form $form): Form { ... } // ❌ 제거
|
|
public static function table(Table $table): Table { ... } // ❌ 제거
|
|
public function canAccessPanel(Panel $panel): bool { ... } // ❌ 제거
|
|
}
|
|
|
|
// mng/app/Models/User.php (After)
|
|
class User extends Authenticatable
|
|
{
|
|
use BelongsToTenant, HasAuditLog;
|
|
|
|
protected $fillable = [
|
|
'tenant_id', 'email', 'password', 'name',
|
|
'role_id', 'department_id', 'is_active',
|
|
];
|
|
|
|
// 순수 Eloquent 관계만 유지
|
|
public function role() { return $this->belongsTo(Role::class); }
|
|
public function department() { return $this->belongsTo(Department::class); }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 UI 설계 원칙 (수정 용이성 최우선)
|
|
|
|
### DaisyUI 사용 철학
|
|
```blade
|
|
{{-- ✅ GOOD: 단순하고 직관적 --}}
|
|
<button class="btn btn-primary">저장</button>
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title">제목</h2>
|
|
<p>내용</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ❌ BAD: 과도한 추상화 --}}
|
|
<x-custom-button variant="primary" size="large" />
|
|
<x-card-wrapper :config="$complexConfig" />
|
|
```
|
|
|
|
### Blade 템플릿 구조 (2레벨 최대)
|
|
```blade
|
|
{{-- layouts/app.blade.php (레이아웃) --}}
|
|
<!DOCTYPE html>
|
|
<html data-theme="light">
|
|
<head>
|
|
<title>{{ config('app.name') }}</title>
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
</head>
|
|
<body>
|
|
<div class="drawer lg:drawer-open">
|
|
{{-- 사이드바 --}}
|
|
<input id="drawer" type="checkbox" class="drawer-toggle" />
|
|
<div class="drawer-side">
|
|
<label for="drawer" class="drawer-overlay"></label>
|
|
<ul class="menu p-4 w-64 bg-base-200">
|
|
@foreach($menus as $menu)
|
|
<li><a href="{{ $menu->url }}">{{ __($menu->name) }}</a></li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
|
|
{{-- 메인 컨텐츠 --}}
|
|
<div class="drawer-content">
|
|
<div class="navbar bg-base-100">
|
|
<div class="flex-1">
|
|
<a class="btn btn-ghost normal-case text-xl">MNG</a>
|
|
</div>
|
|
<div class="flex-none">
|
|
<div class="dropdown dropdown-end">
|
|
<label tabindex="0" class="btn btn-ghost">
|
|
{{ auth()->user()->name }}
|
|
</label>
|
|
<ul class="menu dropdown-content">
|
|
<li><a href="/logout">로그아웃</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<main class="p-6">
|
|
@yield('content')
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
|
|
{{-- users/index.blade.php (페이지) --}}
|
|
@extends('layouts.app')
|
|
|
|
@section('content')
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title">사용자 목록</h2>
|
|
|
|
{{-- Alpine.js 최소 사용 --}}
|
|
<div x-data="{ search: '' }">
|
|
<input x-model="search" type="text"
|
|
placeholder="검색..."
|
|
class="input input-bordered w-full" />
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="table w-full">
|
|
<thead>
|
|
<tr>
|
|
<th>이름</th>
|
|
<th>이메일</th>
|
|
<th>역할</th>
|
|
<th>작업</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($users as $user)
|
|
<tr>
|
|
<td>{{ $user->name }}</td>
|
|
<td>{{ $user->email }}</td>
|
|
<td>{{ $user->role->name }}</td>
|
|
<td>
|
|
<a href="/users/{{ $user->id }}/edit" class="btn btn-sm">수정</a>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{ $users->links() }} {{-- Pagination --}}
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
```
|
|
|
|
### Alpine.js 사용 원칙 (최소화)
|
|
```blade
|
|
{{-- ✅ GOOD: 단순 인터랙션 --}}
|
|
<div x-data="{ open: false }">
|
|
<button @click="open = !open" class="btn">메뉴 열기</button>
|
|
<div x-show="open" class="dropdown-content">메뉴 내용</div>
|
|
</div>
|
|
|
|
{{-- ❌ BAD: 복잡한 로직 (서버에서 처리) --}}
|
|
<div x-data="complexDataFetching()">
|
|
<div x-init="loadData()">...</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 개발 로드맵
|
|
|
|
### Phase 1: 인프라 구축 (1일)
|
|
|
|
#### 체크리스트
|
|
- [ ] Laravel 12 프로젝트 생성 (`mng/`)
|
|
```bash
|
|
cd SAM
|
|
composer create-project laravel/laravel mng
|
|
cd mng
|
|
```
|
|
- [ ] `.env` 환경 변수 설정
|
|
```env
|
|
APP_NAME=MNG
|
|
APP_URL=http://mng.sam.kr
|
|
DB_CONNECTION=pgsql
|
|
DB_HOST=postgres
|
|
DB_PORT=5432
|
|
DB_DATABASE=sam_db
|
|
DB_USERNAME=sam_user
|
|
DB_PASSWORD=sam_password
|
|
```
|
|
- [ ] Composer 패키지 설치
|
|
```bash
|
|
composer require laravel/sanctum
|
|
composer require darkaonline/l5-swagger
|
|
composer require --dev laravel/pint
|
|
```
|
|
- [ ] Tailwind + DaisyUI + HTMX 설정
|
|
```bash
|
|
npm install -D tailwindcss daisyui @tailwindcss/forms
|
|
npm install htmx.org
|
|
```
|
|
```js
|
|
// tailwind.config.js
|
|
module.exports = {
|
|
plugins: [require('daisyui')],
|
|
daisyui: {
|
|
themes: ['light', 'dark'],
|
|
},
|
|
}
|
|
```
|
|
- [ ] Docker Nginx 설정 (mng.sam.kr)
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name mng.sam.kr;
|
|
root /var/www/mng/public;
|
|
index index.php;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.php?$query_string;
|
|
}
|
|
|
|
location ~ \.php$ {
|
|
fastcgi_pass mng:9000;
|
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
|
include fastcgi_params;
|
|
}
|
|
}
|
|
```
|
|
- [ ] admin/ 모델 복사
|
|
```bash
|
|
cp -r admin/app/Models/* mng/app/Models/
|
|
cp -r admin/app/Traits/* mng/app/Traits/
|
|
# Filament 관련 코드 제거 후 커밋
|
|
```
|
|
|
|
#### 산출물
|
|
- `mng/` 디렉토리 (Git 독립 저장소)
|
|
- DaisyUI + Alpine.js 환경
|
|
- 복사된 모델 (Filament 제거)
|
|
|
|
---
|
|
|
|
### Phase 2: 인증 시스템 (2일)
|
|
|
|
#### 로그인 화면 (DaisyUI)
|
|
```blade
|
|
{{-- resources/views/auth/login.blade.php --}}
|
|
<!DOCTYPE html>
|
|
<html data-theme="light">
|
|
<head>
|
|
<title>로그인 - MNG</title>
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
</head>
|
|
<body class="bg-base-200">
|
|
<div class="hero min-h-screen">
|
|
<div class="hero-content flex-col">
|
|
<div class="card w-96 bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title justify-center mb-4">MNG 로그인</h2>
|
|
|
|
<form method="POST" action="/login">
|
|
@csrf
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">이메일</span>
|
|
</label>
|
|
<input type="email" name="email"
|
|
placeholder="email@example.com"
|
|
class="input input-bordered"
|
|
required autofocus />
|
|
</div>
|
|
|
|
<div class="form-control mt-4">
|
|
<label class="label">
|
|
<span class="label-text">비밀번호</span>
|
|
</label>
|
|
<input type="password" name="password"
|
|
class="input input-bordered"
|
|
required />
|
|
</div>
|
|
|
|
@if ($errors->any())
|
|
<div class="alert alert-error mt-4">
|
|
{{ $errors->first() }}
|
|
</div>
|
|
@endif
|
|
|
|
<div class="form-control mt-6">
|
|
<button class="btn btn-primary">로그인</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
#### AuthService (admin/ 참고)
|
|
```php
|
|
// app/Services/AuthService.php
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Hash;
|
|
|
|
class AuthService
|
|
{
|
|
public function login(array $credentials): bool
|
|
{
|
|
return Auth::attempt($credentials);
|
|
}
|
|
|
|
public function logout(): void
|
|
{
|
|
Auth::logout();
|
|
}
|
|
|
|
public function createToken(array $credentials): ?string
|
|
{
|
|
$user = User::where('email', $credentials['email'])->first();
|
|
|
|
if (!$user || !Hash::check($credentials['password'], $user->password)) {
|
|
return null;
|
|
}
|
|
|
|
return $user->createToken('mng-token')->plainTextToken;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 체크리스트
|
|
- [ ] LoginRequest (FormRequest)
|
|
- [ ] AuthService 작성
|
|
- [ ] Web 로그인 구현 (세션)
|
|
- [ ] API 로그인 구현 (토큰)
|
|
- [ ] BelongsToTenant 적용 확인
|
|
- [ ] Feature Test 작성
|
|
|
|
---
|
|
|
|
### Phase 3: 대시보드 (1-2일)
|
|
|
|
#### DaisyUI Drawer 레이아웃
|
|
```blade
|
|
{{-- resources/views/layouts/app.blade.php --}}
|
|
<!DOCTYPE html>
|
|
<html data-theme="light">
|
|
<head>
|
|
<title>{{ config('app.name') }}</title>
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
</head>
|
|
<body>
|
|
<div class="drawer lg:drawer-open">
|
|
<input id="drawer" type="checkbox" class="drawer-toggle" />
|
|
|
|
{{-- 메인 컨텐츠 --}}
|
|
<div class="drawer-content flex flex-col">
|
|
{{-- 네비게이션 바 --}}
|
|
<div class="w-full navbar bg-base-300">
|
|
<div class="flex-none lg:hidden">
|
|
<label for="drawer" class="btn btn-square btn-ghost">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-6 h-6 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
|
</label>
|
|
</div>
|
|
<div class="flex-1 px-2 mx-2">MNG</div>
|
|
<div class="flex-none">
|
|
<div class="dropdown dropdown-end">
|
|
<label tabindex="0" class="btn btn-ghost">
|
|
{{ auth()->user()->name }}
|
|
</label>
|
|
<ul tabindex="0" class="menu menu-compact dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-52">
|
|
<li><a href="/profile">프로필</a></li>
|
|
<li>
|
|
<form method="POST" action="/logout">
|
|
@csrf
|
|
<button type="submit">로그아웃</button>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 페이지 컨텐츠 --}}
|
|
<main class="p-6 flex-1">
|
|
@yield('content')
|
|
</main>
|
|
</div>
|
|
|
|
{{-- 사이드바 --}}
|
|
<div class="drawer-side">
|
|
<label for="drawer" class="drawer-overlay"></label>
|
|
<ul class="menu p-4 w-64 bg-base-200 text-base-content">
|
|
@foreach($menus as $menu)
|
|
@if($menu->children->isEmpty())
|
|
<li>
|
|
<a href="{{ $menu->url }}"
|
|
class="{{ request()->is($menu->url) ? 'active' : '' }}">
|
|
{{ __($menu->name) }}
|
|
</a>
|
|
</li>
|
|
@else
|
|
<li>
|
|
<details>
|
|
<summary>{{ __($menu->name) }}</summary>
|
|
<ul>
|
|
@foreach($menu->children as $child)
|
|
<li><a href="{{ $child->url }}">{{ __($child->name) }}</a></li>
|
|
@endforeach
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
@endif
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
#### 대시보드 컨트롤러
|
|
```php
|
|
// app/Http/Controllers/Web/DashboardController.php
|
|
namespace App\Http\Controllers\Web;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Services\MenuService;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function __construct(
|
|
private MenuService $menuService
|
|
) {}
|
|
|
|
public function index()
|
|
{
|
|
$menus = $this->menuService->getMenusForUser(auth()->user());
|
|
|
|
return view('dashboard.index', compact('menus'));
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 체크리스트
|
|
- [ ] 레이아웃 템플릿 (DaisyUI Drawer)
|
|
- [ ] 메뉴 서비스 (MenuService)
|
|
- [ ] 역할별 메뉴 필터링
|
|
- [ ] 대시보드 메인 페이지
|
|
|
|
---
|
|
|
|
### Phase 4: 핵심 기능 (주 단위)
|
|
|
|
#### 4.1 사용자 관리 (3-5일)
|
|
```blade
|
|
{{-- resources/views/users/index.blade.php --}}
|
|
@extends('layouts.app')
|
|
|
|
@section('content')
|
|
<div class="space-y-4">
|
|
{{-- 헤더 --}}
|
|
<div class="flex justify-between items-center">
|
|
<h1 class="text-2xl font-bold">사용자 관리</h1>
|
|
<a href="/users/create" class="btn btn-primary">사용자 추가</a>
|
|
</div>
|
|
|
|
{{-- 검색/필터 --}}
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<form method="GET" action="/users">
|
|
<div class="grid grid-cols-3 gap-4">
|
|
<div class="form-control">
|
|
<input type="text" name="search"
|
|
placeholder="이름 또는 이메일"
|
|
class="input input-bordered"
|
|
value="{{ request('search') }}" />
|
|
</div>
|
|
<div class="form-control">
|
|
<select name="role_id" class="select select-bordered">
|
|
<option value="">전체 역할</option>
|
|
@foreach($roles as $role)
|
|
<option value="{{ $role->id }}"
|
|
{{ request('role_id') == $role->id ? 'selected' : '' }}>
|
|
{{ $role->name }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div class="form-control">
|
|
<button type="submit" class="btn btn-primary">검색</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 테이블 --}}
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<div class="overflow-x-auto">
|
|
<table class="table w-full">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>이름</th>
|
|
<th>이메일</th>
|
|
<th>역할</th>
|
|
<th>부서</th>
|
|
<th>상태</th>
|
|
<th>작업</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($users as $user)
|
|
<tr>
|
|
<td>{{ $user->id }}</td>
|
|
<td>{{ $user->name }}</td>
|
|
<td>{{ $user->email }}</td>
|
|
<td>{{ $user->role->name }}</td>
|
|
<td>{{ $user->department->name }}</td>
|
|
<td>
|
|
<span class="badge {{ $user->is_active ? 'badge-success' : 'badge-error' }}">
|
|
{{ $user->is_active ? '활성' : '비활성' }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="/users/{{ $user->id }}/edit" class="btn btn-sm">수정</a>
|
|
<button class="btn btn-sm btn-error"
|
|
onclick="confirmDelete({{ $user->id }})">삭제</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{ $users->links() }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function confirmDelete(userId) {
|
|
if (confirm('정말 삭제하시겠습니까?')) {
|
|
document.getElementById('delete-form-' + userId).submit();
|
|
}
|
|
}
|
|
</script>
|
|
@endsection
|
|
```
|
|
|
|
#### 체크리스트
|
|
- [ ] 사용자 목록 (검색, 필터, 페이징)
|
|
- [ ] 사용자 생성 (FormRequest)
|
|
- [ ] 사용자 수정
|
|
- [ ] 사용자 삭제 (Soft Delete)
|
|
- [ ] Feature Test
|
|
|
|
---
|
|
|
|
## 📊 데이터베이스 전략
|
|
|
|
### DB 테이블 전략 (최종)
|
|
```
|
|
✅ 기존 테이블 재사용 (마이그레이션 없음)
|
|
- users, roles, departments
|
|
- products, materials
|
|
- menus, audit_logs
|
|
|
|
🆕 관리자 전용 (admin_*)
|
|
- admin_settings
|
|
- admin_logs
|
|
- admin_preferences
|
|
|
|
📊 통계 (stat_*)
|
|
- stat_daily_sales
|
|
- stat_inventory
|
|
- stat_user_activity
|
|
```
|
|
|
|
### 모델 관리 전략
|
|
```
|
|
초기 복사: admin/app/Models → mng/app/Models
|
|
Filament 제거: form(), table(), canAccessPanel() 등
|
|
이후 운영: mng/ 독립 (admin 점차 deprecated)
|
|
```
|
|
|
|
---
|
|
|
|
## 🛡️ 품질 관리
|
|
|
|
### 코드 품질 체크리스트
|
|
```
|
|
□ Service-First (비즈니스 로직 → Service)
|
|
□ FormRequest (컨트롤러 검증 금지)
|
|
□ BelongsToTenant (multi-tenant 스코프)
|
|
□ i18n 키 (하드코딩 금지)
|
|
□ Soft Delete (deleted_at)
|
|
□ 감사 로그 (HasAuditLog trait)
|
|
□ Feature Test
|
|
□ Pint (코드 스타일)
|
|
```
|
|
|
|
### UI 수정 용이성 체크리스트
|
|
```
|
|
□ DaisyUI 클래스 직접 사용 (추상화 최소)
|
|
□ Alpine.js 단순 인터랙션만
|
|
□ Blade 템플릿 2레벨 이하
|
|
□ 인라인 Tailwind 허용
|
|
□ AI 없이 수정 가능
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 예상 타임라인
|
|
|
|
### MVP (최소 기능 제품) - 2주
|
|
```
|
|
Day 1-2: Phase 1 (인프라) + admin/ 모델 복사
|
|
Day 3-4: Phase 2 (인증)
|
|
Day 5-6: Phase 3 (대시보드)
|
|
Day 7-14: Phase 4 (사용자, 역할, 제품 관리)
|
|
```
|
|
|
|
### 전체 기능 이식 - 4-6주
|
|
```
|
|
Week 3-4: 제품, 자재 관리
|
|
Week 5: 게시판, 통계
|
|
Week 6: 테스트, 최적화
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 참고 문서
|
|
|
|
- [DaisyUI Components](https://daisyui.com/components/)
|
|
- [Alpine.js Documentation](https://alpinejs.dev/)
|
|
- [Laravel 12 Blade](https://laravel.com/docs/12.x/blade)
|
|
|
|
---
|
|
|
|
## ✅ 다음 단계
|
|
|
|
### 즉시 시작 가능
|
|
- [ ] `mng/` Laravel 프로젝트 생성
|
|
- [ ] DaisyUI + Alpine.js 설치
|
|
- [ ] admin/ 모델 복사 및 Filament 제거
|
|
- [ ] 로그인 화면 구현
|
|
|
|
---
|
|
|
|
**작성일**: 2025-01-20
|
|
**버전**: 2.0
|
|
**상태**: 정책 반영 완료 ✅
|
|
**변경사항**:
|
|
- 폴더명: `adm2/` → `mng/`
|
|
- UI: DaisyUI + Blade + Alpine.js 확정
|
|
- DB: 기존 테이블 재사용, `admin_*`, `stat_*` 접두사
|
|
- 모델: admin/ 복사 후 Filament 제거
|
|
- 철학: 단순함, 수정 용이성 최우선 |