diff --git a/backend/app/Http/Controllers/Api/FormApiController.php b/backend/app/Http/Controllers/Api/FormApiController.php index 29ea6aa..563f02e 100644 --- a/backend/app/Http/Controllers/Api/FormApiController.php +++ b/backend/app/Http/Controllers/Api/FormApiController.php @@ -7,73 +7,214 @@ use App\Models\Pelanggan; use App\Models\PelangganDetail; use App\Models\Template; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; class FormApiController extends Controller { + /** + * Convert string to slug-like field name + */ + private function slugify($text) + { + return strtolower( + preg_replace('/[^A-Za-z0-9_]/', '_', trim($text)) + ); + } + public function store(Request $request) { - // ✅ Validasi dasar - $rules = [ - 'template_id' => 'required|exists:templates,id', - 'nama_pemesan' => 'required|string|max:255', - 'no_hp' => 'required|string|max:20', - 'email' => 'required|email', - ]; + try { + // Log incoming request for debugging + Log::info('Form submission received', [ + 'template_id' => $request->template_id, + 'files' => array_keys($request->allFiles()), + 'data_keys' => array_keys($request->except(['_token'])) + ]); - // ✅ Ambil template & fiturnya - $template = Template::with(['fiturs', 'kategori'])->findOrFail($request->template_id); + // ✅ Validasi dasar + $rules = [ + 'template_id' => 'required|exists:templates,id', + 'nama_pemesan' => 'required|string|max:255', + 'no_hp' => 'required|string|max:20', + 'email' => 'required|email', + 'catatan' => 'nullable|string|max:500', + ]; - // ✅ Loop fitur → generate aturan validasi dinamis - foreach ($template->fiturs as $fitur) { - $field = str_replace(' ', '_', strtolower($fitur->deskripsi)); // contoh: "Nama Lengkap Pria" → "nama_lengkap_pria" + // ✅ Ambil template + fiturnya + $template = Template::with(['fiturs', 'kategori'])->findOrFail($request->template_id); - // Aturan default: required string max 255 - $rules[$field] = 'nullable|string|max:255'; + $galleryFields = []; // Track gallery field names - // Kalau fitur ada kata "tanggal" - if (str_contains(strtolower($fitur->deskripsi), 'tanggal')) { - $rules[$field] = 'nullable|date'; + // ✅ Loop fitur → buat validasi dinamis + foreach ($template->fiturs as $fitur) { + $field = $this->slugify($fitur->deskripsi); + + // default text input + $rules[$field] = 'nullable|string|max:255'; + + // tanggal + if (str_contains(strtolower($fitur->deskripsi), 'tanggal')) { + $rules[$field] = 'nullable|date'; + } + + // galeri (cek jumlah: Galeri 2, Galeri 5, dll.) + if (str_contains(strtolower($fitur->deskripsi), 'galeri') || + str_contains(strtolower($fitur->deskripsi), 'gallery')) { + + preg_match('/(\d+)/', $fitur->deskripsi, $matches); + $maxFiles = isset($matches[1]) ? (int) $matches[1] : 10; + + // Add gallery field to tracking + $galleryFields[] = $field; + + // Validation for gallery array + $rules[$field] = "nullable|array|max:$maxFiles"; + $rules[$field . '.*'] = 'file|image|mimes:jpeg,png,jpg,gif,webp|max:10240'; // 10MB + } } - // Kalau fitur ada kata "foto" atau "galeri" - if (str_contains(strtolower($fitur->deskripsi), 'galeri')) { - $rules['galeri'] = 'nullable|array|max:10'; - $rules['galeri.*'] = 'image|mimes:jpeg,png,jpg,gif|max:2048'; + Log::info('Validation rules generated', [ + 'rules' => $rules, + 'gallery_fields' => $galleryFields + ]); + + // ✅ Jalankan validasi + $validatedData = $request->validate($rules); + + // ✅ Process all gallery uploads + $allGalleryPaths = []; + + foreach ($galleryFields as $galleryField) { + if ($request->hasFile($galleryField)) { + $galleryPaths = []; + $files = $request->file($galleryField); + + Log::info("Processing files for field: $galleryField", [ + 'file_count' => is_array($files) ? count($files) : 1 + ]); + + // Handle both single file and array of files + if (!is_array($files)) { + $files = [$files]; + } + + foreach ($files as $file) { + try { + $path = $file->store('gallery', 'public'); + $galleryPaths[] = $path; + Log::info("File uploaded successfully", [ + 'original_name' => $file->getClientOriginalName(), + 'path' => $path + ]); + } catch (\Exception $e) { + Log::error("File upload failed", [ + 'file' => $file->getClientOriginalName(), + 'error' => $e->getMessage() + ]); + throw $e; + } + } + + if (!empty($galleryPaths)) { + $allGalleryPaths[$galleryField] = $galleryPaths; + $validatedData[$galleryField] = $galleryPaths; + } + } } + + Log::info('All gallery uploads processed', [ + 'gallery_data' => $allGalleryPaths + ]); + + // ✅ Simpan ke tabel pelanggan + $pelanggan = Pelanggan::create([ + 'nama_pemesan' => $validatedData['nama_pemesan'], + 'nama_template' => $template->nama_template, + 'kategori' => $template->kategori->nama ?? '-', + 'email' => $validatedData['email'], + 'no_tlpn' => $validatedData['no_hp'], + 'harga' => $template->harga, + 'catatan' => $validatedData['catatan'] ?? null, + ]); + + // ✅ Simpan detail form (dinamis) - include gallery paths + PelangganDetail::create([ + 'pelanggan_id' => $pelanggan->id, + 'detail_form' => $validatedData, + ]); + + Log::info('Form submitted successfully', [ + 'pelanggan_id' => $pelanggan->id, + 'template_id' => $template->id + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Form berhasil dikirim sesuai fitur template', + 'data' => $pelanggan->load('details'), + 'gallery_info' => $allGalleryPaths + ], 201); + + } catch (\Illuminate\Validation\ValidationException $e) { + Log::error('Validation failed', [ + 'errors' => $e->errors(), + 'input' => $request->except(['password', '_token']) + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal', + 'errors' => $e->errors() + ], 422); + + } catch (\Exception $e) { + Log::error('Form submission failed', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + 'input' => $request->except(['password', '_token']) + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan internal server', + 'error' => config('app.debug') ? $e->getMessage() : 'Internal server error' + ], 500); } - - // ✅ Jalankan validasi - $data = $request->validate($rules); - - // --- PROSES UPLOAD GALERI --- - $galleryPaths = []; - if ($request->hasFile('galeri')) { - foreach ($request->file('galeri') as $file) { - $galleryPaths[] = $file->store('gallery', 'public'); - } - } - $data['galeri'] = $galleryPaths; - - // ✅ Simpan ke tabel pelanggan - $pelanggan = Pelanggan::create([ - 'nama_pemesan' => $data['nama_pemesan'], - 'nama_template' => $template->nama_template, - 'kategori' => $template->kategori->nama ?? '-', - 'email' => $data['email'], - 'no_tlpn' => $data['no_hp'], - 'harga' => $template->harga, - ]); - - // ✅ Simpan detail form (dinamis) - PelangganDetail::create([ - 'pelanggan_id' => $pelanggan->id, - 'detail_form' => $data, - ]); - - return response()->json([ - 'success' => true, - 'message' => 'Form berhasil dikirim sesuai fitur template', - 'data' => $pelanggan->load('details') - ], 201); } -} + + public function getFiturs($id) + { + try { + $template = Template::with(['fiturs', 'kategori'])->findOrFail($id); + + return response()->json([ + 'success' => true, + 'template' => [ + 'id' => $template->id, + 'nama_template' => $template->nama_template, + 'kategori' => $template->kategori->nama ?? '-', + 'harga' => $template->harga, + 'thumbnail' => $template->thumbnail ?? null, + ], + 'fiturs' => $template->fiturs->map(function ($fitur) { + return [ + 'id' => $fitur->id, + 'deskripsi' => $fitur->deskripsi, + 'harga' => $fitur->harga, + ]; + }), + ]); + } catch (\Exception $e) { + Log::error('Failed to get template fiturs', [ + 'template_id' => $id, + 'error' => $e->getMessage() + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Template tidak ditemukan', + 'error' => config('app.debug') ? $e->getMessage() : 'Template not found' + ], 404); + } + } +} \ No newline at end of file diff --git a/backend/routes/api.php b/backend/routes/api.php index 1a9aa63..175a20d 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -2,32 +2,48 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; -use App\Http\Controllers\Api\ReviewController; use App\Http\Controllers\Api\KategoriApiController; -use App\Http\Controllers\Api\PernikahanApiController; -use App\Http\Controllers\Api\UlangTahunApiController; -use App\Http\Controllers\Api\KhitanApiController; use App\Http\Controllers\Api\TemplateApiController; use App\Http\Controllers\Api\FormApiController; +use App\Http\Controllers\Api\ReviewController; -// Form API (universal, dinamis berdasarkan template_id) -Route::post('form', [FormApiController::class, 'store']); +/* +|-------------------------------------------------------------------------- +| API Routes +|-------------------------------------------------------------------------- +| +| Here is where you can register API routes for your application. These +| routes are loaded by the RouteServiceProvider and all of them will +| be assigned to the "api" middleware group. Make something great! +| +*/ -// // Form API (user) -// Route::post('form/pernikahan', [PernikahanApiController::class, 'store']); -// Route::post('form/ulang-tahun', [UlangTahunApiController::class, 'store']); -// Route::post('form/khitan', [KhitanApiController::class, 'store']); +Route::middleware('auth:sanctum')->get('/user', function (Request $request) { + return $request->user(); +}); -// API Kategori hanya read-only +// =============================== +// API Kategori (read-only) +// =============================== Route::get('kategoris', [KategoriApiController::class, 'index']); Route::get('kategoris/{kategori}', [KategoriApiController::class, 'show']); +// =============================== // API Reviews +// =============================== Route::apiResource('reviews', ReviewController::class); +// =============================== // API Templates -Route::get('templates/random', [TemplateApiController::class, 'random']); // NEW +// =============================== +Route::get('templates/random', [TemplateApiController::class, 'random']); // random template Route::get('templates', [TemplateApiController::class, 'index']); Route::get('templates/{template}', [TemplateApiController::class, 'show']); Route::get('templates/category/{id}', [TemplateApiController::class, 'byCategory']); -Route::get('/templates/{id}', [TemplateApiController::class, 'show']); +Route::get('/templates/{id}', [TemplateApiController::class, 'show']); // duplicate tapi ga masalah + +// =============================== +// API Form (user submit) +// =============================== +Route::post('form', [FormApiController::class, 'store']); // <<== INI yang ditambah +Route::get('templates/{id}/fiturs', [FormApiController::class, 'getFiturs']); diff --git a/proyek-frontend/app/components/template-page/TemplateGrid.vue b/proyek-frontend/app/components/template-page/TemplateGrid.vue index 01abd96..b7e9208 100644 --- a/proyek-frontend/app/components/template-page/TemplateGrid.vue +++ b/proyek-frontend/app/components/template-page/TemplateGrid.vue @@ -71,10 +71,12 @@ - - Order - + + Order + + diff --git a/proyek-frontend/app/pages/form/[id].vue b/proyek-frontend/app/pages/form/[id].vue new file mode 100644 index 0000000..36b1885 --- /dev/null +++ b/proyek-frontend/app/pages/form/[id].vue @@ -0,0 +1,349 @@ +[id].vue + + + + + \ No newline at end of file