183 lines
4.9 KiB
Svelte
183 lines
4.9 KiB
Svelte
|
|
<script>
|
||
|
|
import { onMount } from 'svelte';
|
||
|
|
import { fetchGeographicalAreas, fetchProvinces, fetchCities } from '$lib/api/geography.js';
|
||
|
|
import { success as toastSuccess, error as toastError } from '$lib/utils/toast.js';
|
||
|
|
import DataTable from '$lib/components/DataTable.svelte';
|
||
|
|
import SelectDropdown from '$lib/components/SelectDropdown.svelte';
|
||
|
|
import { ArrowLeft, MapPin, Globe, Building2 } from 'lucide-svelte';
|
||
|
|
|
||
|
|
let loading = $state(false);
|
||
|
|
let activeTab = $state('provinces');
|
||
|
|
let areas = $state([]);
|
||
|
|
let provinces = $state([]);
|
||
|
|
let cities = $state([]);
|
||
|
|
let selectedProvince = $state('');
|
||
|
|
|
||
|
|
const areaColumns = [
|
||
|
|
{ key: 'AreaGeoID', label: 'ID', class: 'font-medium' },
|
||
|
|
{ key: 'AreaCode', label: 'Code' },
|
||
|
|
{ key: 'AreaName', label: 'Name' },
|
||
|
|
{ key: 'Class', label: 'Class' },
|
||
|
|
{ key: 'Parent', label: 'Parent' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const provinceColumns = [
|
||
|
|
{ key: 'value', label: 'ID', class: 'font-medium' },
|
||
|
|
{ key: 'label', label: 'Province Name' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const cityColumns = [
|
||
|
|
{ key: 'value', label: 'ID', class: 'font-medium' },
|
||
|
|
{ key: 'label', label: 'City Name' },
|
||
|
|
];
|
||
|
|
|
||
|
|
onMount(async () => {
|
||
|
|
await loadProvinces();
|
||
|
|
});
|
||
|
|
|
||
|
|
async function loadAreas() {
|
||
|
|
loading = true;
|
||
|
|
try {
|
||
|
|
const response = await fetchGeographicalAreas();
|
||
|
|
areas = response.data || [];
|
||
|
|
} catch (err) {
|
||
|
|
toastError(err.message || 'Failed to load geographical areas');
|
||
|
|
areas = [];
|
||
|
|
} finally {
|
||
|
|
loading = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function loadProvinces() {
|
||
|
|
loading = true;
|
||
|
|
try {
|
||
|
|
const response = await fetchProvinces();
|
||
|
|
provinces = Array.isArray(response) ? response : (Array.isArray(response.data) ? response.data : []);
|
||
|
|
} catch (err) {
|
||
|
|
toastError(err.message || 'Failed to load provinces');
|
||
|
|
provinces = [];
|
||
|
|
} finally {
|
||
|
|
loading = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function loadCities() {
|
||
|
|
loading = true;
|
||
|
|
try {
|
||
|
|
const provinceId = selectedProvince || null;
|
||
|
|
const response = await fetchCities(provinceId);
|
||
|
|
cities = Array.isArray(response) ? response : (Array.isArray(response.data) ? response.data : []);
|
||
|
|
} catch (err) {
|
||
|
|
toastError(err.message || 'Failed to load cities');
|
||
|
|
cities = [];
|
||
|
|
} finally {
|
||
|
|
loading = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleTabChange(tab) {
|
||
|
|
activeTab = tab;
|
||
|
|
if (tab === 'areas' && areas.length === 0) {
|
||
|
|
loadAreas();
|
||
|
|
} else if (tab === 'cities' && cities.length === 0) {
|
||
|
|
loadCities();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const provinceOptions = $derived(
|
||
|
|
provinces.map((p) => ({ value: p.value, label: p.label }))
|
||
|
|
);
|
||
|
|
|
||
|
|
// Reload cities when province filter changes
|
||
|
|
$effect(() => {
|
||
|
|
if (activeTab === 'cities') {
|
||
|
|
loadCities();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<div class="p-6">
|
||
|
|
<div class="flex items-center gap-4 mb-6">
|
||
|
|
<a href="/master-data" class="btn btn-ghost btn-circle">
|
||
|
|
<ArrowLeft class="w-5 h-5" />
|
||
|
|
</a>
|
||
|
|
<div class="flex-1">
|
||
|
|
<h1 class="text-3xl font-bold text-gray-800">Geography</h1>
|
||
|
|
<p class="text-gray-600">View geographical areas, provinces, and cities</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="tabs tabs-boxed mb-6">
|
||
|
|
<button
|
||
|
|
class="tab gap-2"
|
||
|
|
class:tab-active={activeTab === 'provinces'}
|
||
|
|
onclick={() => handleTabChange('provinces')}
|
||
|
|
>
|
||
|
|
<MapPin class="w-4 h-4" />
|
||
|
|
Provinces
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
class="tab gap-2"
|
||
|
|
class:tab-active={activeTab === 'cities'}
|
||
|
|
onclick={() => handleTabChange('cities')}
|
||
|
|
>
|
||
|
|
<Building2 class="w-4 h-4" />
|
||
|
|
Cities
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
class="tab gap-2"
|
||
|
|
class:tab-active={activeTab === 'areas'}
|
||
|
|
onclick={() => handleTabChange('areas')}
|
||
|
|
>
|
||
|
|
<Globe class="w-4 h-4" />
|
||
|
|
All Areas
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{#if activeTab === 'provinces'}
|
||
|
|
<div class="bg-base-100 rounded-lg shadow border border-base-200">
|
||
|
|
<DataTable
|
||
|
|
columns={provinceColumns}
|
||
|
|
data={provinces}
|
||
|
|
{loading}
|
||
|
|
emptyMessage="No provinces found"
|
||
|
|
hover={true}
|
||
|
|
bordered={false}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
{:else if activeTab === 'cities'}
|
||
|
|
<div class="space-y-4">
|
||
|
|
<div class="bg-base-200 p-4 rounded-lg">
|
||
|
|
<SelectDropdown
|
||
|
|
label="Filter by Province"
|
||
|
|
name="province"
|
||
|
|
bind:value={selectedProvince}
|
||
|
|
options={provinceOptions}
|
||
|
|
placeholder="All Provinces"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div class="bg-base-100 rounded-lg shadow border border-base-200">
|
||
|
|
<DataTable
|
||
|
|
columns={cityColumns}
|
||
|
|
data={cities}
|
||
|
|
{loading}
|
||
|
|
emptyMessage="No cities found"
|
||
|
|
hover={true}
|
||
|
|
bordered={false}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{:else if activeTab === 'areas'}
|
||
|
|
<div class="bg-base-100 rounded-lg shadow border border-base-200">
|
||
|
|
<DataTable
|
||
|
|
columns={areaColumns}
|
||
|
|
data={areas}
|
||
|
|
{loading}
|
||
|
|
emptyMessage="No geographical areas found"
|
||
|
|
hover={true}
|
||
|
|
bordered={false}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
</div>
|