977 lines
48 KiB
PHP
977 lines
48 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', 'Penjahit')
|
|
|
|
@section('content')
|
|
<div class="p-6" x-data="tailorFilters">
|
|
<!-- Header -->
|
|
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
|
|
<h2 class="text-2xl font-semibold text-gray-800">Daftar Penjahit</h2>
|
|
|
|
<!-- Filter Section -->
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<!-- Search -->
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
x-model="searchQuery"
|
|
@input="filterTailors()"
|
|
placeholder="Cari penjahit..."
|
|
class="w-full md:w-64 pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<svg class="w-5 h-5 text-gray-400 absolute left-3 top-2.5" 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>
|
|
|
|
<!-- Specialization Filter Dropdown -->
|
|
<div class="relative" x-data="{ open: false }">
|
|
<button
|
|
@click="open = !open"
|
|
class="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<span x-text="selectedFilters.length ? `${selectedFilters.length} Filter dipilih` : 'Filter Spesialisasi'"></span>
|
|
<svg class="w-5 h-5 text-gray-400" :class="{'rotate-180': open}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Dropdown Menu -->
|
|
<div
|
|
x-show="open"
|
|
@click.away="open = false"
|
|
x-transition:enter="transition ease-out duration-100"
|
|
x-transition:enter-start="transform opacity-0 scale-95"
|
|
x-transition:enter-end="transform opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-75"
|
|
x-transition:leave-start="transform opacity-100 scale-100"
|
|
x-transition:leave-end="transform opacity-0 scale-95"
|
|
class="absolute right-0 mt-2 w-72 bg-white rounded-lg shadow-lg z-50"
|
|
>
|
|
<div class="p-4">
|
|
<!-- Categories -->
|
|
<template x-for="(specs, category) in specializations" :key="category">
|
|
<div class="mb-4">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-2" x-text="category"></h4>
|
|
<div class="space-y-2">
|
|
<template x-for="spec in specs" :key="spec">
|
|
<label class="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
:value="spec"
|
|
x-model="selectedFilters"
|
|
@change="filterTailors()"
|
|
class="w-4 h-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
|
|
>
|
|
<span class="ml-2 text-sm text-gray-600" x-text="spec"></span>
|
|
</label>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Actions -->
|
|
<div class="pt-3 border-t border-gray-200">
|
|
<button
|
|
@click="clearFilters()"
|
|
class="text-sm text-gray-600 hover:text-blue-600 flex items-center gap-1"
|
|
>
|
|
<svg class="w-4 h-4" 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>
|
|
Hapus semua filter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button onclick="showAddModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2">
|
|
<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="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
</svg>
|
|
Tambah Penjahit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if(session('error'))
|
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">
|
|
<span class="block sm:inline">{{ session('error') }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('success'))
|
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
|
|
<span class="block sm:inline">{{ session('success') }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Tailors Grid -->
|
|
<div id="tailorsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
@forelse($tailors as $tailor)
|
|
<div class="tailor-card bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
|
|
data-specializations='@json($tailor['specializations'])'
|
|
data-name="{{ strtolower($tailor['name']) }}">
|
|
<!-- Tailor Header -->
|
|
<div class="p-4 border-b">
|
|
<div class="flex items-center gap-4">
|
|
<div class="w-16 h-16 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
|
|
@if(isset($tailor['profile']['photo']))
|
|
<img src="{{ env('API_URL') . $tailor['profile']['photo'] }}" alt="{{ $tailor['name'] }}" class="w-full h-full object-cover">
|
|
@else
|
|
<span class="text-2xl text-gray-600">{{ substr($tailor['name'], 0, 2) }}</span>
|
|
@endif
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-800">{{ $tailor['name'] }}</h3>
|
|
<div class="flex items-center gap-2 mt-1">
|
|
<div class="flex items-center">
|
|
@for($i = 1; $i <= 5; $i++)
|
|
@if($i <= $tailor['ratings']['average_rating'])
|
|
<svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
|
</svg>
|
|
@else
|
|
<svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
|
</svg>
|
|
@endif
|
|
@endfor
|
|
<span class="ml-1 text-sm text-gray-600">({{ $tailor['ratings']['total_ratings'] }})</span>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-gray-600 mt-1">{{ $tailor['phone'] }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shop Info -->
|
|
<div class="p-4 border-b">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-2">Informasi Toko</h4>
|
|
<p class="text-sm text-gray-600">{{ $tailor['profile']['shop_name'] }}</p>
|
|
<p class="text-sm text-gray-600">{{ $tailor['profile']['address'] }}</p>
|
|
<p class="text-sm text-gray-600 mt-2">{{ $tailor['profile']['description'] }}</p>
|
|
</div>
|
|
|
|
<!-- Specializations -->
|
|
@if(count($tailor['specializations']) > 0)
|
|
<div class="p-4 border-b">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-2">Spesialisasi</h4>
|
|
<div class="flex flex-wrap gap-2">
|
|
@foreach($tailor['specializations'] as $specialization)
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
{{ $specialization['name'] }}
|
|
</span>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Latest Review -->
|
|
@if(isset($tailor['ratings']['reviews']) && count($tailor['ratings']['reviews']) > 0)
|
|
<div class="p-4 border-b">
|
|
<h4 class="text-sm font-medium text-gray-700 mb-2">Review Terbaru</h4>
|
|
<div class="text-sm text-gray-600">
|
|
<p class="italic">"{!! nl2br(e($tailor['ratings']['reviews'][0]['comment'])) !!}"</p>
|
|
<p class="text-xs text-gray-500 mt-1">{{ \Carbon\Carbon::parse($tailor['ratings']['reviews'][0]['created_at'])->diffForHumans() }}</p>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Actions -->
|
|
<div class="p-4 bg-gray-50 flex justify-end gap-2">
|
|
<button onclick="openGallery({{ $tailor['id'] }})" class="text-blue-600 hover:text-blue-800" title="Lihat Galeri">
|
|
<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="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
</svg>
|
|
</button>
|
|
<button onclick="showEditModal({{ $tailor['id'] }})" class="text-gray-600 hover:text-gray-800" title="Edit">
|
|
<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.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
|
</svg>
|
|
</button>
|
|
<button onclick="deleteTailor({{ $tailor['id'] }})" class="text-red-600 hover:text-red-800" title="Hapus">
|
|
<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"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="col-span-3 text-center py-8">
|
|
<p class="text-gray-500">Tidak ada data penjahit</p>
|
|
</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gallery Modal -->
|
|
<div id="galleryModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="min-h-screen px-4 text-center">
|
|
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
|
</div>
|
|
|
|
<!-- Modal panel -->
|
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="w-full">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
|
Galeri Karya
|
|
</h3>
|
|
<button type="button" onclick="closeGallery()" class="text-gray-400 hover:text-gray-500">
|
|
<span class="sr-only">Close</span>
|
|
<svg class="h-6 w-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" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div id="galleryContent" class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
<!-- Gallery items will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Tailor Modal -->
|
|
<div id="addTailorModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden z-50">
|
|
<div class="min-h-screen px-4 text-center">
|
|
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
|
</div>
|
|
|
|
<!-- Modal panel -->
|
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="w-full">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
Tambah Penjahit Baru
|
|
</h3>
|
|
<button type="button" onclick="closeAddModal()" class="text-gray-400 hover:text-gray-500">
|
|
<span class="sr-only">Close</span>
|
|
<svg class="h-6 w-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" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<form id="addTailorForm" onsubmit="addTailor(event)">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="name" class="block text-sm font-medium text-gray-700">Nama</label>
|
|
<input type="text" name="name" id="name" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
|
<input type="email" name="email" id="email" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
|
|
<input type="password" name="password" id="password" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="password_confirmation" class="block text-sm font-medium text-gray-700">Konfirmasi Password</label>
|
|
<input type="password" name="password_confirmation" id="password_confirmation" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-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" name="phone_number" id="phone_number" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="address" class="block text-sm font-medium text-gray-700">Alamat</label>
|
|
<input type="text" name="address" id="address" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<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" name="latitude" id="latitude" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-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-blue-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" name="longitude" id="longitude" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label for="shop_description" class="block text-sm font-medium text-gray-700">Deskripsi Toko</label>
|
|
<textarea name="shop_description" id="shop_description" rows="3" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"></textarea>
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Spesialisasi</label>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4" id="specializationsContainer">
|
|
<!-- Specializations will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
<button type="submit"
|
|
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
|
|
Simpan
|
|
</button>
|
|
<button type="button" onclick="closeAddModal()"
|
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:w-auto sm:text-sm">
|
|
Batal
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Tailor Modal -->
|
|
<div id="editTailorModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden z-50">
|
|
<div class="min-h-screen px-4 text-center">
|
|
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
|
</div>
|
|
|
|
<!-- Modal panel -->
|
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="w-full">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
Edit Penjahit
|
|
</h3>
|
|
<button type="button" onclick="closeEditModal()" class="text-gray-400 hover:text-gray-500">
|
|
<span class="sr-only">Close</span>
|
|
<svg class="h-6 w-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" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<form id="editTailorForm" onsubmit="updateTailor(event)">
|
|
<input type="hidden" id="edit_tailor_id">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="edit_name" class="block text-sm font-medium text-gray-700">Nama</label>
|
|
<input type="text" name="name" id="edit_name" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="edit_email" class="block text-sm font-medium text-gray-700">Email</label>
|
|
<input type="email" name="email" id="edit_email" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="edit_phone_number" class="block text-sm font-medium text-gray-700">Nomor Telepon</label>
|
|
<input type="text" name="phone_number" id="edit_phone_number" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div>
|
|
<label for="edit_address" class="block text-sm font-medium text-gray-700">Alamat</label>
|
|
<input type="text" name="address" id="edit_address" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label for="edit_shop_description" class="block text-sm font-medium text-gray-700">Deskripsi Toko</label>
|
|
<textarea name="shop_description" id="edit_shop_description" rows="3" required
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"></textarea>
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Spesialisasi</label>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4" id="editSpecializationsContainer">
|
|
<!-- Specializations will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
<button type="submit"
|
|
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
|
|
Simpan
|
|
</button>
|
|
<button type="button" onclick="closeEditModal()"
|
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:w-auto sm:text-sm">
|
|
Batal
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@endsection
|
|
|
|
@push('styles')
|
|
<link href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css" rel="stylesheet">
|
|
@endpush
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<script>
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('tailorFilters', () => ({
|
|
searchQuery: '',
|
|
selectedFilters: [],
|
|
specializations: {
|
|
'Gaya Busana': ['Casual', 'Formal', 'Tradisional', 'Modern'],
|
|
'Jenis Layanan': ['Jahit Baru', 'Perbaikan', 'Design/Custom'],
|
|
'Hiasan Busana': ['Border', 'Payet', 'Sulam']
|
|
},
|
|
|
|
filterTailors() {
|
|
const cards = document.querySelectorAll('.tailor-card');
|
|
const searchLower = this.searchQuery.toLowerCase();
|
|
|
|
cards.forEach(card => {
|
|
const specializations = JSON.parse(card.dataset.specializations);
|
|
const name = card.dataset.name;
|
|
|
|
const matchesSearch = name.includes(searchLower);
|
|
const matchesFilters = this.selectedFilters.length === 0 ||
|
|
specializations.some(spec => this.selectedFilters.includes(spec.name));
|
|
|
|
card.style.display = (matchesSearch && matchesFilters) ? '' : 'none';
|
|
});
|
|
|
|
this.updateNoResults();
|
|
},
|
|
|
|
clearFilters() {
|
|
this.selectedFilters = [];
|
|
this.searchQuery = '';
|
|
this.filterTailors();
|
|
},
|
|
|
|
updateNoResults() {
|
|
const visibleCards = document.querySelectorAll('.tailor-card[style=""]').length;
|
|
const noResultsDiv = document.querySelector('.no-results');
|
|
|
|
if (visibleCards === 0) {
|
|
if (!noResultsDiv) {
|
|
const grid = document.getElementById('tailorsGrid');
|
|
const div = document.createElement('div');
|
|
div.className = 'no-results col-span-3 text-center py-8';
|
|
div.innerHTML = '<p class="text-gray-500">Tidak ada penjahit yang sesuai dengan filter</p>';
|
|
grid.appendChild(div);
|
|
}
|
|
} else if (noResultsDiv) {
|
|
noResultsDiv.remove();
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
|
|
function openGallery(tailorId) {
|
|
const modal = document.getElementById('galleryModal');
|
|
const content = document.getElementById('galleryContent');
|
|
|
|
// Show modal and loading state
|
|
modal.classList.remove('hidden');
|
|
content.innerHTML = '<div class="col-span-3 text-center py-4">Loading...</div>';
|
|
|
|
// Get token from session (passed from backend)
|
|
const token = '{{ session('api_token') }}';
|
|
const tokenType = '{{ session('token_type') }}';
|
|
|
|
// Fetch gallery data
|
|
fetch(window.apiUrl(`api/tailors/${tailorId}/gallery`), {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `${tokenType} ${token}`
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data.length > 0) {
|
|
content.innerHTML = data.data.map(item => `
|
|
<div class="relative group">
|
|
<img src="${window.assetUrl(item.photo)}"
|
|
alt="${item.title}"
|
|
class="w-full aspect-square object-cover rounded-lg">
|
|
<div class="absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col justify-end p-4 rounded-lg">
|
|
<h4 class="text-white font-medium">${item.title}</h4>
|
|
<p class="text-white text-sm">${item.description || ''}</p>
|
|
<span class="text-white text-xs mt-1">${item.category}</span>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
content.innerHTML = '<div class="col-span-3 text-center py-4">Tidak ada foto dalam galeri</div>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
content.innerHTML = '<div class="col-span-3 text-center py-4 text-red-600">Gagal memuat galeri</div>';
|
|
});
|
|
}
|
|
|
|
function closeGallery() {
|
|
const modal = document.getElementById('galleryModal');
|
|
modal.classList.add('hidden');
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
document.getElementById('galleryModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
closeGallery();
|
|
}
|
|
});
|
|
|
|
async function showAddModal() {
|
|
try {
|
|
// Get all specializations
|
|
const response = await fetch(window.apiUrl('api/specializations'), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch specializations');
|
|
}
|
|
|
|
const data = await response.json();
|
|
const specializations = data.data;
|
|
|
|
// Group specializations by category
|
|
const groupedSpecializations = specializations.reduce((acc, spec) => {
|
|
if (!acc[spec.category]) {
|
|
acc[spec.category] = [];
|
|
}
|
|
acc[spec.category].push(spec);
|
|
return acc;
|
|
}, {});
|
|
|
|
// Render specialization checkboxes
|
|
const container = document.getElementById('specializationsContainer');
|
|
container.innerHTML = Object.entries(groupedSpecializations).map(([category, specs]) => `
|
|
<div class="space-y-2">
|
|
<h4 class="text-sm font-medium text-gray-700">${category}</h4>
|
|
<div class="space-y-2">
|
|
${specs.map(spec => `
|
|
<label class="flex items-center">
|
|
<input type="checkbox" name="specializations[]" value="${spec.id}"
|
|
class="h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
|
|
<span class="ml-2 text-sm text-gray-700">${spec.name}</span>
|
|
</label>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Show modal
|
|
document.getElementById('addTailorModal').classList.remove('hidden');
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: 'Gagal memuat data spesialisasi',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeAddModal() {
|
|
document.getElementById('addTailorModal').classList.add('hidden');
|
|
}
|
|
|
|
async function addTailor(event) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
|
|
// Get selected specializations
|
|
const selectedSpecializations = Array.from(formData.getAll('specializations[]')).map(id => parseInt(id));
|
|
|
|
try {
|
|
const response = await fetch(window.apiUrl('api/penjahit/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'),
|
|
password_confirmation: formData.get('password_confirmation'),
|
|
phone_number: formData.get('phone_number'),
|
|
address: formData.get('address'),
|
|
latitude: parseFloat(formData.get('latitude')),
|
|
longitude: parseFloat(formData.get('longitude')),
|
|
shop_description: formData.get('shop_description'),
|
|
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 penjahit:\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'
|
|
});
|
|
return;
|
|
}
|
|
throw new Error(responseData.message || 'Failed to add tailor');
|
|
}
|
|
|
|
if (responseData.success) {
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: responseData.message || 'Penjahit berhasil ditambahkan',
|
|
icon: 'success',
|
|
confirmButtonText: 'OK'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
closeAddModal();
|
|
window.location.reload();
|
|
}
|
|
});
|
|
} else {
|
|
throw new Error(responseData.message || 'Failed to add tailor');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Gagal!',
|
|
text: error.message || 'Gagal menambahkan penjahit. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
}
|
|
|
|
function getCurrentLocation() {
|
|
if (!navigator.geolocation) {
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: 'Geolocation tidak didukung oleh browser Anda',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
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'
|
|
});
|
|
},
|
|
(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'
|
|
});
|
|
},
|
|
{
|
|
enableHighAccuracy: true,
|
|
timeout: 5000,
|
|
maximumAge: 0
|
|
}
|
|
);
|
|
}
|
|
|
|
async function deleteTailor(id) {
|
|
Swal.fire({
|
|
title: 'Apakah Anda yakin?',
|
|
text: "Data penjahit akan dihapus secara permanen!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#4F46E5',
|
|
cancelButtonColor: '#6B7280',
|
|
confirmButtonText: 'Ya, hapus!',
|
|
cancelButtonText: 'Batal'
|
|
}).then(async (result) => {
|
|
if (result.isConfirmed) {
|
|
try {
|
|
const response = await fetch(window.apiUrl(`api/tailors/${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 || 'Gagal menghapus penjahit');
|
|
}
|
|
|
|
if (responseData.status === 'success') {
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: responseData.message || 'Penjahit berhasil dihapus',
|
|
icon: 'success',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
window.location.reload();
|
|
}
|
|
});
|
|
} else {
|
|
throw new Error(responseData.message || 'Gagal menghapus penjahit');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Gagal!',
|
|
text: error.message || 'Gagal menghapus penjahit. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function showEditModal(id) {
|
|
try {
|
|
// Get tailor data
|
|
const response = await fetch(window.apiUrl(`api/tailors/${id}`), {
|
|
headers: {
|
|
'Authorization': '{{ session('token_type') }} {{ session('api_token') }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch tailor data');
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
console.log('Tailor Data:', responseData); // Debug log
|
|
|
|
if (!responseData.data) {
|
|
throw new Error('Invalid response format');
|
|
}
|
|
|
|
const tailor = responseData.data;
|
|
|
|
// Get all specializations
|
|
const specializationsResponse = await fetch(window.apiUrl('api/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;
|
|
|
|
// Fill form with tailor data
|
|
document.getElementById('edit_tailor_id').value = tailor.id;
|
|
document.getElementById('edit_name').value = tailor.name || '';
|
|
document.getElementById('edit_email').value = tailor.email || '';
|
|
document.getElementById('edit_phone_number').value = tailor.phone_number || tailor.phone || '';
|
|
document.getElementById('edit_address').value = tailor.address || (tailor.profile ? tailor.profile.address : '') || '';
|
|
document.getElementById('edit_shop_description').value = tailor.shop_description || (tailor.profile ? tailor.profile.description : '') || '';
|
|
|
|
// Group specializations by category
|
|
const groupedSpecializations = specializations.reduce((acc, spec) => {
|
|
if (!acc[spec.category]) {
|
|
acc[spec.category] = [];
|
|
}
|
|
acc[spec.category].push(spec);
|
|
return acc;
|
|
}, {});
|
|
|
|
// Get current specialization IDs
|
|
const currentSpecializations = tailor.specializations || tailor.specialization_ids || [];
|
|
const currentSpecializationIds = currentSpecializations.map(spec =>
|
|
typeof spec === 'object' ? spec.id : spec
|
|
);
|
|
|
|
// Render specialization checkboxes
|
|
const container = document.getElementById('editSpecializationsContainer');
|
|
container.innerHTML = Object.entries(groupedSpecializations).map(([category, specs]) => `
|
|
<div class="space-y-2">
|
|
<h4 class="text-sm font-medium text-gray-700">${category}</h4>
|
|
<div class="space-y-2">
|
|
${specs.map(spec => `
|
|
<label class="flex items-center">
|
|
<input type="checkbox" name="specializations[]" value="${spec.id}"
|
|
${currentSpecializationIds.includes(spec.id) ? 'checked' : ''}
|
|
class="h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
|
|
<span class="ml-2 text-sm text-gray-700">${spec.name}</span>
|
|
</label>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Show modal
|
|
document.getElementById('editTailorModal').classList.remove('hidden');
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: 'Gagal memuat data penjahit: ' + error.message,
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeEditModal() {
|
|
document.getElementById('editTailorModal').classList.add('hidden');
|
|
}
|
|
|
|
async function updateTailor(event) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
const tailorId = document.getElementById('edit_tailor_id').value;
|
|
|
|
// Get selected specializations
|
|
const selectedSpecializations = Array.from(formData.getAll('specializations[]')).map(id => parseInt(id));
|
|
|
|
try {
|
|
const response = await fetch(window.apiUrl(`api/tailors/${tailorId}`), {
|
|
method: 'PUT',
|
|
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'),
|
|
phone_number: formData.get('phone_number'),
|
|
address: formData.get('address'),
|
|
shop_description: formData.get('shop_description'),
|
|
specialization_ids: selectedSpecializations
|
|
})
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(responseData.message || 'Gagal mengupdate penjahit');
|
|
}
|
|
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: 'Data penjahit 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: error.message || 'Gagal mengupdate penjahit. Silakan coba lagi nanti.',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK',
|
|
confirmButtonColor: '#4F46E5'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update the edit button to use showEditModal
|
|
document.querySelectorAll('[data-edit-tailor]').forEach(button => {
|
|
button.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const tailorId = button.dataset.editTailor;
|
|
showEditModal(tailorId);
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|