217 lines
8.6 KiB
Vue
217 lines
8.6 KiB
Vue
<script setup>
|
|
import { ref, onMounted } from 'vue';
|
|
import axios from 'axios';
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
import { useNavigate } from '../composables/Core/useNavigate';
|
|
import { useModal } from '../composables/Core/useModal';
|
|
import { useAuth } from '../composables/Core/useAuth';
|
|
import TransactionListSkeleton from '../Components/Core/Skeleton/TransactionListSkeleton.vue';
|
|
|
|
usePageTitle('Cooperatives');
|
|
const { navigate } = useNavigate();
|
|
const modal = useModal();
|
|
const { isUltimate, isSuperOperator } = useAuth();
|
|
|
|
const cooperatives = ref([]);
|
|
const loading = ref(true);
|
|
const error = ref(null);
|
|
const showCreateModal = ref(false);
|
|
const isSaving = ref(false);
|
|
const createForm = ref({
|
|
name: '',
|
|
address: ''
|
|
});
|
|
|
|
const fetchCooperatives = async () => {
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
const response = await axios.post('/Cooperatives/List');
|
|
if (response.data.success) {
|
|
cooperatives.value = response.data.data;
|
|
} else {
|
|
error.value = response.data.message || 'Failed to load cooperatives.';
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch cooperatives:', err);
|
|
error.value = err.response?.data?.message || 'A server error occurred. Please try again later.';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const openCreateModal = () => {
|
|
createForm.value = { name: '', address: '' };
|
|
showCreateModal.value = true;
|
|
};
|
|
|
|
const closeCreateModal = () => {
|
|
showCreateModal.value = false;
|
|
createForm.value = { name: '', address: '' };
|
|
};
|
|
|
|
const handleCreate = async () => {
|
|
if (!createForm.value.name.trim()) {
|
|
modal.open({ title: 'Error', body: 'Cooperative name is required.' });
|
|
return;
|
|
}
|
|
|
|
isSaving.value = true;
|
|
try {
|
|
const response = await axios.post('/Cooperatives/Create', createForm.value);
|
|
if (response.data.success) {
|
|
showCreateModal.value = false;
|
|
createForm.value = { name: '', address: '' };
|
|
await fetchCooperatives();
|
|
modal.open({
|
|
title: 'Success',
|
|
body: 'Cooperative created successfully!',
|
|
});
|
|
} else {
|
|
modal.open({
|
|
title: 'Error',
|
|
body: response.data.message || 'Failed to create cooperative. Please try again.'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to create cooperative:', error);
|
|
modal.open({
|
|
title: 'Error',
|
|
body: error.response?.data?.message || 'Failed to create cooperative. Please try again.'
|
|
});
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
};
|
|
|
|
const viewDetails = (hashkey) => {
|
|
navigate({ page: 'CooperativeDetail', props: { target: hashkey } });
|
|
};
|
|
|
|
const goToCreate = () => {
|
|
navigate({ page: 'CreateCooperative' });
|
|
};
|
|
|
|
onMounted(fetchCooperatives);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="cooperative-list pb-5">
|
|
<div class="tf-container mt-4">
|
|
<div class="mb-4">
|
|
<h3 class="fw_6 mb-3">Cooperatives</h3>
|
|
<div class="d-flex gap-2">
|
|
<button v-if="isUltimate || isSuperOperator" @click="navigate({ page: 'BatchAddCooperatives' })" class="btn btn-outline-primary rounded-pill px-4 py-2 d-flex align-items-center gap-2 flex-grow-1 justify-content-center">
|
|
<i class="fas fa-layer-group"></i> Batch Add
|
|
</button>
|
|
<button @click="goToCreate" class="btn btn-primary rounded-pill px-4 py-2 d-flex align-items-center gap-2 flex-grow-1 justify-content-center">
|
|
<i class="fas fa-plus"></i> New Cooperative
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="loading" class="mt-2 text-center">
|
|
<TransactionListSkeleton :count="6" />
|
|
</div>
|
|
|
|
<div v-else-if="error" class="text-center py-5 px-4 animate-fade-in">
|
|
<div class="bg-soft-danger text-danger rounded-circle mx-auto mb-4 p-3 d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
|
|
<i class="fas fa-exclamation-triangle fa-3x"></i>
|
|
</div>
|
|
<h4 class="fw-bold mb-2 text-dark">Data Not Loaded</h4>
|
|
<p class="text-muted mb-4 px-lg-5">{{ error }}</p>
|
|
<button @click="fetchCooperatives" class="btn btn-outline-primary rounded-pill px-4">
|
|
<i class="fas fa-sync-alt me-2"></i> Try Again
|
|
</button>
|
|
</div>
|
|
|
|
<div v-else-if="cooperatives.length === 0" class="text-center py-5">
|
|
<i class="fas fa-users-slash fa-3x text-muted opacity-2 mb-3"></i>
|
|
<p class="text-muted">No cooperatives found.</p>
|
|
<button @click="goToCreate" class="btn btn-primary mt-3 rounded-pill px-4">
|
|
<i class="fas fa-plus me-2"></i> Create First Cooperative
|
|
</button>
|
|
</div>
|
|
|
|
<div v-else class="row g-3">
|
|
<div v-for="coop in cooperatives" :key="coop.hashkey" class="col-12 col-md-6">
|
|
<div @click="viewDetails(coop.hashkey)" class="card border-0 shadow-sm rounded-20 p-3 cursor-pointer hover-card">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="bg-primary-subtle rounded-circle p-3 text-primary">
|
|
<i class="fas fa-users fa-lg"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h5 class="fw_6 mb-1 text-truncate">{{ coop.name }}</h5>
|
|
<p class="text-muted small mb-0"><i class="fas fa-map-marker-alt me-1"></i> {{ coop.address || 'No address' }}</p>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-light text-dark rounded-pill border">{{ coop.members_count || 0 }} Members</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Cooperative Modal -->
|
|
<div v-if="showCreateModal" class="modal-backdrop-custom" @click.self="closeCreateModal">
|
|
<div class="modal-card">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h5 class="fw_6 mb-0">Create Cooperative</h5>
|
|
<button @click="closeCreateModal" class="btn-close"></button>
|
|
</div>
|
|
<form @submit.prevent="handleCreate">
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">Cooperative Name *</label>
|
|
<input v-model="createForm.name" type="text" class="form-control rounded-pill" required placeholder="Enter cooperative name">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="form-label small fw-bold">Address</label>
|
|
<textarea v-model="createForm.address" class="form-control rounded-15" rows="2" placeholder="Enter address (optional)"></textarea>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" @click="closeCreateModal" class="btn btn-light rounded-pill flex-grow-1 py-2">Cancel</button>
|
|
<button :disabled="isSaving" type="submit" class="btn btn-primary rounded-pill flex-grow-1 py-2">
|
|
<span v-if="isSaving"><i class="fas fa-spinner fa-spin me-1"></i> Creating...</span>
|
|
<span v-else>Create</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.rounded-20 { border-radius: 20px; }
|
|
.rounded-15 { border-radius: 15px; }
|
|
.cursor-pointer { cursor: pointer; }
|
|
.hover-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
|
|
}
|
|
.bg-soft-danger { background-color: rgba(220, 53, 69, 0.1); }
|
|
.bg-primary-subtle { background-color: rgba(13, 110, 253, 0.1); }
|
|
.modal-backdrop-custom {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 1050;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 1rem;
|
|
}
|
|
.modal-card {
|
|
background: white;
|
|
border-radius: 20px;
|
|
padding: 1.5rem;
|
|
width: 100%;
|
|
max-width: 450px;
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
}
|
|
</style>
|