Laravel 고객 견적 요청 폼 - 자동 이메일 발송 - 바이브코딩 레시피
입문 PHP Laravel AI: Claude

Laravel 고객 견적 요청 폼 - 자동 이메일 발송

리포지토리명 quote-request-form-laravel
조회수 64 0건 제출

문제 설명

프로젝트 개요

소규모 비즈니스(인테리어, 웹 개발, 디자인 등)를 운영하는 창업자를 위한 고객 견적 요청 폼을 만듭니다. 고객이 웹사이트에서 서비스 종류, 예산, 연락처 등을 입력하면 자동으로 관리자 이메일로 전송되고, 고객에게는 감사 메시지가 표시됩니다.

Laravel 13.x + Blade 템플릿 + Tailwind CSS로 구성하며, SQLite 데이터베이스에 견적 요청 내역을 저장하고, Laravel의 Mail 기능으로 이메일을 발송합니다. 프로그래밍 초보자도 AI 도구와 함께라면 충분히 완성할 수 있는 실용적인 프로젝트입니다.

필수 기능

  1. 견적 요청 폼 페이지

    • 고객 이름, 이메일, 전화번호 입력
    • 서비스 종류 선택 (드롭다운: 웹 개발, 앱 개발, 디자인, 마케팅 등)
    • 예산 범위 선택 (라디오 버튼: 100만원 미만, 100300만원, 300500만원, 500만원 이상)
    • 프로젝트 설명 (텍스트 영역)
    • 제출 버튼
  2. 데이터 저장 및 검증

    • Laravel Validation으로 필수 항목 검증 (이메일 형식, 전화번호 형식 등)
    • SQLite 데이터베이스에 견적 요청 저장 (quotes 테이블)
    • 제출 시간 자동 기록
  3. 자동 이메일 발송

    • 관리자 이메일로 견적 요청 내용 발송 (Laravel Mail + Mailtrap/SMTP)
    • 이메일 본문에 고객 정보, 서비스 종류, 예산, 프로젝트 설명 포함
  4. 감사 페이지

    • 제출 완료 후 "견적 요청이 접수되었습니다. 빠른 시일 내에 연락드리겠습니다." 메시지 표시
    • 홈으로 돌아가기 버튼
  5. 견적 요청 목록 페이지 (관리자용)

    • 모든 견적 요청을 테이블 형태로 표시 (이름, 이메일, 서비스 종류, 예산, 날짜)
    • 최신 순으로 정렬
    • 각 항목 클릭 시 상세 내용 모달 팝업

보너스 기능 (선택)

  • 견적 요청 폼에 파일 첨부 기능 (참고 자료 업로드)
  • 견적 요청 상태 관리 (대기 중, 검토 중, 완료)
  • 고객에게도 자동 확인 이메일 발송
  • 다크 모드 지원
  • 견적 요청 통계 (서비스 종류별, 예산 범위별 차트)

기술 스택

  • 백엔드: Laravel 13.x
  • 프론트엔드: Blade 템플릿, Tailwind CSS
  • 데이터베이스: SQLite
  • 이메일: Laravel Mail (Mailtrap 또는 Gmail SMTP)

학습 목표

  • Laravel 라우팅, 컨트롤러, 모델 기본 구조 이해
  • Blade 템플릿 문법과 컴포넌트 사용법 익히기
  • Laravel Validation으로 폼 데이터 검증하기
  • Laravel Mail로 이메일 발송 기능 구현하기
  • Tailwind CSS로 반응형 UI 디자인하기
  • SQLite 데이터베이스와 마이그레이션 활용하기

제약 조건

  • Laravel 13.x 버전 사용 필수
  • Blade 템플릿 엔진 사용 (React/Vue 등 SPA 프레임워크 금지)
  • Tailwind CSS로 스타일링 (CDN 또는 npm 설치)
  • SQLite 데이터베이스 사용 (.env 파일에서 DB_CONNECTION=sqlite 설정)
  • Laravel Mail 기능 사용 (Mailtrap 또는 Gmail SMTP 설정)
  • 견적 요청 폼은 POST 메서드로 제출
  • CSRF 토큰 필수 (@csrf 디렉티브 사용)
  • 반응형 디자인 (모바일, 태블릿, 데스크탑 대응)
  • Laravel Validation 규칙 사용 (required, email, max, min 등)
  • 성공 메시지는 세션 플래시 메시지 또는 리다이렉트로 표시

프롬프트 레시피

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

1
Step 1
mkdir quote-request-form-laravel && cd quote-request-form-laravel

이 폴더에서 Laravel 13.x 프로젝트를 생성하고 기본 설정을 완료합니다.

1. Laravel 13.x 프로젝트 생성:
   ```bash
   composer create-project laravel/laravel . "^13.0"
   ```

2. .env 파일에서 데이터베이스를 SQLite로 설정:
   ```
   DB_CONNECTION=sqlite
   # DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD 주석 처리
   ```

3. database/database.sqlite 파일 생성:
   ```bash
   touch database/database.sqlite
   ```

4. Tailwind CSS 설치 및 설정:
   ```bash
   npm install -D tailwindcss postcss autoprefixer
   npx tailwindcss init -p
   ```

5. tailwind.config.js 파일 수정:
   ```javascript
   content: [
     "./resources/**/*.blade.php",
     "./resources/**/*.js",
   ],
   ```

6. resources/css/app.css 파일에 Tailwind 디렉티브 추가:
   ```css
   @tailwind base;
   @tailwind components;
   @tailwind utilities;
   ```

7. npm 빌드 실행:
   ```bash
   npm install && npm run build
   ```

8. 개발 서버 실행:
   ```bash
   php artisan serve
   ```

프로젝트가 http://localhost:8000 에서 실행되고, Laravel 기본 화면이 표시되면 성공입니다.

기대 결과: Laravel 13.x 프로젝트가 생성되고, SQLite 데이터베이스와 Tailwind CSS가 설정되었습니다. 개발 서버가 실행 중이며, 브라우저에서 Laravel 기본 화면을 확인할 수 있습니다.

2
Step 2
견적 요청 데이터를 저장할 데이터베이스 테이블과 모델을 생성합니다.

1. Quote 모델과 마이그레이션 파일 생성:
   ```bash
   php artisan make:model Quote -m
   ```

2. database/migrations/xxxx_create_quotes_table.php 파일을 열고 up() 메서드 수정:
   ```php
   public function up()
   {
       Schema::create('quotes', function (Blueprint $table) {
           $table->id();
           $table->string('name');
           $table->string('email');
           $table->string('phone');
           $table->string('service_type');
           $table->string('budget_range');
           $table->text('description');
           $table->timestamps();
       });
   }
   ```

3. 마이그레이션 실행:
   ```bash
   php artisan migrate
   ```

4. app/Models/Quote.php 파일을 열고 fillable 속성 추가:
   ```php
   protected $fillable = [
       'name',
       'email',
       'phone',
       'service_type',
       'budget_range',
       'description',
   ];
   ```

마이그레이션이 성공적으로 실행되고, database/database.sqlite 파일에 quotes 테이블이 생성되었는지 확인하세요.

기대 결과: Quote 모델과 quotes 테이블이 생성되었습니다. 테이블에는 name, email, phone, service_type, budget_range, description, timestamps 컬럼이 포함되어 있습니다.

3
Step 3
견적 요청 폼 페이지를 Blade 템플릿으로 만들고, 라우트와 컨트롤러를 설정합니다.

1. QuoteController 생성:
   ```bash
   php artisan make:controller QuoteController
   ```

2. app/Http/Controllers/QuoteController.php 파일을 열고 다음 메서드 추가:
   ```php
   public function create()
   {
       return view('quotes.create');
   }

   public function store(Request $request)
   {
       $validated = $request->validate([
           'name' => 'required|max:255',
           'email' => 'required|email|max:255',
           'phone' => 'required|max:20',
           'service_type' => 'required',
           'budget_range' => 'required',
           'description' => 'required|max:1000',
       ]);

       Quote::create($validated);

       return redirect()->route('quotes.thanks')->with('success', '견적 요청이 접수되었습니다.');
   }

   public function thanks()
   {
       return view('quotes.thanks');
   }
   ```

3. routes/web.php 파일에 라우트 추가:
   ```php
   use App\Http\Controllers\QuoteController;

   Route::get('/', [QuoteController::class, 'create'])->name('quotes.create');
   Route::post('/quotes', [QuoteController::class, 'store'])->name('quotes.store');
   Route::get('/thanks', [QuoteController::class, 'thanks'])->name('quotes.thanks');
   ```

4. resources/views/quotes 폴더 생성 후 create.blade.php 파일 생성:
   ```blade
   <!DOCTYPE html>
   <html lang="ko">
   <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>견적 요청</title>
       @vite('resources/css/app.css')
   </head>
   <body class="bg-gray-100">
       <div class="container mx-auto px-4 py-8 max-w-2xl">
           <h1 class="text-3xl font-bold text-center mb-8 text-gray-800">견적 요청하기</h1>
           
           <form action="{{ route('quotes.store') }}" method="POST" class="bg-white shadow-md rounded-lg p-6">
               @csrf
               
               <div class="mb-4">
                   <label class="block text-gray-700 font-semibold mb-2">이름 *</label>
                   <input type="text" name="name" value="{{ old('name') }}" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
                   @error('name')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
               </div>

               <div class="mb-4">
                   <label class="block text-gray-700 font-semibold mb-2">이메일 *</label>
                   <input type="email" name="email" value="{{ old('email') }}" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
                   @error('email')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
               </div>

               <div class="mb-4">
                   <label class="block text-gray-700 font-semibold mb-2">전화번호 *</label>
                   <input type="text" name="phone" value="{{ old('phone') }}" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
                   @error('phone')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
               </div>

               <div class="mb-4">
                   <label class="block text-gray-700 font-semibold mb-2">서비스 종류 *</label>
                   <select name="service_type" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
                       <option value="">선택하세요</option>
                       <option value="웹 개발" {{ old('service_type') == '웹 개발' ? 'selected' : '' }}>웹 개발</option>
                       <option value="앱 개발" {{ old('service_type') == '앱 개발' ? 'selected' : '' }}>앱 개발</option>
                       <option value="디자인" {{ old('service_type') == '디자인' ? 'selected' : '' }}>디자인</option>
                       <option value="마케팅" {{ old('service_type') == '마케팅' ? 'selected' : '' }}>마케팅</option>
                       <option value="기타" {{ old('service_type') == '기타' ? 'selected' : '' }}>기타</option>
                   </select>
                   @error('service_type')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
               </div>

               <div class="mb-4">
                   <label class="block text-gray-700 font-semibold mb-2">예산 범위 *</label>
                   <div class="space-y-2">
                       <label class="flex items-center">
                           <input type="radio" name="budget_range" value="100만원 미만" {{ old('budget_range') == '100만원 미만' ? 'checked' : '' }} class="mr-2" required>
                           100만원 미만
                       </label>
                       <label class="flex items-center">
                           <input type="radio" name="budget_range" value="100~300만원" {{ old('budget_range') == '100~300만원' ? 'checked' : '' }} class="mr-2">
                           100~300만원
                       </label>
                       <label class="flex items-center">
                           <input type="radio" name="budget_range" value="300~500만원" {{ old('budget_range') == '300~500만원' ? 'checked' : '' }} class="mr-2">
                           300~500만원
                       </label>
                       <label class="flex items-center">
                           <input type="radio" name="budget_range" value="500만원 이상" {{ old('budget_range') == '500만원 이상' ? 'checked' : '' }} class="mr-2">
                           500만원 이상
                       </label>
                   </div>
                   @error('budget_range')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
               </div>

               <div class="mb-6">
                   <label class="block text-gray-700 font-semibold mb-2">프로젝트 설명 *</label>
                   <textarea name="description" rows="5" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>{{ old('description') }}</textarea>
                   @error('description')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
               </div>

               <button type="submit" class="w-full bg-blue-600 text-white font-semibold py-3 rounded-lg hover:bg-blue-700 transition duration-200">
                   견적 요청 제출하기
               </button>
           </form>
       </div>
   </body>
   </html>
   ```

5. resources/views/quotes/thanks.blade.php 파일 생성:
   ```blade
   <!DOCTYPE html>
   <html lang="ko">
   <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>감사합니다</title>
       @vite('resources/css/app.css')
   </head>
   <body class="bg-gray-100">
       <div class="container mx-auto px-4 py-16 max-w-2xl text-center">
           <div class="bg-white shadow-md rounded-lg p-8">
               <svg class="w-16 h-16 text-green-500 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                   <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
               </svg>
               <h1 class="text-3xl font-bold text-gray-800 mb-4">견적 요청이 접수되었습니다!</h1>
               <p class="text-gray-600 mb-6">빠른 시일 내에 연락드리겠습니다. 감사합니다.</p>
               <a href="{{ route('quotes.create') }}" class="inline-block bg-blue-600 text-white font-semibold px-6 py-3 rounded-lg hover:bg-blue-700 transition duration-200">
                   홈으로 돌아가기
               </a>
           </div>
       </div>
   </body>
   </html>
   ```

브라우저에서 http://localhost:8000 을 열어 견적 요청 폼이 표시되는지 확인하고, 폼을 제출하면 감사 페이지로 이동하는지 테스트하세요.

기대 결과: 견적 요청 폼 페이지가 Tailwind CSS로 스타일링되어 표시됩니다. 폼을 제출하면 데이터가 검증되고 데이터베이스에 저장된 후, 감사 페이지로 리다이렉트됩니다. 검증 오류가 있으면 에러 메시지가 표시됩니다.

4
Step 4
견적 요청이 제출되면 관리자에게 이메일을 자동으로 발송하는 기능을 추가합니다.

1. .env 파일에서 메일 설정 (Mailtrap 사용 예시):
   ```
   MAIL_MAILER=smtp
   MAIL_HOST=sandbox.smtp.mailtrap.io
   MAIL_PORT=2525
   MAIL_USERNAME=your_mailtrap_username
   MAIL_PASSWORD=your_mailtrap_password
   MAIL_ENCRYPTION=tls
   MAIL_FROM_ADDRESS="noreply@example.com"
   MAIL_FROM_NAME="${APP_NAME}"
   
   ADMIN_EMAIL=admin@example.com
   ```
   (Mailtrap 계정이 없다면 https://mailtrap.io 에서 무료 가입 후 SMTP 설정 정보를 가져오세요)

2. QuoteSubmitted Mailable 클래스 생성:
   ```bash
   php artisan make:mail QuoteSubmitted
   ```

3. app/Mail/QuoteSubmitted.php 파일을 열고 다음과 같이 수정:
   ```php
   <?php

   namespace App\Mail;

   use App\Models\Quote;
   use Illuminate\Bus\Queueable;
   use Illuminate\Mail\Mailable;
   use Illuminate\Mail\Mailables\Content;
   use Illuminate\Mail\Mailables\Envelope;
   use Illuminate\Queue\SerializesModels;

   class QuoteSubmitted extends Mailable
   {
       use Queueable, SerializesModels;

       public function __construct(public Quote $quote)
       {
       }

       public function envelope(): Envelope
       {
           return new Envelope(
               subject: '새로운 견적 요청이 도착했습니다',
           );
       }

       public function content(): Content
       {
           return new Content(
               view: 'emails.quote-submitted',
           );
       }
   }
   ```

4. resources/views/emails 폴더 생성 후 quote-submitted.blade.php 파일 생성:
   ```blade
   <!DOCTYPE html>
   <html lang="ko">
   <head>
       <meta charset="UTF-8">
       <style>
           body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
           .container { max-width: 600px; margin: 0 auto; padding: 20px; }
           .header { background-color: #2563eb; color: white; padding: 20px; text-align: center; }
           .content { background-color: #f9fafb; padding: 20px; }
           .field { margin-bottom: 15px; }
           .label { font-weight: bold; color: #4b5563; }
           .value { color: #1f2937; }
       </style>
   </head>
   <body>
       <div class="container">
           <div class="header">
               <h1>새로운 견적 요청</h1>
           </div>
           <div class="content">
               <p>새로운 견적 요청이 접수되었습니다.</p>
               
               <div class="field">
                   <span class="label">이름:</span>
                   <span class="value">{{ $quote->name }}</span>
               </div>
               
               <div class="field">
                   <span class="label">이메일:</span>
                   <span class="value">{{ $quote->email }}</span>
               </div>
               
               <div class="field">
                   <span class="label">전화번호:</span>
                   <span class="value">{{ $quote->phone }}</span>
               </div>
               
               <div class="field">
                   <span class="label">서비스 종류:</span>
                   <span class="value">{{ $quote->service_type }}</span>
               </div>
               
               <div class="field">
                   <span class="label">예산 범위:</span>
                   <span class="value">{{ $quote->budget_range }}</span>
               </div>
               
               <div class="field">
                   <span class="label">프로젝트 설명:</span>
                   <div class="value">{{ $quote->description }}</div>
               </div>
               
               <div class="field">
                   <span class="label">요청 시간:</span>
                   <span class="value">{{ $quote->created_at->format('Y-m-d H:i:s') }}</span>
               </div>
           </div>
       </div>
   </body>
   </html>
   ```

5. QuoteController의 store() 메서드를 수정하여 이메일 발송 추가:
   ```php
   use App\Mail\QuoteSubmitted;
   use Illuminate\Support\Facades\Mail;

   public function store(Request $request)
   {
       $validated = $request->validate([
           'name' => 'required|max:255',
           'email' => 'required|email|max:255',
           'phone' => 'required|max:20',
           'service_type' => 'required',
           'budget_range' => 'required',
           'description' => 'required|max:1000',
       ]);

       $quote = Quote::create($validated);

       // 관리자에게 이메일 발송
       Mail::to(env('ADMIN_EMAIL'))->send(new QuoteSubmitted($quote));

       return redirect()->route('quotes.thanks')->with('success', '견적 요청이 접수되었습니다.');
   }
   ```

견적 요청 폼을 다시 제출해보고, Mailtrap 받은편지함에서 이메일이 도착했는지 확인하세요.

기대 결과: 견적 요청이 제출되면 관리자 이메일(ADMIN_EMAIL)로 견적 요청 내용이 포함된 이메일이 자동으로 발송됩니다. Mailtrap에서 이메일을 확인할 수 있으며, 이메일에는 고객 정보, 서비스 종류, 예산 범위, 프로젝트 설명이 포함되어 있습니다.

5
Step 5
관리자가 모든 견적 요청을 확인할 수 있는 목록 페이지를 만듭니다.

1. QuoteController에 index() 메서드 추가:
   ```php
   public function index()
   {
       $quotes = Quote::latest()->get();
       return view('quotes.index', compact('quotes'));
   }
   ```

2. routes/web.php 파일에 라우트 추가:
   ```php
   Route::get('/admin/quotes', [QuoteController::class, 'index'])->name('quotes.index');
   ```

3. resources/views/quotes/index.blade.php 파일 생성:
   ```blade
   <!DOCTYPE html>
   <html lang="ko">
   <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>견적 요청 목록</title>
       @vite('resources/css/app.css')
   </head>
   <body class="bg-gray-100">
       <div class="container mx-auto px-4 py-8">
           <div class="flex justify-between items-center mb-6">
               <h1 class="text-3xl font-bold text-gray-800">견적 요청 목록</h1>
               <a href="{{ route('quotes.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition duration-200">
                   새 견적 요청
               </a>
           </div>

           @if($quotes->isEmpty())
               <div class="bg-white shadow-md rounded-lg p-8 text-center">
                   <p class="text-gray-500">아직 견적 요청이 없습니다.</p>
               </div>
           @else
               <div class="bg-white shadow-md rounded-lg overflow-hidden">
                   <table class="min-w-full divide-y divide-gray-200">
                       <thead class="bg-gray-50">
                           <tr>
                               <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">이름</th>
                               <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">이메일</th>
                               <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">서비스</th>
                               <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">예산</th>
                               <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">요청일</th>
                               <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상세</th>
                           </tr>
                       </thead>
                       <tbody class="bg-white divide-y divide-gray-200">
                           @foreach($quotes as $quote)
                           <tr class="hover:bg-gray-50">
                               <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $quote->name }}</td>
                               <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->email }}</td>
                               <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->service_type }}</td>
                               <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->budget_range }}</td>
                               <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->created_at->format('Y-m-d') }}</td>
                               <td class="px-6 py-4 whitespace-nowrap text-sm">
                                   <button onclick="showModal({{ $quote->id }})" class="text-blue-600 hover:text-blue-800 font-semibold">
                                       보기
                                   </button>
                               </td>
                           </tr>
                           @endforeach
                       </tbody>
                   </table>
               </div>
           @endif
       </div>

       <!-- 모달 -->
       <div id="modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
           <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white">
               <div class="flex justify-between items-center mb-4">
                   <h3 class="text-xl font-bold text-gray-800">견적 요청 상세</h3>
                   <button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
                       <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                       </svg>
                   </button>
               </div>
               <div id="modal-content" class="space-y-4">
                   <!-- 동적으로 내용 추가 -->
               </div>
           </div>
       </div>

       <script>
           const quotes = @json($quotes);

           function showModal(quoteId) {
               const quote = quotes.find(q => q.id === quoteId);
               if (!quote) return;

               const content = `
                   <div class="border-b pb-3">
                       <p class="text-sm text-gray-500">이름</p>
                       <p class="text-gray-900 font-semibold">${quote.name}</p>
                   </div>
                   <div class="border-b pb-3">
                       <p class="text-sm text-gray-500">이메일</p>
                       <p class="text-gray-900">${quote.email}</p>
                   </div>
                   <div class="border-b pb-3">
                       <p class="text-sm text-gray-500">전화번호</p>
                       <p class="text-gray-900">${quote.phone}</p>
                   </div>
                   <div class="border-b pb-3">
                       <p class="text-sm text-gray-500">서비스 종류</p>
                       <p class="text-gray-900">${quote.service_type}</p>
                   </div>
                   <div class="border-b pb-3">
                       <p class="text-sm text-gray-500">예산 범위</p>
                       <p class="text-gray-900">${quote.budget_range}</p>
                   </div>
                   <div class="border-b pb-3">
                       <p class="text-sm text-gray-500">프로젝트 설명</p>
                       <p class="text-gray-900">${quote.description}</p>
                   </div>
                   <div>
                       <p class="text-sm text-gray-500">요청 시간</p>
                       <p class="text-gray-900">${new Date(quote.created_at).toLocaleString('ko-KR')}</p>
                   </div>
               `;

               document.getElementById('modal-content').innerHTML = content;
               document.getElementById('modal').classList.remove('hidden');
           }

           function closeModal() {
               document.getElementById('modal').classList.add('hidden');
           }

           // 모달 외부 클릭 시 닫기
           document.getElementById('modal').addEventListener('click', function(e) {
               if (e.target === this) {
                   closeModal();
               }
           });
       </script>
   </body>
   </html>
   ```

브라우저에서 http://localhost:8000/admin/quotes 를 열어 견적 요청 목록이 표시되는지 확인하고, '보기' 버튼을 클릭하면 모달 팝업으로 상세 내용이 표시되는지 테스트하세요.

기대 결과: 관리자용 견적 요청 목록 페이지가 생성됩니다. 모든 견적 요청이 테이블 형태로 표시되며, 각 항목의 '보기' 버튼을 클릭하면 모달 팝업으로 상세 내용(이름, 이메일, 전화번호, 서비스 종류, 예산 범위, 프로젝트 설명, 요청 시간)이 표시됩니다. 반응형 디자인이 적용되어 모바일에서도 잘 보입니다.

제출된 작품 (0)

로그인 후 제출

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

댓글 (0)

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