Files
BarangaySystem/resources/js/Pages/Fragments/Home/HomeSuperOperator.vue
2026-06-06 18:43:00 +08:00

251 lines
13 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, h, computed } from 'vue';
import usePageData from '../../../composables/usePageData.js';
import { useNavigate } from '../../../composables/Core/useNavigate.js';
import { useUserNotes } from '../../../composables/useUserNotes.js';
import { useModal } from '../../../composables/Core/useModal.js';
import { useAuth } from '../../../composables/Core/useAuth.js';
import { useGlobalTransactions } from '../../../composables/useGlobalTransactions.js';
import { useActivity } from '../../../composables/useActivity.js';
import { usePrefetch } from '../../../composables/Core/usePrefetch.js';
import BalanceBox from '../../../Components/Core/Stats/BalanceBox.vue';
import ServiceButtonGrid from '../../../Components/Core/Services/ServiceButtonGrid.vue';
import SideTextButtonList from '../../../Components/Core/Services/SideTextButtonList.vue';
import SearchableList from '../../../Components/Core/Search/SearchableList.vue';
import GlobalAnnouncement from '../../../Components/GlobalAnnouncement.vue';
import HomeSkeleton from '../../../Components/Core/Skeleton/HomeSkeleton.vue';
import CooperativeDetail from '@/Pages/CooperativeDetail.vue';
import DocumentRepository from '@/Pages/Fragments/DocumentRepository.vue';
import GovernanceResolutions from '@/Pages/Fragments/GovernanceResolutions.vue';
import OrgHierarchyExplorer from '@/Pages/Fragments/Home/OrgHierarchyExplorer.vue';
import { useUIStore } from '../../../stores/ui';
const uiStore = useUIStore();
const isOrgExplorerMode = computed(() => ['tandem', 'ngo'].includes(uiStore.app_mode));
const { user } = useAuth();
const { precache } = useGlobalTransactions();
const url = '/home-data';
const payload = {};
const { data, loading, error, fetchPageData } = usePageData();
const { navigate } = useNavigate();
const { notes, fetchNotes, dismissNotes, hasNotes } = useUserNotes();
const { activities, fetchRecentActivities, loading: loadingActivities } = useActivity();
const { prefetchEverything } = usePrefetch();
const modal = useModal();
const showStaleIndicator = ref(false);
const stats = ref([
{ title: "Transactions", number: 0, unit: "Total", align: "left", numberId: "total_transactions_no" },
{ title: "Value", number: 0, unit: "PHP", align: "left", numberId: "total_transactions_php" },
{ title: "Projected", number: 0, unit: "Income", align: "right", numberId: "projected_income_today" },
]);
const balanceFooterItems = ref([
{ title: "Add Transaction", icon: "https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/c9fd442fe676.bin", pagename: "AddTransaction" },
{ title: "View Reports", icon: "https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/f87407046b18.bin", pagename: "ListReports" },
]);
const activeOrgHash = computed(() => {
const coops = user.value?.settings?.cooperatives;
if (Array.isArray(coops) && coops.length > 0) return coops[0];
return null;
});
const hubTab = ref('overview');
const services = [
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/32248fe10b94.bin', title: 'Users', pagename: 'UserList' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/85605eacd4c8.bin', title: 'Stores', pagename: 'ManageStoresAdmin' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/ef1a9a079a2d.svg', title: 'Products', pagename: 'ManageProductsAdmin' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/c9fd442fe676.bin', title: 'Transactions', pagename: 'ManageGlobalTransactions' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/f87407046b18.bin', title: 'Reports', pagename: 'ListReports' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/fa711c34b4ef.svg', title: 'Accounting', pagename: 'AccountingDashboard' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/5b5ef88c0ad1.svg', title: 'POS Keys', pagename: 'PosAccessKeys' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/04d0e432a298.bin', title: 'Market', pagename: 'ListProductsMarket' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/4d9cb130fad1.bin', title: 'Shipments', pagename: 'ShipmentList' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/5d0ad5d52b8c.bin', title: 'Farmers', pagename: 'FarmerProfileEdit' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/3360da347e6b.svg', title: 'Verification', pagename: 'VerificationDashboard' },
{ icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/d9b9b9179ce0.svg', title: 'Cooperatives', pagename: 'CooperativeList' },
];
const quickActionsItems = computed(() => [
{ text: 'Onboard New User', pagename: 'CreateUser', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/516ed2aaaa4c.bin' },
{ text: 'Register New Store', pagename: 'CreateStore', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/85605eacd4c8.bin' },
{ text: 'Create New Product', pagename: 'CreateProductUltimate', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/f0a0193d728e.bin' },
{ text: 'Create New Cooperative', pagename: 'CreateCooperative', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/d9b9b9179ce0.svg' },
{ text: 'Add Organization', pagename: 'CreateOrganization', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/f0cc8da0402c.svg' },
{ text: 'My Personal Profile', pagename: 'UserInfoEdit', pagestring: user.value?.hashkey, icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/ac7a1cebe580.bin' },
{ text: 'Referrals & Leads', pagename: 'ListReferrals', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/7d45d4bdbc74.bin' },
{ text: 'Send Credit Transfer', pagename: 'TransferMyCredit', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/074af7aca12b.bin' },
{ text: 'Landing Page Editor', pagename: 'LandingPageEditor', icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/3d721f4acf47.svg' },
]);
const showNotesModal = () => {
modal.continueCancelModal({
title: 'Notes',
body: h('div', { style: 'white-space: pre-wrap; font-size: 16px; line-height: 1.5; color: #333;' }, notes.value),
continueText: 'Dismiss Note',
cancelText: 'Close',
continueClass: 'btn btn-danger w-50 py-2 rounded-3 shadow-sm fw-bold',
cancelClass: 'btn btn-light w-50 py-2 rounded-3 border fw-bold text-muted',
onContinue: async () => {
const success = await dismissNotes();
if (success) await fetchNotes();
}
});
};
onMounted(async () => {
precache();
fetchRecentActivities(10);
const result = await fetchPageData(url, payload);
if (result && result.stale) showStaleIndicator.value = true;
if (data.value?.stats) {
stats.value = stats.value.map(s => {
if (data.value.stats[s.numberId] !== undefined) return { ...s, number: data.value.stats[s.numberId] };
return s;
});
}
await fetchNotes();
if (hasNotes()) showNotesModal();
setTimeout(() => { prefetchEverything(); }, 1000);
});
const handleItemClick = (item) => {
if (item.pagename) navigate({ page: item.pagename, props: { data: item.pagestring || '' } });
};
</script>
<template>
<div class="home-super-operator-fragment pb-5">
<HomeSkeleton v-if="loading && !data" />
<div v-if="showStaleIndicator" class="stale-notice">
Displaying cached data.
</div>
<div v-if="data">
<BalanceBox :stats="stats" :footer-items="balanceFooterItems" @footer-click="handleItemClick" />
<div class="mt-2">
<ServiceButtonGrid :items="services" @item-click="handleItemClick" />
</div>
<div class="mt-3">
<GlobalAnnouncement />
</div>
<div v-if="isOrgExplorerMode" class="mt-4 px-3">
<div class="card border-0 shadow-sm rounded-4 bg-white p-3">
<OrgHierarchyExplorer />
</div>
</div>
<div v-if="activeOrgHash && !isOrgExplorerMode" class="mt-4 px-3">
<div class="d-flex align-items-center justify-content-between mb-3">
<h5 class="fw_7 mb-0 d-flex align-items-center gap-2" style="color: var(--text-primary) !important;">
<i class="fas fa-landmark text-primary opacity-50"></i>
Cooperative Hub
</h5>
<div class="d-flex gap-1 bg-soft-primary p-1 rounded-pill">
<button
@click="hubTab = 'overview'"
:class="['btn btn-xs rounded-pill px-3 transition-all', hubTab === 'overview' ? 'btn-primary shadow-sm' : 'btn-transparent text-primary small']"
>Overview</button>
<button
@click="hubTab = 'docs'"
:class="['btn btn-xs rounded-pill px-3 transition-all', hubTab === 'docs' ? 'btn-primary shadow-sm' : 'btn-transparent text-primary small']"
>Docs</button>
<button
@click="hubTab = 'votes'"
:class="['btn btn-xs rounded-pill px-3 transition-all', hubTab === 'votes' ? 'btn-primary shadow-sm' : 'btn-transparent text-primary small']"
>Resolutions</button>
</div>
</div>
<div class="card border-0 shadow-sm rounded-20 bg-white overflow-hidden p-0">
<div v-if="hubTab === 'overview'">
<CooperativeDetail :target="activeOrgHash" />
</div>
<div v-else-if="hubTab === 'docs'" class="p-3">
<DocumentRepository :org-hash="activeOrgHash" />
</div>
<div v-else-if="hubTab === 'votes'" class="p-3">
<GovernanceResolutions :org-hash="activeOrgHash" />
</div>
</div>
</div>
<div class="tf-container mt-4">
<h5 class="fw_7 mb-3" style="color: var(--text-primary) !important;">Management & Tools</h5>
<SideTextButtonList :items="quickActionsItems" @item-click="handleItemClick" />
</div>
<div class="mt-4 px-3">
<div class="activity-section card border-0 shadow-sm rounded-4 bg-white overflow-hidden">
<div class="card-header bg-white border-0 py-3 px-4 d-flex align-items-center justify-content-between">
<h5 class="fw-bold mb-0 d-flex align-items-center gap-2">
<i class="fas fa-history text-primary"></i>
Recent System Activity
</h5>
<a
href="javascript:void(0);"
class="btn btn-sm btn-outline-primary rounded-pill px-3 fw-semibold"
@click="navigate({ page: 'ManageGlobalTransactions' })"
>View All</a>
</div>
<div class="card-body p-0">
<SearchableList
title=""
:items="activities"
:loading="loadingActivities"
empty-text="No recent activity recorded"
@item-click="handleItemClick"
/>
</div>
</div>
</div>
</div>
<div v-if="error && !data">
<div class="tf-container mt-5 text-center">
<p style="color: var(--text-muted);">Dashboard disconnected. Please retry.</p>
<button class="btn btn-primary mt-2 rounded-pill px-4" @click="fetchPageData(url, payload)">Reconnect</button>
</div>
</div>
</div>
</template>
<style scoped>
.stale-notice {
background-color: #fff3cd;
color: #856404;
padding: 8px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 0.85rem;
text-align: center;
}
.btn-xs {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
line-height: 1.5;
border-radius: 50rem;
}
.bg-soft-primary {
background-color: rgba(var(--primary-rgb), 0.1);
}
.transition-all {
transition: all 0.2s ease;
}
</style>