1132 lines
59 KiB
PHP
1132 lines
59 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', 'Pelanggan')
|
|
|
|
@section('content')
|
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-900">Daftar Pelanggan</h2>
|
|
<button id="addCustomerBtn" class="inline-flex items-center px-4 py-2 bg-indigo-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-indigo-700 focus:bg-indigo-700 active:bg-indigo-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
</svg>
|
|
Tambah Pelanggan
|
|
</button>
|
|
</div>
|
|
|
|
<div id="alert" class="hidden mb-6">
|
|
<!-- Alert will be shown here -->
|
|
</div>
|
|
|
|
<!-- Customer Detail Modal -->
|
|
<div id="customerDetailModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
|
|
<div class="relative top-20 mx-auto p-5 border w-3/4 shadow-lg rounded-lg bg-white">
|
|
<div class="mt-3">
|
|
<div class="flex justify-between items-center border-b pb-4">
|
|
<h3 class="text-xl font-semibold text-gray-800">Detail Pelanggan</h3>
|
|
<button onclick="closeCustomerDetailModal()" class="text-gray-400 hover:text-gray-500 transition-colors duration-200">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4 px-7 py-3">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="space-y-4">
|
|
<div class="flex items-center space-x-4">
|
|
<div id="customerPhoto" class="h-20 w-20 rounded-full bg-indigo-100 flex items-center justify-center">
|
|
<!-- Photo will be inserted here -->
|
|
</div>
|
|
<div>
|
|
<h4 id="customerName" class="text-xl font-semibold text-gray-900"></h4>
|
|
<p id="customerEmail" class="text-sm text-gray-500"></p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h5 class="text-sm font-medium text-gray-500">Informasi Kontak</h5>
|
|
<div class="mt-2 space-y-2">
|
|
<p id="customerPhone" class="text-sm text-gray-900"></p>
|
|
<p id="customerAddress" class="text-sm text-gray-900"></p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h5 class="text-sm font-medium text-gray-500">Lokasi</h5>
|
|
<div class="mt-2 space-y-2">
|
|
<p id="customerLatitude" class="text-sm text-gray-900"></p>
|
|
<p id="customerLongitude" class="text-sm text-gray-900"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h5 class="text-sm font-medium text-gray-500 mb-3">Spesialisasi yang Disukai</h5>
|
|
<div class="grid grid-cols-2 gap-4" id="customerSpecs">
|
|
<!-- Specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 border-t pt-6">
|
|
<div class="grid grid-cols-2 gap-6">
|
|
<div>
|
|
<h5 class="text-sm font-medium text-gray-500 mb-3">Statistik Pesanan</h5>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-gray-500">Total Pesanan</span>
|
|
<span id="totalBookings" class="text-lg font-semibold text-gray-900">0</span>
|
|
</div>
|
|
<div class="flex justify-between items-center mt-2">
|
|
<span class="text-sm text-gray-500">Pesanan Selesai</span>
|
|
<span id="completedBookings" class="text-lg font-semibold text-gray-900">0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h5 class="text-sm font-medium text-gray-500 mb-3">Statistik Rating</h5>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-gray-500">Total Rating</span>
|
|
<span id="totalRatings" class="text-lg font-semibold text-gray-900">0</span>
|
|
</div>
|
|
<div class="flex justify-between items-center mt-2">
|
|
<span class="text-sm text-gray-500">Rata-rata Rating</span>
|
|
<span id="averageRating" class="text-lg font-semibold text-gray-900">0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Customer Modal -->
|
|
<div id="editCustomerModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
|
|
<div class="relative top-20 mx-auto p-5 border w-1/2 shadow-lg rounded-lg bg-white">
|
|
<div class="mt-3">
|
|
<div class="flex justify-between items-center border-b pb-4">
|
|
<h3 class="text-xl font-semibold text-gray-800">Edit Spesialisasi Pelanggan</h3>
|
|
<button onclick="closeEditModal()" class="text-gray-400 hover:text-gray-500 transition-colors duration-200">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4 px-7 py-3">
|
|
<form id="editCustomerForm" onsubmit="updateCustomer(event)">
|
|
<input type="hidden" id="editCustomerId">
|
|
<div class="space-y-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">Spesialisasi yang Disukai</label>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
|
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
|
|
</svg>
|
|
Gaya Busana
|
|
</h4>
|
|
<div class="space-y-2" id="styleSpecializations">
|
|
<!-- Style specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
|
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
</svg>
|
|
Jenis Layanan
|
|
</h4>
|
|
<div class="space-y-2" id="serviceSpecializations">
|
|
<!-- Service specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
|
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
</svg>
|
|
Hiasan Busana
|
|
</h4>
|
|
<div class="space-y-2" id="decorationSpecializations">
|
|
<!-- Decoration specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 flex justify-end space-x-3 border-t pt-4">
|
|
<button type="button" onclick="closeEditModal()" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
|
|
<svg class="w-5 h-5 mr-2" 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" />
|
|
</svg>
|
|
Batal
|
|
</button>
|
|
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
Simpan Perubahan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Customer Modal -->
|
|
<div id="addCustomerModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
|
|
<div class="relative top-20 mx-auto p-5 border w-1/2 shadow-lg rounded-lg bg-white">
|
|
<div class="mt-3">
|
|
<div class="flex justify-between items-center border-b pb-4">
|
|
<h3 class="text-xl font-semibold text-gray-800">Tambah Pelanggan Baru</h3>
|
|
<button onclick="closeAddModal()" class="text-gray-400 hover:text-gray-500 transition-colors duration-200">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4 px-7 py-3">
|
|
<form id="addCustomerForm" onsubmit="addCustomer(event)">
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label for="name" class="block text-sm font-medium text-gray-700">Nama Lengkap</label>
|
|
<input type="text" id="name" name="name" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
|
<input type="email" id="email" name="email" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
|
|
<input type="password" id="password" name="password" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="phone_number" class="block text-sm font-medium text-gray-700">Nomor Telepon</label>
|
|
<input type="text" id="phone_number" name="phone_number" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="address" class="block text-sm font-medium text-gray-700">Alamat</label>
|
|
<textarea id="address" name="address" rows="3" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"></textarea>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="latitude" class="block text-sm font-medium text-gray-700">Latitude</label>
|
|
<div class="flex gap-2">
|
|
<input type="number" step="any" id="latitude" name="latitude" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
<button type="button" onclick="getCurrentLocation()"
|
|
class="mt-1 inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
</svg>
|
|
Dapatkan Lokasi
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="longitude" class="block text-sm font-medium text-gray-700">Longitude</label>
|
|
<input type="number" step="any" id="longitude" name="longitude" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">Spesialisasi yang Disukai</label>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
|
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
|
|
</svg>
|
|
Gaya Busana
|
|
</h4>
|
|
<div class="space-y-2" id="addStyleSpecializations">
|
|
<!-- Style specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
|
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
</svg>
|
|
Jenis Layanan
|
|
</h4>
|
|
<div class="space-y-2" id="addServiceSpecializations">
|
|
<!-- Service specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
|
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
</svg>
|
|
Hiasan Busana
|
|
</h4>
|
|
<div class="space-y-2" id="addDecorationSpecializations">
|
|
<!-- Decoration specializations will be inserted here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 flex justify-end space-x-3 border-t pt-4">
|
|
<button type="button" onclick="closeAddModal()" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
|
|
<svg class="w-5 h-5 mr-2" 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" />
|
|
</svg>
|
|
Batal
|
|
</button>
|
|
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
Tambah Pelanggan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow-md rounded-lg overflow-hidden">
|
|
<!-- Search and Filter Section -->
|
|
<div class="p-4 border-b" id="searchSection">
|
|
<div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
|
|
<div class="w-full md:w-64">
|
|
<div class="relative">
|
|
<input type="text" id="searchInput" placeholder="Cari pelanggan..."
|
|
class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent">
|
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-4">
|
|
<div class="flex items-center">
|
|
<label for="perPage" class="mr-2 text-sm text-gray-600">Tampilkan:</label>
|
|
<select id="perPage" class="rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent">
|
|
<option value="10">10</option>
|
|
<option value="25">25</option>
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Nama
|
|
</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Email
|
|
</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Telepon
|
|
</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Alamat
|
|
</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Spesialisasi
|
|
</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Pesanan
|
|
</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Aksi
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="customersTableBody" class="bg-white divide-y divide-gray-200">
|
|
<!-- Data will be loaded here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
|
<div class="flex-1 flex justify-between sm:hidden">
|
|
<button id="prevPageMobile" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
|
|
Sebelumnya
|
|
</button>
|
|
<button id="nextPageMobile" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
|
|
Selanjutnya
|
|
</button>
|
|
</div>
|
|
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-700">
|
|
Menampilkan
|
|
<span id="startItem" class="font-medium">1</span>
|
|
sampai
|
|
<span id="endItem" class="font-medium">10</span>
|
|
dari
|
|
<span id="totalItems" class="font-medium">0</span>
|
|
hasil
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
|
<button id="prevPage" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
|
<span class="sr-only">Sebelumnya</span>
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
</button>
|
|
<div id="paginationNumbers" class="flex">
|
|
<!-- Page numbers will be inserted here -->
|
|
</div>
|
|
<button id="nextPage" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
|
<span class="sr-only">Selanjutnya</span>
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<script>
|
|
let currentPage = 1;
|
|
let perPage = 10;
|
|
let totalPages = 1;
|
|
let searchQuery = '';
|
|
let allCustomers = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
try {
|
|
const response = await fetch(window.apiUrl('customers'), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
window.location.href = '{{ route('admin.login') }}';
|
|
return;
|
|
}
|
|
throw new Error('Failed to fetch customers');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'success') {
|
|
allCustomers = data.data;
|
|
updateTable();
|
|
setupEventListeners();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showErrorAlert('Gagal memuat data pelanggan. Silakan coba lagi nanti.');
|
|
}
|
|
});
|
|
|
|
function setupEventListeners() {
|
|
// Search input
|
|
document.getElementById('searchInput').addEventListener('input', (e) => {
|
|
searchQuery = e.target.value.toLowerCase();
|
|
currentPage = 1;
|
|
updateTable();
|
|
});
|
|
|
|
// Per page select
|
|
document.getElementById('perPage').addEventListener('change', (e) => {
|
|
perPage = parseInt(e.target.value);
|
|
currentPage = 1;
|
|
updateTable();
|
|
});
|
|
|
|
// Pagination buttons
|
|
document.getElementById('prevPage').addEventListener('click', () => {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
updateTable();
|
|
}
|
|
});
|
|
|
|
document.getElementById('nextPage').addEventListener('click', () => {
|
|
if (currentPage < totalPages) {
|
|
currentPage++;
|
|
updateTable();
|
|
}
|
|
});
|
|
|
|
document.getElementById('prevPageMobile').addEventListener('click', () => {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
updateTable();
|
|
}
|
|
});
|
|
|
|
document.getElementById('nextPageMobile').addEventListener('click', () => {
|
|
if (currentPage < totalPages) {
|
|
currentPage++;
|
|
updateTable();
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateTable() {
|
|
// Filter customers based on search query
|
|
let filteredCustomers = allCustomers.filter(customer =>
|
|
customer.name.toLowerCase().includes(searchQuery) ||
|
|
customer.email.toLowerCase().includes(searchQuery) ||
|
|
(customer.phone_number && customer.phone_number.toLowerCase().includes(searchQuery)) ||
|
|
(customer.address && customer.address.toLowerCase().includes(searchQuery))
|
|
);
|
|
|
|
// Calculate pagination
|
|
totalPages = Math.ceil(filteredCustomers.length / perPage);
|
|
const startIndex = (currentPage - 1) * perPage;
|
|
const endIndex = startIndex + perPage;
|
|
const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex);
|
|
|
|
// Update table body
|
|
const tableBody = document.getElementById('customersTableBody');
|
|
tableBody.innerHTML = paginatedCustomers.map(customer => `
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-10 w-10">
|
|
${customer.profile_photo
|
|
? `<img class="h-10 w-10 rounded-full object-cover" src="${window.assetUrl(customer.profile_photo)}" alt="${customer.name}">`
|
|
: `<div class="h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center">
|
|
<span class="text-indigo-700 font-medium text-sm">${customer.name.substring(0, 2).toUpperCase()}</span>
|
|
</div>`
|
|
}
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm font-medium text-gray-900">${customer.name}</div>
|
|
<div class="text-sm text-gray-500">Pelanggan</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900">${customer.email}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900">${customer.phone_number || '-'}</div>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<div class="text-sm text-gray-900 max-w-xs truncate">${customer.address || '-'}</div>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<div class="flex flex-wrap gap-1">
|
|
${customer.preferred_specializations.map(spec => `
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
${spec.name}
|
|
</span>
|
|
`).join('')}
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm">
|
|
<div>Total: ${customer.bookings.total}</div>
|
|
<div>Selesai: ${customer.bookings.completed}</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<div class="flex space-x-2">
|
|
<button onclick="viewCustomer(${customer.id})" class="text-indigo-600 hover:text-indigo-900">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
|
</svg>
|
|
</button>
|
|
<button onclick="editCustomer(${customer.id})" class="text-yellow-600 hover:text-yellow-900">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
|
</svg>
|
|
</button>
|
|
<button onclick="deleteCustomer(${customer.id})" class="text-red-600 hover:text-red-800">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
// Update pagination info
|
|
document.getElementById('startItem').textContent = startIndex + 1;
|
|
document.getElementById('endItem').textContent = Math.min(endIndex, filteredCustomers.length);
|
|
document.getElementById('totalItems').textContent = filteredCustomers.length;
|
|
|
|
// Update pagination numbers
|
|
const paginationNumbers = document.getElementById('paginationNumbers');
|
|
let paginationHTML = '';
|
|
|
|
// Always show first page
|
|
paginationHTML += `
|
|
<button class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium ${
|
|
currentPage === 1 ? 'text-indigo-600 bg-indigo-50' : 'text-gray-700 hover:bg-gray-50'
|
|
}" onclick="currentPage = 1; updateTable();">
|
|
1
|
|
</button>
|
|
`;
|
|
|
|
// Show pages around current page
|
|
const startPage = Math.max(2, currentPage - 1);
|
|
const endPage = Math.min(totalPages - 1, currentPage + 1);
|
|
|
|
if (startPage > 2) {
|
|
paginationHTML += '<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700">...</span>';
|
|
}
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
paginationHTML += `
|
|
<button class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium ${
|
|
currentPage === i ? 'text-indigo-600 bg-indigo-50' : 'text-gray-700 hover:bg-gray-50'
|
|
}" onclick="currentPage = ${i}; updateTable();">
|
|
${i}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
if (endPage < totalPages - 1) {
|
|
paginationHTML += '<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700">...</span>';
|
|
}
|
|
|
|
// Always show last page if there is more than one page
|
|
if (totalPages > 1) {
|
|
paginationHTML += `
|
|
<button class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium ${
|
|
currentPage === totalPages ? 'text-indigo-600 bg-indigo-50' : 'text-gray-700 hover:bg-gray-50'
|
|
}" onclick="currentPage = ${totalPages}; updateTable();">
|
|
${totalPages}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
paginationNumbers.innerHTML = paginationHTML;
|
|
|
|
// Update pagination button states
|
|
document.getElementById('prevPage').disabled = currentPage === 1;
|
|
document.getElementById('nextPage').disabled = currentPage === totalPages;
|
|
document.getElementById('prevPageMobile').disabled = currentPage === 1;
|
|
document.getElementById('nextPageMobile').disabled = currentPage === totalPages;
|
|
}
|
|
|
|
function showErrorAlert(message) {
|
|
const alert = document.getElementById('alert');
|
|
alert.classList.remove('hidden');
|
|
alert.classList.add('bg-red-100', 'border-l-4', 'border-red-500', 'text-red-700', 'p-4');
|
|
alert.innerHTML = `
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm">${message}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async function viewCustomer(id) {
|
|
try {
|
|
const response = await fetch(window.apiUrl(`customers/${id}`), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch customer details');
|
|
}
|
|
|
|
const data = await response.json();
|
|
const customer = data.data;
|
|
|
|
// Populate detail modal with customer info
|
|
document.getElementById('customerName').textContent = customer.name;
|
|
document.getElementById('customerEmail').textContent = customer.email;
|
|
document.getElementById('customerPhone').textContent = customer.phone_number || 'Tidak ada';
|
|
document.getElementById('customerAddress').textContent = customer.address || 'Tidak ada';
|
|
document.getElementById('customerSpecs').innerHTML = '';
|
|
|
|
// Update profile photo
|
|
const photoContainer = document.getElementById('customerPhoto');
|
|
if (customer.profile_photo) {
|
|
photoContainer.innerHTML = `<img class="h-20 w-20 rounded-full object-cover" src="${window.assetUrl(customer.profile_photo)}" alt="${customer.name}">`;
|
|
} else {
|
|
photoContainer.innerHTML = `<span class="text-indigo-700 font-medium text-xl">${customer.name.substring(0, 2).toUpperCase()}</span>`;
|
|
}
|
|
|
|
// Populate preferred specializations
|
|
if (customer.preferred_specializations && customer.preferred_specializations.length > 0) {
|
|
const specsContainer = document.getElementById('customerSpecs');
|
|
specsContainer.innerHTML = customer.preferred_specializations.map(spec => `
|
|
<div class="flex items-center p-2 bg-gray-50 rounded-lg mb-2">
|
|
<div class="h-8 w-8 mr-3">
|
|
<img src="${window.assetUrl(spec.photo)}" alt="${spec.name}" class="h-8 w-8 rounded object-cover">
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-800">${spec.name}</p>
|
|
<p class="text-xs text-gray-500">${spec.category}</p>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
document.getElementById('customerSpecs').innerHTML = '<p class="text-gray-500 italic">Tidak ada spesialisasi favorit</p>';
|
|
}
|
|
|
|
// Show booking statistics
|
|
document.getElementById('totalBookings').textContent = customer.bookings.total;
|
|
document.getElementById('completedBookings').textContent = customer.bookings.completed;
|
|
|
|
// Show modal
|
|
document.getElementById('customerDetailModal').classList.remove('hidden');
|
|
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showErrorAlert('Gagal memuat detail pelanggan');
|
|
}
|
|
}
|
|
|
|
function closeCustomerDetailModal() {
|
|
document.getElementById('customerDetailModal').classList.add('hidden');
|
|
}
|
|
|
|
async function editCustomer(id) {
|
|
try {
|
|
// Get customer data
|
|
const customerResponse = await fetch(window.apiUrl(`customers/${id}`), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!customerResponse.ok) {
|
|
throw new Error('Failed to fetch customer data');
|
|
}
|
|
|
|
const customerData = await customerResponse.json();
|
|
const customer = customerData.data;
|
|
|
|
// Get all specializations for the dropdown
|
|
const specializationsResponse = await fetch(window.apiUrl('specializations'), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!specializationsResponse.ok) {
|
|
throw new Error('Failed to fetch specializations');
|
|
}
|
|
|
|
const specializationsData = await specializationsResponse.json();
|
|
const specializations = specializationsData.data;
|
|
|
|
// Update form fields
|
|
document.getElementById('editCustomerId').value = customer.id;
|
|
|
|
// Group specializations by category
|
|
const styleSpecializations = specializations.filter(s => s.category === 'Gaya Busana');
|
|
const serviceSpecializations = specializations.filter(s => s.category === 'Jenis Layanan');
|
|
const decorationSpecializations = specializations.filter(s => s.category === 'Hiasan Busana');
|
|
|
|
// Update specialization checkboxes
|
|
document.getElementById('styleSpecializations').innerHTML = styleSpecializations.map(spec => `
|
|
<div class="flex items-center p-2 hover:bg-white rounded-md transition-colors duration-200">
|
|
<input type="checkbox" id="spec_${spec.id}" name="specializations[]" value="${spec.id}"
|
|
${customer.preferred_specializations.some(s => s.id === spec.id) ? 'checked' : ''}
|
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded transition-colors duration-200">
|
|
<label for="spec_${spec.id}" class="ml-3 block text-sm text-gray-700">${spec.name}</label>
|
|
</div>
|
|
`).join('');
|
|
|
|
document.getElementById('serviceSpecializations').innerHTML = serviceSpecializations.map(spec => `
|
|
<div class="flex items-center p-2 hover:bg-white rounded-md transition-colors duration-200">
|
|
<input type="checkbox" id="spec_${spec.id}" name="specializations[]" value="${spec.id}"
|
|
${customer.preferred_specializations.some(s => s.id === spec.id) ? 'checked' : ''}
|
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded transition-colors duration-200">
|
|
<label for="spec_${spec.id}" class="ml-3 block text-sm text-gray-700">${spec.name}</label>
|
|
</div>
|
|
`).join('');
|
|
|
|
document.getElementById('decorationSpecializations').innerHTML = decorationSpecializations.map(spec => `
|
|
<div class="flex items-center p-2 hover:bg-white rounded-md transition-colors duration-200">
|
|
<input type="checkbox" id="spec_${spec.id}" name="specializations[]" value="${spec.id}"
|
|
${customer.preferred_specializations.some(s => s.id === spec.id) ? 'checked' : ''}
|
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded transition-colors duration-200">
|
|
<label for="spec_${spec.id}" class="ml-3 block text-sm text-gray-700">${spec.name}</label>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Show modal
|
|
document.getElementById('editCustomerModal').classList.remove('hidden');
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('Gagal memuat data pelanggan. Silakan coba lagi nanti.');
|
|
}
|
|
}
|
|
|
|
async function updateCustomer(event) {
|
|
event.preventDefault();
|
|
|
|
const customerId = document.getElementById('editCustomerId').value;
|
|
const form = document.getElementById('editCustomerForm');
|
|
const formData = new FormData(form);
|
|
|
|
try {
|
|
// Ambil ID spesialisasi yang dicentang
|
|
const specializationIds = Array.from(formData.getAll('specializations[]')).map(id => parseInt(id));
|
|
|
|
// Gunakan endpoint yang benar dengan metode PUT
|
|
const response = await fetch(window.apiUrl(`customers/${customerId}/specializations`), {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
specialization_ids: specializationIds
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update customer');
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
if (responseData.status === 'success') {
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: 'Spesialisasi pelanggan berhasil diperbarui',
|
|
icon: 'success',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
closeEditModal();
|
|
window.location.reload();
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Gagal!',
|
|
text: 'Gagal memperbarui spesialisasi pelanggan. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeEditModal() {
|
|
document.getElementById('editCustomerModal').classList.add('hidden');
|
|
}
|
|
|
|
async function deleteCustomer(id) {
|
|
Swal.fire({
|
|
title: 'Anda yakin?',
|
|
text: "Pelanggan ini akan dihapus dan tidak dapat dikembalikan!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Ya, hapus!',
|
|
cancelButtonText: 'Batal'
|
|
}).then(async (result) => {
|
|
if (result.isConfirmed) {
|
|
try {
|
|
const response = await fetch(window.apiUrl(`customers/${id}`), {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(responseData.message || 'Failed to delete customer');
|
|
}
|
|
|
|
if (responseData.status === 'success') {
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: responseData.message || 'Pelanggan berhasil dihapus',
|
|
icon: 'success',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
window.location.reload();
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Gagal!',
|
|
text: error.message || 'Gagal menghapus pelanggan. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function showAddModal() {
|
|
try {
|
|
// Hide search section
|
|
document.getElementById('searchSection').classList.add('hidden');
|
|
|
|
// Get all specializations
|
|
const specializationsResponse = await fetch(window.apiUrl('specializations'), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!specializationsResponse.ok) {
|
|
throw new Error('Failed to fetch specializations');
|
|
}
|
|
|
|
const specializationsData = await specializationsResponse.json();
|
|
const specializations = specializationsData.data;
|
|
|
|
// Group specializations by category
|
|
const styleSpecializations = specializations.filter(s => s.category === 'Gaya Busana');
|
|
const serviceSpecializations = specializations.filter(s => s.category === 'Jenis Layanan');
|
|
const decorationSpecializations = specializations.filter(s => s.category === 'Hiasan Busana');
|
|
|
|
// Update specialization checkboxes
|
|
document.getElementById('addStyleSpecializations').innerHTML = styleSpecializations.map(spec => `
|
|
<div class="flex items-center p-2 hover:bg-white rounded-md transition-colors duration-200">
|
|
<input type="checkbox" id="add_spec_${spec.id}" name="preferred_specializations[]" value="${spec.id}"
|
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded transition-colors duration-200">
|
|
<label for="add_spec_${spec.id}" class="ml-3 block text-sm text-gray-700">${spec.name}</label>
|
|
</div>
|
|
`).join('');
|
|
|
|
document.getElementById('addServiceSpecializations').innerHTML = serviceSpecializations.map(spec => `
|
|
<div class="flex items-center p-2 hover:bg-white rounded-md transition-colors duration-200">
|
|
<input type="checkbox" id="add_spec_${spec.id}" name="preferred_specializations[]" value="${spec.id}"
|
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded transition-colors duration-200">
|
|
<label for="add_spec_${spec.id}" class="ml-3 block text-sm text-gray-700">${spec.name}</label>
|
|
</div>
|
|
`).join('');
|
|
|
|
document.getElementById('addDecorationSpecializations').innerHTML = decorationSpecializations.map(spec => `
|
|
<div class="flex items-center p-2 hover:bg-white rounded-md transition-colors duration-200">
|
|
<input type="checkbox" id="add_spec_${spec.id}" name="preferred_specializations[]" value="${spec.id}"
|
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded transition-colors duration-200">
|
|
<label for="add_spec_${spec.id}" class="ml-3 block text-sm text-gray-700">${spec.name}</label>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Show modal
|
|
document.getElementById('addCustomerModal').classList.remove('hidden');
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Gagal!',
|
|
text: 'Gagal memuat data spesialisasi. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeAddModal() {
|
|
// Show search section again
|
|
document.getElementById('searchSection').classList.remove('hidden');
|
|
document.getElementById('addCustomerModal').classList.add('hidden');
|
|
}
|
|
|
|
async function addCustomer(event) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
|
|
// Get selected specializations
|
|
const selectedSpecializations = Array.from(formData.getAll('preferred_specializations[]')).map(id => parseInt(id));
|
|
|
|
try {
|
|
const response = await fetch(window.apiUrl('pelanggan/register'), {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
name: formData.get('name'),
|
|
email: formData.get('email'),
|
|
password: formData.get('password'),
|
|
phone_number: formData.get('phone_number'),
|
|
address: formData.get('address'),
|
|
latitude: parseFloat(formData.get('latitude')),
|
|
longitude: parseFloat(formData.get('longitude')),
|
|
preferred_specializations: selectedSpecializations
|
|
})
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
console.log('API Response:', responseData); // Debug log
|
|
|
|
if (!response.ok) {
|
|
// Handle validation errors
|
|
if (response.status === 422) {
|
|
let errorMessage = 'Gagal menambahkan pelanggan:\n';
|
|
if (responseData.errors) {
|
|
Object.keys(responseData.errors).forEach(key => {
|
|
errorMessage += `- ${responseData.errors[key].join(', ')}\n`;
|
|
});
|
|
} else {
|
|
errorMessage += responseData.message || 'Terjadi kesalahan validasi';
|
|
}
|
|
|
|
Swal.fire({
|
|
title: 'Validasi Error',
|
|
text: errorMessage,
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
return;
|
|
}
|
|
throw new Error(responseData.message || 'Failed to add customer');
|
|
}
|
|
|
|
if (responseData.success) {
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: responseData.message || 'Pelanggan berhasil ditambahkan',
|
|
icon: 'success',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
closeAddModal();
|
|
window.location.reload();
|
|
}
|
|
});
|
|
} else {
|
|
throw new Error(responseData.message || 'Failed to add customer');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Gagal!',
|
|
text: error.message || 'Gagal menambahkan pelanggan. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update the add button click handler
|
|
document.getElementById('addCustomerBtn').addEventListener('click', showAddModal);
|
|
|
|
function getCurrentLocation() {
|
|
if (!navigator.geolocation) {
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: 'Geolocation tidak didukung oleh browser Anda',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
Swal.fire({
|
|
title: 'Mendapatkan lokasi...',
|
|
text: 'Mohon tunggu sebentar',
|
|
allowOutsideClick: false,
|
|
didOpen: () => {
|
|
Swal.showLoading();
|
|
}
|
|
});
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
(position) => {
|
|
const latitude = position.coords.latitude;
|
|
const longitude = position.coords.longitude;
|
|
|
|
// Update form fields
|
|
document.getElementById('latitude').value = latitude;
|
|
document.getElementById('longitude').value = longitude;
|
|
|
|
// Close loading
|
|
Swal.close();
|
|
|
|
// Show success message
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: 'Lokasi berhasil didapatkan',
|
|
icon: 'success',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
},
|
|
(error) => {
|
|
let errorMessage = 'Gagal mendapatkan lokasi: ';
|
|
switch (error.code) {
|
|
case error.PERMISSION_DENIED:
|
|
errorMessage += 'Akses lokasi ditolak';
|
|
break;
|
|
case error.POSITION_UNAVAILABLE:
|
|
errorMessage += 'Informasi lokasi tidak tersedia';
|
|
break;
|
|
case error.TIMEOUT:
|
|
errorMessage += 'Permintaan lokasi timeout';
|
|
break;
|
|
default:
|
|
errorMessage += 'Terjadi kesalahan yang tidak diketahui';
|
|
break;
|
|
}
|
|
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: errorMessage,
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
},
|
|
{
|
|
enableHighAccuracy: true,
|
|
timeout: 5000,
|
|
maximumAge: 0
|
|
}
|
|
);
|
|
}
|
|
</script>
|
|
@endpush
|
|
@endsection
|