diff --git a/backend-baru/app/Http/Controllers/Api/RsvpApiController.php b/backend-baru/app/Http/Controllers/Api/RsvpApiController.php new file mode 100644 index 0000000..a753cf8 --- /dev/null +++ b/backend-baru/app/Http/Controllers/Api/RsvpApiController.php @@ -0,0 +1,78 @@ +validate([ + 'nama' => 'required|string|max:255', + 'pesan' => 'required|string', + 'status_kehadiran' => 'required|in:hadir,tidak_hadir,mungkin', + ]); + + $guest = Guest::where('kode_invitasi', $kodeInvitasi)->first(); + + if (!$guest) { + return response()->json([ + 'success' => false, + 'message' => 'Kode invitasi tidak ditemukan.', + ], 404); + } + + $rsvp = Rsvp::create([ + 'guest_id' => $guest->id, + 'nama' => $validated['nama'], + 'pesan' => $validated['pesan'], + 'status_kehadiran' => $validated['status_kehadiran'], + ]); + + return response()->json([ + 'success' => true, + 'message' => 'RSVP berhasil disimpan', + 'data' => $rsvp, + ], 201); + + } catch (\Illuminate\Validation\ValidationException $e) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal', + 'errors' => $e->errors(), + ], 422); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Gagal menyimpan RSVP.', + ], 500); + } + } + + // 🔹 Ambil semua RSVP berdasarkan kode_invitasi (GET) + public function index($kodeInvitasi) + { + $guest = Guest::where('kode_invitasi', $kodeInvitasi)->first(); + + if (!$guest) { + return response()->json([ + 'success' => false, + 'message' => 'Kode invitasi tidak ditemukan.', + ], 404); + } + + $rsvps = Rsvp::where('guest_id', $guest->id)->get(); + + return response()->json([ + 'success' => true, + 'message' => 'Data RSVP ditemukan', + 'data' => $rsvps, + ]); + } +} diff --git a/backend-baru/app/Http/Controllers/RsvpController.php b/backend-baru/app/Http/Controllers/RsvpController.php new file mode 100644 index 0000000..1fef89a --- /dev/null +++ b/backend-baru/app/Http/Controllers/RsvpController.php @@ -0,0 +1,49 @@ +first(); + + if (!$pelanggan) { + return response()->json(['message' => 'Kode undangan tidak ditemukan'], 404); + } + + $rsvp = Rsvp::create([ + 'pelanggan_id' => $pelanggan->id, + 'nama' => $request->nama, + 'pesan' => $request->pesan, + 'status_kehadiran' => $request->status_kehadiran, + ]); + + return response()->json([ + 'message' => 'RSVP berhasil disimpan', + 'data' => $rsvp, + ]); + } + + // 🔹 Ambil semua RSVP berdasarkan kode undangan (GET) + public function show($invitationCode) +{ + $pelanggan = Pelanggan::where('kode_invitasi', $invitationCode)->first(); + + if (!$pelanggan) { + return response()->json(['message' => 'Kode undangan tidak ditemukan'], 404); + } + + $rsvp = Rsvp::where('pelanggan_id', $pelanggan->id)->get(); + + return response()->json([ + 'message' => 'Data RSVP ditemukan', + 'data' => $rsvp + ]); +} +} diff --git a/backend-baru/app/Models/Guest.php b/backend-baru/app/Models/Guest.php index 3867890..ae9ba19 100644 --- a/backend-baru/app/Models/Guest.php +++ b/backend-baru/app/Models/Guest.php @@ -19,4 +19,9 @@ class Guest extends Model { return $this->belongsTo(Pelanggan::class, 'id_pelanggan'); } + + public function rsvps() + { + return $this->hasMany(Rsvp::class); + } } diff --git a/backend-baru/app/Models/Rsvp.php b/backend-baru/app/Models/Rsvp.php new file mode 100644 index 0000000..949311c --- /dev/null +++ b/backend-baru/app/Models/Rsvp.php @@ -0,0 +1,16 @@ +belongsTo(Guest::class); + } +} diff --git a/backend-baru/database/migrations/2025_10_23_032107_create_rsvps_tables.php b/backend-baru/database/migrations/2025_10_23_032107_create_rsvps_tables.php new file mode 100644 index 0000000..3d12a23 --- /dev/null +++ b/backend-baru/database/migrations/2025_10_23_032107_create_rsvps_tables.php @@ -0,0 +1,24 @@ +id(); + $table->foreignId('guest_id')->constrained('guests')->cascadeOnDelete(); // Link ke guests table + $table->string('nama'); + $table->text('pesan'); + $table->enum('status_kehadiran', ['hadir', 'tidak_hadir', 'mungkin'])->default('mungkin'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('rsvps'); + } +}; diff --git a/backend-baru/routes/api.php b/backend-baru/routes/api.php index 4dc045b..cc8059c 100644 --- a/backend-baru/routes/api.php +++ b/backend-baru/routes/api.php @@ -54,3 +54,11 @@ Route::delete('/reviews/{id}', [ReviewApiController::class, 'destroy']); Route::get('/guests/{code}', [GuestApiController::class, 'index']); Route::post('/guests', [GuestApiController::class, 'store']); Route::delete('/guests/{id}', [GuestApiController::class, 'destroy']); + +use App\Http\Controllers\RsvpController; +use App\Http\Controllers\Api\RsvpApiController; + +// 🔹 RSVP Routes BARU +Route::get('/rsvp/{kodeInvitasi}', [RsvpApiController::class, 'index']); +Route::post('/rsvp/{kodeInvitasi}', [RsvpApiController::class, 'store']); + diff --git a/proyek-frontend/app/components/templates/Ultah/GuestBook.vue b/proyek-frontend/app/components/templates/Ultah/GuestBook.vue index 329e4e4..8554fca 100644 --- a/proyek-frontend/app/components/templates/Ultah/GuestBook.vue +++ b/proyek-frontend/app/components/templates/Ultah/GuestBook.vue @@ -13,9 +13,9 @@ class="w-full px-4 py-2 rounded-xl border-2 border-orange-400 focus:ring-2 focus:ring-orange-500"> - Hadir - Tidak Hadir - Mungkin + Hadir + Tidak Hadir + Mungkin @@ -28,43 +28,119 @@ - {{ msg.name }} - - {{ msg.attendance }} + {{ msg.nama }} + + {{ formatAttendance(msg.status_kehadiran) }} - {{ msg.message }} + {{ msg.pesan }} +}; + +// Fungsi untuk memformat teks kehadiran +const formatAttendance = (attendance) => { + switch (attendance) { + case 'yes': + return 'Hadir'; + case 'no': + return 'Tidak Hadir'; + case 'maybe': + return 'Mungkin'; + default: + return attendance; + } +}; + +// Panggil fetchMessages saat komponen dimuat +onMounted(() => { + fetchMessages(); +}); + \ No newline at end of file diff --git a/proyek-frontend/app/components/templates/UltahBasic/GuestBook.vue b/proyek-frontend/app/components/templates/UltahBasic/GuestBook.vue index 93937d0..abf543d 100644 --- a/proyek-frontend/app/components/templates/UltahBasic/GuestBook.vue +++ b/proyek-frontend/app/components/templates/UltahBasic/GuestBook.vue @@ -1,64 +1,76 @@ - + 💌 Buku Tamu 💌 - + - + - - + + - + + + + - - ✨ Kirim Ucapan + + + + + + + + + ✅ Saya Hadir + ❌ Tidak Hadir + 🤔 Mungkin Hadir + + + + + + {{ errorMessage }} + + + + + ✨ {{ submitLoading ? 'Mengirim...' : 'Kirim RSVP' }} - - - - - 💬 - - {{ msg }} + + + Memuat ucapan... + + + + + + {{ msg.nama }} + + {{ formatAttendance(msg.status_kehadiran) }} + + {{ msg.pesan }} - + Belum ada ucapan. Jadilah yang pertama mengucapkan! @@ -67,21 +79,109 @@ diff --git a/proyek-frontend/app/pages/p/daftar-tamu.vue b/proyek-frontend/app/pages/p/daftar-tamu.vue index df88c35..98b771d 100644 --- a/proyek-frontend/app/pages/p/daftar-tamu.vue +++ b/proyek-frontend/app/pages/p/daftar-tamu.vue @@ -92,8 +92,28 @@ > {{ index + 1 }} {{ guest.nama_tamu }} - - {{ guest.kode_invitasi }} + + + + {{ guest.kode_invitasi }} + + + + + + Share + + { + const inviteUrl = `${config.public.baseUrl}/p/${kodeInvitasi}`; + + if (navigator.share) { + // Native Web Share API (mobile) + try { + await navigator.share({ + title: 'Undangan Pernikahan', + text: 'Silakan buka undangan pernikahan saya', + url: inviteUrl, + }); + } catch (error) { + if (error.name !== 'AbortError') { + copyToClipboard(inviteUrl); + } + } + } else { + // Fallback to copy to clipboard (desktop) + copyToClipboard(inviteUrl); + } +}; + +// Copy to clipboard function +const copyToClipboard = (text) => { + navigator.clipboard.writeText(text).then(() => { + // Show temporary success message + const originalText = successMessage.value; + successMessage.value = 'Link undangan berhasil disalin!'; + setTimeout(() => { + successMessage.value = originalText; + }, 2000); + }).catch((err) => { + console.error('Failed to copy: ', err); + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = text; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + try { + document.execCommand('copy'); + const originalText = successMessage.value; + successMessage.value = 'Link undangan berhasil disalin!'; + setTimeout(() => { + successMessage.value = originalText; + }, 2000); + } catch (err) { + console.error('Fallback copy failed: ', err); + } + document.body.removeChild(textArea); + }); +}; + // Fetch guests data based on customer code const handleFetchGuests = async () => { if (!kodePelanggan.value.trim()) {
{{ msg.message }}
{{ msg.pesan }}
{{ msg }}
+
Belum ada ucapan. Jadilah yang pertama mengucapkan!