240 lines
7.0 KiB
JavaScript
240 lines
7.0 KiB
JavaScript
// assets/js/pages/manager/ManagerApp.js
|
|
const { useState, useEffect } = React;
|
|
|
|
const ManagerApp = () => {
|
|
const state = useManagerState();
|
|
const handlers = useManagerHandlers(state);
|
|
|
|
const {
|
|
user,
|
|
setUser,
|
|
activeTab,
|
|
setActiveTab,
|
|
stats,
|
|
showDetailModal,
|
|
setShowDetailModal,
|
|
selectedItem,
|
|
showVerifyModal,
|
|
setShowVerifyModal,
|
|
selectedClaim,
|
|
toast,
|
|
setToast,
|
|
showCloseCaseModal,
|
|
setShowCloseCaseModal,
|
|
closeCaseData,
|
|
setCloseCaseData,
|
|
loading,
|
|
showReportFoundModal,
|
|
setShowReportFoundModal,
|
|
photoPreview,
|
|
setPhotoPreview,
|
|
showManualClaimModal,
|
|
setShowManualClaimModal,
|
|
selectedItemForClaim,
|
|
setSelectedItemForClaim,
|
|
showApproveModal,
|
|
setShowApproveModal,
|
|
} = state;
|
|
|
|
const {
|
|
loadData,
|
|
handleApproveClaim,
|
|
handleRejectClaim,
|
|
handleCloseCase,
|
|
submitCloseCase,
|
|
submitReportFound,
|
|
handlePhotoChange,
|
|
handleLogout,
|
|
submitApproveClaim,
|
|
} = handlers;
|
|
|
|
useEffect(() => {
|
|
if (!AuthUtils.checkAuthAndRedirect("manager")) return;
|
|
const currentUser = AuthUtils.getCurrentUser();
|
|
setUser(currentUser);
|
|
loadData();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (activeTab === "claims") {
|
|
handlers.loadClaims();
|
|
}
|
|
}, [activeTab]);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900">
|
|
<Navbar user={user} onLogout={handleLogout} userType="manager" />
|
|
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold text-white mb-2">
|
|
Dashboard Manager
|
|
</h1>
|
|
<p className="text-slate-400">
|
|
Kelola barang temuan dan verifikasi klaim
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<StatCard
|
|
title="Total Barang"
|
|
value={stats.total_items || 0}
|
|
icon="📦"
|
|
/>
|
|
<StatCard
|
|
title="Pending Claim"
|
|
value={stats.pending_claims || 0}
|
|
icon="⏳"
|
|
colorClass="text-yellow-400"
|
|
/>
|
|
<StatCard
|
|
title="Verified"
|
|
value={stats.verified || 0}
|
|
icon="✅"
|
|
colorClass="text-green-400"
|
|
/>
|
|
<StatCard
|
|
title="Expired"
|
|
value={stats.expired || 0}
|
|
icon="⚠️"
|
|
colorClass="text-red-400"
|
|
/>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex gap-2 mb-6 flex-wrap">
|
|
<button
|
|
onClick={() => setActiveTab("items")}
|
|
className={`px-6 py-3 rounded-xl font-semibold transition ${
|
|
activeTab === "items"
|
|
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg shadow-blue-500/50"
|
|
: "bg-slate-800 text-slate-300 hover:bg-slate-700 border border-slate-700"
|
|
}`}
|
|
>
|
|
📦 Kelola Barang
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
setActiveTab("lost-items");
|
|
handlers.loadLostItems();
|
|
}}
|
|
className={`px-6 py-3 rounded-xl font-semibold transition ${
|
|
activeTab === "lost-items"
|
|
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg shadow-blue-500/50"
|
|
: "bg-slate-800 text-slate-300 hover:bg-slate-700 border border-slate-700"
|
|
}`}
|
|
>
|
|
😢 Kelola Barang Hilang
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => setActiveTab("claims")}
|
|
className={`px-6 py-3 rounded-xl font-semibold transition ${
|
|
activeTab === "claims"
|
|
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg shadow-blue-500/50"
|
|
: "bg-slate-800 text-slate-300 hover:bg-slate-700 border border-slate-700"
|
|
}`}
|
|
>
|
|
🤝 Verifikasi Klaim
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === "items" && (
|
|
<ItemsTabManager state={state} handlers={handlers} />
|
|
)}
|
|
{activeTab === "lost-items" && (
|
|
<LostItemsTabManager state={state} handlers={handlers} />
|
|
)}
|
|
{activeTab === "claims" && (
|
|
<ClaimsTabManager state={state} handlers={handlers} />
|
|
)}
|
|
|
|
{/* Modals */}
|
|
<DetailModal
|
|
isOpen={showDetailModal}
|
|
onClose={() => setShowDetailModal(false)}
|
|
item={selectedItem}
|
|
/>
|
|
|
|
<VerifyClaimModal
|
|
isOpen={showVerifyModal}
|
|
onClose={() => setShowVerifyModal(false)}
|
|
claim={selectedClaim}
|
|
onApprove={handleApproveClaim}
|
|
onReject={handleRejectClaim}
|
|
onCloseCase={handleCloseCase}
|
|
/>
|
|
|
|
<CloseCaseModal
|
|
isOpen={showCloseCaseModal}
|
|
onClose={() => setShowCloseCaseModal(false)}
|
|
claim={selectedClaim}
|
|
closeCaseData={closeCaseData}
|
|
setCloseCaseData={setCloseCaseData}
|
|
onSubmit={submitCloseCase}
|
|
loading={loading}
|
|
/>
|
|
|
|
<ManagerReportFoundModal
|
|
isOpen={showReportFoundModal}
|
|
onClose={() => {
|
|
setShowReportFoundModal(false);
|
|
setPhotoPreview(null);
|
|
}}
|
|
categories={state.categories}
|
|
onSubmit={submitReportFound}
|
|
loading={loading}
|
|
photoPreview={photoPreview}
|
|
onPhotoChange={(e) => handlePhotoChange(e)}
|
|
/>
|
|
|
|
<MatchLostItemModal
|
|
isOpen={state.showMatchLostItemModal}
|
|
onClose={() => {
|
|
state.setShowMatchLostItemModal(false);
|
|
state.setSelectedLostItem(null);
|
|
}}
|
|
lostItem={state.selectedLostItem}
|
|
items={state.items}
|
|
onSubmit={handlers.submitMatchLostItem}
|
|
loading={loading}
|
|
/>
|
|
|
|
<ApproveClaimModal
|
|
isOpen={showApproveModal}
|
|
onClose={() => setShowApproveModal(false)}
|
|
onSubmit={submitApproveClaim}
|
|
loading={loading}
|
|
/>
|
|
|
|
<ManagerManualClaimModal
|
|
isOpen={showManualClaimModal}
|
|
onClose={() => {
|
|
setShowManualClaimModal(false);
|
|
setSelectedItemForClaim(null);
|
|
}}
|
|
item={selectedItemForClaim}
|
|
onSubmit={handlers.submitManualClaim}
|
|
loading={loading}
|
|
/>
|
|
<EditItemModal state={state} handlers={handlers} />
|
|
|
|
{/* Toast */}
|
|
{toast && (
|
|
<Toast
|
|
message={toast.message}
|
|
type={toast.type}
|
|
onClose={() => setToast(null)}
|
|
/>
|
|
)}
|
|
<AIChatbot />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
ReactDOM.render(<ManagerApp />, document.getElementById("root"));
|