diff --git a/app/Http/Controllers/Admin/EmployeeController.php b/app/Http/Controllers/Admin/EmployeeController.php index a576cea..828c3ec 100644 --- a/app/Http/Controllers/Admin/EmployeeController.php +++ b/app/Http/Controllers/Admin/EmployeeController.php @@ -3,13 +3,12 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Requests\StoreEmployeeRequest; +use App\Http\Requests\UpdateEmployeeRequest; use App\Models\Department; use App\Models\Employee; use App\Models\Position; -use App\Models\User; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Hash; -use Illuminate\Validation\Rule; use Inertia\Inertia; class EmployeeController extends Controller @@ -20,9 +19,7 @@ public function index(Request $request) if ($request->search) { $query->where('nip', 'like', '%' . $request->search . '%') - ->orWhereHas('user', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); - }); + ->orWhere('name', 'like', '%' . $request->search . '%'); } if ($request->department_id) { @@ -40,73 +37,32 @@ public function create() { return Inertia::render('admin/employees/create', [ 'departments' => Department::all(), - 'positions' => Position::all(), + 'positions' => Position::all(), ]); } - public function store(Request $request) + public function store(StoreEmployeeRequest $request) { - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|email|unique:users,email', - 'nip' => 'required|string|unique:employees,nip', - 'gender' => 'required|in:L,P', - 'place_of_birth' => 'nullable|string|max:255', - 'birth_date' => 'required|date', - 'address' => 'nullable|string', - 'phone_number' => 'nullable|string|max:20', - 'department_id' => 'required|exists:departments,id', - 'position_id' => 'required|exists:positions,id', - 'status' => 'required|in:PKWT,PKWTT,Magang', - 'join_date' => 'required|date', - ], [ - 'name.required' => 'Nama lengkap wajib diisi.', - 'name.max' => 'Nama lengkap maksimal 255 karakter.', - 'email.required' => 'Email wajib diisi.', - 'email.email' => 'Format email tidak valid.', - 'email.unique' => 'Email sudah digunakan oleh karyawan lain.', - 'nip.required' => 'NIP wajib diisi.', - 'nip.unique' => 'NIP sudah terdaftar.', - 'gender.required' => 'Jenis kelamin wajib dipilih.', - 'gender.in' => 'Jenis kelamin hanya boleh Laki-laki atau Perempuan.', - 'place_of_birth.max' => 'Tempat lahir maksimal 255 karakter.', - 'birth_date.required' => 'Tanggal lahir wajib diisi.', - 'birth_date.date' => 'Format tanggal lahir tidak valid.', - 'phone_number.max' => 'Nomor telepon maksimal 20 karakter.', - 'department_id.required' => 'Departemen wajib dipilih.', - 'department_id.exists' => 'Departemen tidak ditemukan.', - 'position_id.required' => 'Jabatan wajib dipilih.', - 'position_id.exists' => 'Jabatan tidak ditemukan.', - 'status.required' => 'Status kepegawaian wajib dipilih.', - 'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.', - 'join_date.required' => 'Tanggal masuk wajib diisi.', - 'join_date.date' => 'Format tanggal masuk tidak valid.', - ]); + $validated = $request->validated(); - $user = User::create([ - 'name' => $validated['name'], - 'email' => $validated['email'], - 'password' => Hash::make('password'), - 'role' => 'employee', + // Simpan hanya data karyawan ke tabel employees. + // Pembuatan akun User dilakukan secara terpisah oleh admin + // melalui menu Manajemen Pengguna. + Employee::create([ + 'nip' => $validated['nip'], + 'name' => $validated['name'], + 'email' => $validated['email'] ?? null, + 'gender' => $validated['gender'], + 'place_of_birth' => $validated['place_of_birth'] ?? null, + 'birth_date' => $validated['birth_date'], + 'address' => $validated['address'] ?? null, + 'phone_number' => $validated['phone_number'] ?? null, + 'department_id' => $validated['department_id'], + 'position_id' => $validated['position_id'], + 'status' => $validated['status'], + 'join_date' => $validated['join_date'], ]); - $employee = Employee::create([ - 'user_id' => $user->id, - 'nip' => $validated['nip'], - 'name' => $validated['name'], - 'gender' => $validated['gender'], - 'place_of_birth' => $validated['place_of_birth'], - 'birth_date' => $validated['birth_date'], - 'address' => $validated['address'], - 'phone_number' => $validated['phone_number'], - 'department_id' => $validated['department_id'], - 'position_id' => $validated['position_id'], - 'status' => $validated['status'], - 'join_date' => $validated['join_date'], - ]); - - $user->update(['employee_id' => $employee->id]); - return redirect()->route('admin.employees.index') ->with('success', 'Data karyawan berhasil ditambahkan.'); } @@ -116,65 +72,47 @@ public function edit(Employee $employee) $employee->load(['user', 'department', 'position']); return Inertia::render('admin/employees/edit', [ - 'employee' => $employee, + 'employee' => $employee, 'departments' => Department::all(), - 'positions' => Position::all(), + 'positions' => Position::all(), ]); } - public function update(Request $request, Employee $employee) + public function update(UpdateEmployeeRequest $request, Employee $employee) { - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'email' => ['required', 'email', Rule::unique('users')->ignore($employee->user_id)], - 'nip' => ['required', 'string', Rule::unique('employees')->ignore($employee->id)], - 'gender' => 'required|in:L,P', - 'place_of_birth' => 'nullable|string|max:255', - 'birth_date' => 'required|date', - 'address' => 'nullable|string', - 'phone_number' => 'nullable|string|max:20', - 'department_id' => 'required|exists:departments,id', - 'position_id' => 'required|exists:positions,id', - 'status' => 'required|in:PKWT,PKWTT,Magang', - 'join_date' => 'required|date', - ], [ - 'name.required' => 'Nama lengkap wajib diisi.', - 'name.max' => 'Nama lengkap maksimal 255 karakter.', - 'email.required' => 'Email wajib diisi.', - 'email.email' => 'Format email tidak valid.', - 'email.unique' => 'Email sudah digunakan oleh karyawan lain.', - 'nip.required' => 'NIP wajib diisi.', - 'nip.unique' => 'NIP sudah terdaftar.', - 'gender.required' => 'Jenis kelamin wajib dipilih.', - 'gender.in' => 'Jenis kelamin hanya boleh Laki-laki atau Perempuan.', - 'place_of_birth.max' => 'Tempat lahir maksimal 255 karakter.', - 'birth_date.required' => 'Tanggal lahir wajib diisi.', - 'birth_date.date' => 'Format tanggal lahir tidak valid.', - 'phone_number.max' => 'Nomor telepon maksimal 20 karakter.', - 'department_id.required' => 'Departemen wajib dipilih.', - 'department_id.exists' => 'Departemen tidak ditemukan.', - 'position_id.required' => 'Jabatan wajib dipilih.', - 'position_id.exists' => 'Jabatan tidak ditemukan.', - 'status.required' => 'Status kepegawaian wajib dipilih.', - 'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.', - 'join_date.required' => 'Tanggal masuk wajib diisi.', - 'join_date.date' => 'Format tanggal masuk tidak valid.', - ]); + $validated = $request->validated(); - $employee->user->update([ - 'name' => $validated['name'], - 'email' => $validated['email'], - ]); + // Sinkronisasi ke akun User jika karyawan sudah memiliki akun + if ($employee->user) { + $employee->user->update([ + 'name' => $validated['name'], + 'email' => $validated['email'] ?? $employee->user->email, + ]); + } - $employee->update($validated); + // Update data karyawan di tabel employees + $employee->update([ + 'name' => $validated['name'], + 'email' => $validated['email'] ?? null, + 'nip' => $validated['nip'], + 'gender' => $validated['gender'], + 'place_of_birth' => $validated['place_of_birth'] ?? null, + 'birth_date' => $validated['birth_date'], + 'address' => $validated['address'] ?? null, + 'phone_number' => $validated['phone_number'] ?? null, + 'department_id' => $validated['department_id'], + 'position_id' => $validated['position_id'], + 'status' => $validated['status'], + 'join_date' => $validated['join_date'], + ]); return redirect()->route('admin.employees.index') - ->with('success', 'Data karyawan diperbarui.'); + ->with('success', 'Data karyawan berhasil diperbarui.'); } public function destroy(Employee $employee) { - $employee->user->delete(); - return back()->with('success', 'Karyawan dihapus.'); + $employee->user?->delete(); + return back()->with('success', 'Karyawan berhasil dihapus.'); } } \ No newline at end of file diff --git a/app/Http/Requests/StoreEmployeeRequest.php b/app/Http/Requests/StoreEmployeeRequest.php index cce0c82..dbb6851 100644 --- a/app/Http/Requests/StoreEmployeeRequest.php +++ b/app/Http/Requests/StoreEmployeeRequest.php @@ -6,29 +6,77 @@ class StoreEmployeeRequest extends FormRequest { - /** - * Determine if the user is authorized to make this request. - */ public function authorize(): bool { return true; } - /** - * Get the validation rules that apply to the request. - * - * @return array|string> - */ public function rules(): array { return [ - 'name' => 'required|string|max:255', - 'email' => 'required|email|unique:users,email', - 'password' => 'required|string|min:8', - 'nip' => 'required|string|unique:employees,nip', - 'position' => 'required|string', - 'status' => 'required|in:PKWT,PKWTT', - 'join_date' => 'required|date', + // ── Identitas ──────────────────────────────────────────────── + 'name' => ['required', 'string', 'max:255', 'regex:/^[\pL\s]+$/u'], + 'email' => ['nullable', 'email:rfc,dns', 'max:255', 'unique:employees,email'], + + // ── Kepegawaian ────────────────────────────────────────────── + 'nip' => ['required', 'digits:6', 'unique:employees,nip'], + + // ── Data Pribadi ───────────────────────────────────────────── + 'gender' => ['required', 'in:L,P'], + 'place_of_birth'=> ['nullable', 'string', 'max:100'], + 'birth_date' => ['required', 'date', 'before:today'], + 'address' => ['nullable', 'string', 'max:500'], + 'phone_number' => ['nullable', 'digits_between:8,15'], + + // ── Jabatan & Departemen ───────────────────────────────────── + 'department_id' => ['required', 'exists:departments,id'], + 'position_id' => ['required', 'exists:positions,id'], + + // ── Status & Tanggal ───────────────────────────────────────── + 'status' => ['required', 'in:PKWT,PKWTT,Magang'], + 'join_date' => ['required', 'date'], + ]; + } + + public function messages(): array + { + return [ + // Nama + 'name.required' => 'Nama lengkap wajib diisi.', + 'name.max' => 'Nama lengkap maksimal 255 karakter.', + 'name.regex' => 'Nama lengkap hanya boleh berisi huruf dan spasi.', + + // Email + 'email.email' => 'Format email tidak valid.', + 'email.unique' => 'Email sudah digunakan oleh karyawan lain.', + 'email.max' => 'Email maksimal 255 karakter.', + + // NIP + 'nip.required' => 'NIP wajib diisi.', + 'nip.digits' => 'NIP harus tepat 6 digit angka.', + 'nip.unique' => 'NIP sudah terdaftar pada karyawan lain.', + + // Data Pribadi + 'gender.required' => 'Jenis kelamin wajib dipilih.', + 'gender.in' => 'Jenis kelamin tidak valid.', + 'place_of_birth.max' => 'Tempat lahir maksimal 100 karakter.', + 'birth_date.required' => 'Tanggal lahir wajib diisi.', + 'birth_date.date' => 'Format tanggal lahir tidak valid.', + 'birth_date.before' => 'Tanggal lahir harus sebelum hari ini.', + 'address.max' => 'Alamat maksimal 500 karakter.', + 'phone_number.digits_between' => 'Nomor HP harus berupa angka, minimal 8 dan maksimal 15 digit.', + + // Jabatan & Departemen + 'department_id.required'=> 'Departemen wajib dipilih.', + 'department_id.exists' => 'Departemen tidak ditemukan.', + 'position_id.required' => 'Jabatan wajib dipilih.', + 'position_id.exists' => 'Jabatan tidak ditemukan.', + + // Status & Tanggal + 'status.required' => 'Status kepegawaian wajib dipilih.', + 'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.', + 'join_date.required' => 'Tanggal masuk wajib diisi.', + 'join_date.date' => 'Format tanggal masuk tidak valid.', ]; } } diff --git a/app/Http/Requests/UpdateEmployeeRequest.php b/app/Http/Requests/UpdateEmployeeRequest.php index 40bcee6..8a522a6 100644 --- a/app/Http/Requests/UpdateEmployeeRequest.php +++ b/app/Http/Requests/UpdateEmployeeRequest.php @@ -3,36 +3,95 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class UpdateEmployeeRequest extends FormRequest { - /** - * Determine if the user is authorized to make this request. - */ public function authorize(): bool { return true; } - /** - * Get the validation rules that apply to the request. - * - * @return array|string> - */ public function rules(): array { - $employeeId = $this->route('employee')->id; - $userId = $this->route('employee')->user_id; + $employee = $this->route('employee'); + $employeeId = $employee->id; + $userId = $employee->user_id; return [ - 'name' => 'required|string|max:255', - 'email' => 'required|email|unique:users,email,' . $userId, - 'nip' => 'required|string|unique:employees,nip,' . $employeeId, - 'position' => 'required|string', - 'status' => 'required|in:PKWT,PKWTT', - 'join_date' => 'required|date', - // Password opsional saat update - 'password' => 'nullable|string|min:8', + // ── Identitas ──────────────────────────────────────────────── + 'name' => ['required', 'string', 'max:255', 'regex:/^[\pL\s]+$/u'], + 'email' => [ + 'nullable', + 'email:rfc,dns', + 'max:255', + // Abaikan email milik employee ini sendiri + Rule::unique('employees', 'email')->ignore($employeeId), + ], + + // ── Kepegawaian ────────────────────────────────────────────── + 'nip' => [ + 'required', + 'digits:6', + Rule::unique('employees', 'nip')->ignore($employeeId), + ], + + // ── Data Pribadi ───────────────────────────────────────────── + 'gender' => ['required', 'in:L,P'], + 'place_of_birth'=> ['nullable', 'string', 'max:100'], + 'birth_date' => ['required', 'date', 'before:today'], + 'address' => ['nullable', 'string', 'max:500'], + 'phone_number' => ['nullable', 'digits_between:8,15'], + + // ── Jabatan & Departemen ───────────────────────────────────── + 'department_id' => ['required', 'exists:departments,id'], + 'position_id' => ['required', 'exists:positions,id'], + + // ── Status & Tanggal ───────────────────────────────────────── + 'status' => ['required', 'in:PKWT,PKWTT,Magang'], + 'join_date' => ['required', 'date'], + ]; + } + + public function messages(): array + { + return [ + // Nama + 'name.required' => 'Nama lengkap wajib diisi.', + 'name.max' => 'Nama lengkap maksimal 255 karakter.', + 'name.regex' => 'Nama lengkap hanya boleh berisi huruf dan spasi.', + + // Email + 'email.email' => 'Format email tidak valid.', + 'email.unique' => 'Email sudah digunakan oleh karyawan lain.', + 'email.max' => 'Email maksimal 255 karakter.', + + // NIP + 'nip.required' => 'NIP wajib diisi.', + 'nip.digits' => 'NIP harus tepat 6 digit angka.', + 'nip.unique' => 'NIP sudah terdaftar pada karyawan lain.', + + // Data Pribadi + 'gender.required' => 'Jenis kelamin wajib dipilih.', + 'gender.in' => 'Jenis kelamin tidak valid.', + 'place_of_birth.max' => 'Tempat lahir maksimal 100 karakter.', + 'birth_date.required' => 'Tanggal lahir wajib diisi.', + 'birth_date.date' => 'Format tanggal lahir tidak valid.', + 'birth_date.before' => 'Tanggal lahir harus sebelum hari ini.', + 'address.max' => 'Alamat maksimal 500 karakter.', + 'phone_number.digits_between' => 'Nomor HP harus berupa angka, minimal 8 dan maksimal 15 digit.', + + // Jabatan & Departemen + 'department_id.required'=> 'Departemen wajib dipilih.', + 'department_id.exists' => 'Departemen tidak ditemukan.', + 'position_id.required' => 'Jabatan wajib dipilih.', + 'position_id.exists' => 'Jabatan tidak ditemukan.', + + // Status & Tanggal + 'status.required' => 'Status kepegawaian wajib dipilih.', + 'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.', + 'join_date.required' => 'Tanggal masuk wajib diisi.', + 'join_date.date' => 'Format tanggal masuk tidak valid.', ]; } } diff --git a/app/Models/Employee.php b/app/Models/Employee.php index 7e38cb0..4633b33 100644 --- a/app/Models/Employee.php +++ b/app/Models/Employee.php @@ -10,9 +10,10 @@ class Employee extends Model use HasFactory; protected $fillable = [ - 'user_id', + 'user_id', // diisi oleh UserController saat akun digenerate 'nip', 'name', + 'email', 'gender', 'place_of_birth', 'birth_date', @@ -21,13 +22,13 @@ class Employee extends Model 'department_id', 'position_id', 'status', - 'join_date' + 'join_date', ]; - // Relasi ke User + // Relasi ke User (nullable: karyawan boleh belum punya akun) public function user() { - return $this->belongsTo(User::class); + return $this->belongsTo(User::class)->withDefault(); } // Relasi ke Departemen diff --git a/database/migrations/2026_05_10_150200_make_user_id_nullable_on_employees_table.php b/database/migrations/2026_05_10_150200_make_user_id_nullable_on_employees_table.php new file mode 100644 index 0000000..7a0bad1 --- /dev/null +++ b/database/migrations/2026_05_10_150200_make_user_id_nullable_on_employees_table.php @@ -0,0 +1,53 @@ +dropForeign(['user_id']); + + // Ubah kolom menjadi nullable dan pasang kembali foreign key + // dengan nullOnDelete agar baris employee tidak ikut terhapus + // jika akun User-nya dihapus. + $table->foreignId('user_id') + ->nullable() + ->change(); + + $table->foreign('user_id') + ->references('id') + ->on('users') + ->nullOnDelete(); + }); + } + + /** + * Rollback: kembalikan user_id ke NOT NULL dengan cascade delete. + */ + public function down(): void + { + Schema::table('employees', function (Blueprint $table) { + $table->dropForeign(['user_id']); + + $table->foreignId('user_id') + ->nullable(false) + ->change(); + + $table->foreign('user_id') + ->references('id') + ->on('users') + ->onDelete('cascade'); + }); + } +}; diff --git a/database/migrations/2026_05_10_151400_add_email_to_employees_table.php b/database/migrations/2026_05_10_151400_add_email_to_employees_table.php new file mode 100644 index 0000000..9521159 --- /dev/null +++ b/database/migrations/2026_05_10_151400_add_email_to_employees_table.php @@ -0,0 +1,30 @@ +string('email')->nullable()->after('name'); + }); + } + + public function down(): void + { + Schema::table('employees', function (Blueprint $table) { + $table->dropColumn('email'); + }); + } +}; diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx index 0c31215..f50af8f 100644 --- a/resources/js/components/app-logo-icon.tsx +++ b/resources/js/components/app-logo-icon.tsx @@ -2,12 +2,6 @@ import type { SVGAttributes } from 'react'; export default function AppLogoIcon(props: SVGAttributes) { return ( - - - + Logo ); } diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx index 7665bc5..e5fad47 100644 --- a/resources/js/components/app-logo.tsx +++ b/resources/js/components/app-logo.tsx @@ -3,12 +3,12 @@ import AppLogoIcon from './app-logo-icon'; export default function AppLogo() { return ( <> -
- +
+
- SDM App + HRIS App
diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 14f015a..5b67442 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -52,11 +52,6 @@ export function AppSidebar() { href: '/admin/payrolls', icon: Wallet, }, - { - title: 'Manajemen Absensi', - href: '/admin/attendances', - icon: CalendarClock, - }, { title: 'Manajemen Pengguna', href: '/admin/users', diff --git a/resources/js/layouts/employee-layout.tsx b/resources/js/layouts/employee-layout.tsx index a85eae4..aeca0a3 100644 --- a/resources/js/layouts/employee-layout.tsx +++ b/resources/js/layouts/employee-layout.tsx @@ -37,9 +37,6 @@ export default function EmployeeLayout({ children, title }: EmployeeLayoutProps)
Halo, {user.name}
- - - diff --git a/resources/js/pages/admin/employees/create.tsx b/resources/js/pages/admin/employees/create.tsx index 9ed8827..6496c37 100644 --- a/resources/js/pages/admin/employees/create.tsx +++ b/resources/js/pages/admin/employees/create.tsx @@ -76,7 +76,8 @@ export default function Create({ departments, positions }: PageProps) {
setData('email', e.target.value)} /> diff --git a/resources/js/pages/admin/employees/edit.tsx b/resources/js/pages/admin/employees/edit.tsx index e66d635..d04f44d 100644 --- a/resources/js/pages/admin/employees/edit.tsx +++ b/resources/js/pages/admin/employees/edit.tsx @@ -11,6 +11,7 @@ import AppLayout from '@/layouts/app-layout'; interface Employee { id: number; + name: string; nip: string; gender: string; birth_date: string; @@ -24,7 +25,7 @@ interface Employee { user: { name: string; email: string; - }; + } | null; } interface PageProps { @@ -37,8 +38,8 @@ interface PageProps { export default function Edit({ employee, departments, positions }: PageProps) { // Inisialisasi State const { data, setData, put, processing, errors } = useForm({ - name: employee.user.name, - email: employee.user.email, + name: employee.name, + email: employee.user?.email ?? '', nip: employee.nip, gender: employee.gender, birth_date: employee.birth_date, @@ -58,7 +59,7 @@ export default function Edit({ employee, departments, positions }: PageProps) { return ( - +
- + Form Akun Baru diff --git a/resources/js/pages/employee/attendances/index.tsx b/resources/js/pages/employee/attendances/index.tsx index ba11adf..5587cc2 100644 --- a/resources/js/pages/employee/attendances/index.tsx +++ b/resources/js/pages/employee/attendances/index.tsx @@ -10,6 +10,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ import EmployeeLayout from '@/layouts/employee-layout'; import { AttendanceMapModal } from '@/components/AttendanceMapModal'; import { MapPin, AlertCircle, RefreshCw, CheckCircle2, LogOut } from 'lucide-react'; +import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; +import 'leaflet/dist/leaflet.css'; interface Attendance { id: number; @@ -146,37 +148,67 @@ export default function AttendanceIndex({ attendances, todayAttendance }: PagePr
-
- {isLoadingLocation ? ( - - ) : locationError ? ( - - ) : ( - - )} -
-
-

- {isLoadingLocation ? 'Mencari lokasi Anda...' : - locationError ? 'Gagal Akses Lokasi' : - 'Lokasi Ditemukan'} -

-

- {isLoadingLocation ? 'Harap tunggu, pastikan GPS aktif.' : - locationError ? locationError : - `Koordinat: ${coordinates?.lat.toFixed(6)}, ${coordinates?.lng.toFixed(6)}`} -

-
- {locationError && ( - +
+ {isLoadingLocation ? ( + + ) : locationError ? ( + + ) : ( + )} +
+
+

+ {isLoadingLocation ? 'Mencari lokasi Anda...' : + locationError ? 'Gagal Akses Lokasi' : + 'Lokasi Ditemukan'} +

+

+ {isLoadingLocation ? 'Harap tunggu, pastikan GPS aktif.' : + locationError ? locationError : + `Koordinat: ${coordinates?.lat.toFixed(6)}, ${coordinates?.lng.toFixed(6)}`} +

+
+ {locationError && ( + + )} +
+ + {/* Peta Preview - Hanya muncul jika koordinat berhasil didapatkan */} + {coordinates && !isLoadingLocation && !locationError && ( +
+ + + + +
+

📍 Lokasi Anda Saat Ini

+

+ Lat: {coordinates.lat.toFixed(6)}
+ Lng: {coordinates.lng.toFixed(6)} +

+
+
+
+
+
+ )}
diff --git a/vite.config.ts b/vite.config.ts index a1f1c9e..3971236 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,9 @@ import laravel from 'laravel-vite-plugin'; import { defineConfig } from 'vite'; export default defineConfig({ + server: { + host: '127.0.0.1', + }, plugins: [ laravel({ input: ['resources/css/app.css', 'resources/js/app.tsx'],