updates 2

This commit is contained in:
ashok 2026-03-14 17:13:13 +05:30
parent e1e55959e1
commit 50ba5c23e8
19 changed files with 379 additions and 137 deletions

View File

@ -17,7 +17,7 @@ public function index(Request $request)
} }
$branches = $query->get(); $branches = $query->get();
// Attach is_deletable flag to each branch // Attach is_deletable flag and total revenue to each branch
$branches->each(function ($branch) { $branches->each(function ($branch) {
$inUse = \App\Models\Staff::where('branch_id', $branch->id)->exists() $inUse = \App\Models\Staff::where('branch_id', $branch->id)->exists()
|| \App\Models\Product::where('branch_id', $branch->id)->exists() || \App\Models\Product::where('branch_id', $branch->id)->exists()
@ -27,6 +27,9 @@ public function index(Request $request)
|| \App\Models\ProductSale::where('branch_id', $branch->id)->exists() || \App\Models\ProductSale::where('branch_id', $branch->id)->exists()
|| \App\Models\Receptionist::where('branch_id', $branch->id)->exists(); || \App\Models\Receptionist::where('branch_id', $branch->id)->exists();
$branch->is_deletable = !$inUse; $branch->is_deletable = !$inUse;
// Calculate total revenue (total credit in accounts)
$branch->total_revenue = \App\Models\Account::where('branch_id', $branch->id)->sum('credit');
}); });
return response()->json($branches); return response()->json($branches);
@ -123,6 +126,21 @@ public function update(Request $request, $id)
} }
} }
// Process new documents if provided
if (!empty($validated['new_docs'])) {
foreach ($validated['new_docs'] as $doc) {
$path = $doc['file']->store('branch_documents', 'public');
BranchDocument::create([
'branch_id' => $branch->id,
'name' => $doc['name'],
'document_number' => $doc['document_number'] ?? null,
'path' => $path,
'expiry_date' => $doc['expiry_date'],
'reminder_days' => $doc['reminder_days'] ?? 30
]);
}
}
return response()->json(['message' => 'Branch updated successfully', 'branch' => $branch->load('documents')]); return response()->json(['message' => 'Branch updated successfully', 'branch' => $branch->load('documents')]);
} }

View File

@ -104,8 +104,18 @@ public function getSalaryHistory(Request $request)
$query = Expense::with(['branch']) $query = Expense::with(['branch'])
->where('expense_category_id', $salaryCategory->id); ->where('expense_category_id', $salaryCategory->id);
if ($user->isReceptionist()) { $branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
$query->where('branch_id', $user->branch_id); $startDate = $request->query('start_date');
$endDate = $request->query('end_date');
if ($branchId) {
$query->where('branch_id', $branchId);
}
if ($startDate) {
$query->where('date', '>=', $startDate);
}
if ($endDate) {
$query->where('date', '<=', $endDate);
} }
$expenses = $query->orderBy('date', 'desc')->get(); $expenses = $query->orderBy('date', 'desc')->get();

View File

@ -145,7 +145,14 @@ public function getSales(Request $request)
$query->where('date', '<=', $endDate); $query->where('date', '<=', $endDate);
} }
return response()->json($query->orderBy('date', 'desc')->get()); $sales = $query->orderBy('date', 'desc')->get()->map(function($s) {
$originalTotal = ($s->subtotal_amount ?? 0) + ($s->vat_amount ?? 0);
$s->is_adjusted = abs($s->total_amount - $originalTotal) > 0.01;
$s->original_amount = $originalTotal;
return $s;
});
return response()->json($sales);
} }
public function storeSale(Request $request) public function storeSale(Request $request)

View File

@ -22,8 +22,12 @@ public function getProfitReport(Request $request)
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id'); $branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
$startDate = $request->query('start_date'); $startDate = $request->query('start_date');
$endDate = $request->query('end_date'); $endDate = $request->query('end_date');
$page = $request->query('page', 1);
$perPage = $request->query('per_page', 10);
$query = Account::with('accountable'); // Base Query from Account table (Ledger)
$query = Account::query()->with('accountable');
if ($branchId) { if ($branchId) {
$query->where('branch_id', $branchId); $query->where('branch_id', $branchId);
} }
@ -34,24 +38,18 @@ public function getProfitReport(Request $request)
$query->where('date', '<=', $endDate); $query->where('date', '<=', $endDate);
} }
// Stats from the same filtered query
$totalCredits = (clone $query)->sum('credit'); $totalCredits = (clone $query)->sum('credit');
$totalDebits = (clone $query)->sum('debit'); $totalDebits = (clone $query)->sum('debit');
// Fetch All Ledger Transactions for the breakdown // Fetch Paginated Transactions from the same filtered query
$accounts = Account::where(function($q) { $allTransactions = $query->where(function($q) {
$q->where('credit', '>', 0)->orWhere('debit', '>', 0); $q->where('credit', '>', 0)->orWhere('debit', '>', 0);
}); })
->orderBy('date', 'desc')
if ($branchId) { ->orderBy('time', 'desc')
$accounts->where('branch_id', $branchId); ->orderBy('id', 'desc')
} ->get()
if ($startDate) {
$accounts->where('date', '>=', $startDate);
}
if ($endDate) {
$accounts->where('date', '<=', $endDate);
}
$accounts = $accounts->get()
->map(function($a) { ->map(function($a) {
$isAdjusted = false; $isAdjusted = false;
$originalAmount = $a->credit > 0 ? $a->credit : $a->debit; $originalAmount = $a->credit > 0 ? $a->credit : $a->debit;
@ -68,41 +66,22 @@ public function getProfitReport(Request $request)
} }
return [ return [
'id' => $a->id,
'date' => $a->date, 'date' => $a->date,
'time' => $a->time || '00:00:00',
'type' => $a->credit > 0 ? 'Income' : 'Expense', 'type' => $a->credit > 0 ? 'Income' : 'Expense',
'category' => $a->type, 'category' => $a->type,
'description' => $a->description, 'description' => $a->description,
'amount' => $a->credit > 0 ? $a->credit : $a->debit, 'amount' => $a->credit > 0 ? $a->credit : $a->debit,
'branch' => 'N/A', 'branch' => 'N/A', // Branch name if needed can be added via relation
'is_adjusted' => $isAdjusted, 'is_adjusted' => $isAdjusted,
'original_amount' => $originalAmount, 'original_amount' => $originalAmount,
'remarks' => $remarks 'remarks' => $remarks
]; ];
}); });
$expensesQuery = Expense::with('category', 'branch'); $totalTransactions = $allTransactions->count();
if ($branchId) { $paginatedTransactions = $allTransactions->forPage($page, $perPage)->values();
$expensesQuery->where('branch_id', $branchId);
}
if ($startDate) {
$expensesQuery->where('date', '>=', $startDate);
}
if ($endDate) {
$expensesQuery->where('date', '<=', $endDate);
}
$expenses = $expensesQuery->get()->map(function($e) {
return [
'date' => $e->date,
'type' => 'Expense',
'category' => $e->category->name ?? 'Other',
'description' => $e->remarks,
'amount' => $e->amount,
'branch' => $e->branch->name ?? 'Global'
];
});
$transactions = $accounts->concat($expenses)->sortByDesc('date')->values();
$lowStockCount = \App\Models\Product::query(); $lowStockCount = \App\Models\Product::query();
if ($branchId) { if ($branchId) {
@ -121,9 +100,11 @@ public function getProfitReport(Request $request)
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()]) ->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
->sum('credit'); ->sum('credit');
$monthExpense = Expense::when($branchId, fn($q) => $q->where('branch_id', $branchId)) // For trend, we can also use Account table for expenses
$monthExpense = Account::where('branch_id', $branchId ?: '!=', 0)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()]) ->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
->sum('amount'); ->sum('debit');
$trend[] = [ $trend[] = [
'month' => $monthStart->format('M'), 'month' => $monthStart->format('M'),
@ -139,7 +120,13 @@ public function getProfitReport(Request $request)
'total_expense' => $totalDebits, 'total_expense' => $totalDebits,
'net_profit' => $totalCredits - $totalDebits, 'net_profit' => $totalCredits - $totalDebits,
'low_stock_count' => $lowStockCount, 'low_stock_count' => $lowStockCount,
'transactions' => $transactions, 'transactions' => $paginatedTransactions,
'pagination' => [
'total' => $totalTransactions,
'per_page' => (int)$perPage,
'current_page' => (int)$page,
'last_page' => ceil($totalTransactions / $perPage)
],
'trend' => $trend 'trend' => $trend
]); ]);
} }
@ -151,6 +138,8 @@ public function getExpiryReminders(Request $request)
if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); if (!$user) return response()->json(['message' => 'Unauthenticated'], 401);
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id'); $branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
$startDate = $request->query('start_date');
$endDate = $request->query('end_date');
$staffDocsQuery = \App\Models\StaffDocument::with('staff.branch') $staffDocsQuery = \App\Models\StaffDocument::with('staff.branch')
->whereNotNull('expiry_date'); ->whereNotNull('expiry_date');
@ -161,6 +150,13 @@ public function getExpiryReminders(Request $request)
}); });
} }
if ($startDate) {
$staffDocsQuery->where('expiry_date', '>=', $startDate);
}
if ($endDate) {
$staffDocsQuery->where('expiry_date', '<=', $endDate);
}
$staffDocs = $staffDocsQuery->get() $staffDocs = $staffDocsQuery->get()
->filter(function($doc) use ($today) { ->filter(function($doc) use ($today) {
$expiryDate = Carbon::parse($doc->expiry_date)->startOfDay(); $expiryDate = Carbon::parse($doc->expiry_date)->startOfDay();
@ -192,6 +188,13 @@ public function getExpiryReminders(Request $request)
$branchDocsQuery->where('branch_id', $branchId); $branchDocsQuery->where('branch_id', $branchId);
} }
if ($startDate) {
$branchDocsQuery->where('expiry_date', '>=', $startDate);
}
if ($endDate) {
$branchDocsQuery->where('expiry_date', '<=', $endDate);
}
$branchReminders = $branchDocsQuery->get() $branchReminders = $branchDocsQuery->get()
->filter(function($doc) use ($today) { ->filter(function($doc) use ($today) {
$expiryDate = Carbon::parse($doc->expiry_date); $expiryDate = Carbon::parse($doc->expiry_date);
@ -216,7 +219,7 @@ public function getExpiryReminders(Request $request)
}); });
return response()->json([ return response()->json([
'reminders' => $staffDocs->concat($branchReminders)->values() 'reminders' => $staffDocs->concat($branchReminders)->sortBy('expiry_date')->values()
]); ]);
} }
@ -311,7 +314,7 @@ public function getInvestmentReport(Request $request)
return response()->json([ return response()->json([
'total_invested' => $totalInvested, 'total_invested' => $totalInvested,
'total_roi_returned' => $totalROIReturned, 'total_roi_returned' => $totalROIReturned,
'investors' => $reportData 'investors' => collect($reportData)->sortByDesc('investment_date')->values()
]); ]);
} }
} }

View File

@ -63,7 +63,7 @@ export default function List() {
{ header: 'Manager', key: 'manager_name' }, { header: 'Manager', key: 'manager_name' },
{ {
header: 'Revenue (AED)', header: 'Revenue (AED)',
render: () => <span className="font-bold text-gray-900">0.00</span> render: (row) => <span className="font-bold text-emerald-600">{(row.total_revenue || 0).toLocaleString()} AED</span>
}, },
{ {
header: 'Status', header: 'Status',

View File

@ -215,12 +215,16 @@ export default function AddCollectionModal({ isOpen, onClose, onSave, branches,
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Branch</label> <label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Branch</label>
<select <select
required required
className="w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none" className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none truncate"
value={formData.branch_id} value={formData.branch_id}
onChange={e => setFormData({...formData, branch_id: e.target.value, items: [], amount: ''})} onChange={e => setFormData({...formData, branch_id: e.target.value, items: [], amount: ''})}
> >
<option value="">Select Branch</option> <option value="">Select Branch</option>
{branches.map(b => <option key={b.id} value={b.id}>{b.name}</option>)} {branches.map(b => (
<option key={b.id} value={b.id}>
{b.name.length > 40 ? b.name.substring(0, 40) + '...' : b.name}
</option>
))}
</select> </select>
</div> </div>
) : ( ) : (
@ -236,7 +240,7 @@ export default function AddCollectionModal({ isOpen, onClose, onSave, branches,
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Collection Type</label> <label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Collection Type</label>
<select <select
required required
className="w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none" className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none truncate"
value={formData.collection_type_id} value={formData.collection_type_id}
onChange={e => { onChange={e => {
const typeId = e.target.value; const typeId = e.target.value;
@ -250,7 +254,11 @@ export default function AddCollectionModal({ isOpen, onClose, onSave, branches,
}} }}
> >
<option value="">Select Type</option> <option value="">Select Type</option>
{types.map(t => <option key={t.id} value={t.id}>{t.name}</option>)} {types.map(t => (
<option key={t.id} value={t.id}>
{t.name.length > 40 ? t.name.substring(0, 40) + '...' : t.name}
</option>
))}
</select> </select>
</div> </div>
@ -258,7 +266,7 @@ export default function AddCollectionModal({ isOpen, onClose, onSave, branches,
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Payment Method</label> <label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Payment Method</label>
<select <select
required required
className="w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none" className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none truncate"
value={formData.payment_method} value={formData.payment_method}
onChange={e => setFormData({...formData, payment_method: e.target.value})} onChange={e => setFormData({...formData, payment_method: e.target.value})}
> >

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { MoreVertical, ArrowDownRight, ArrowUpRight } from 'lucide-react'; import { MoreVertical, ArrowDownRight, ArrowUpRight, ChevronLeft, ChevronRight } from 'lucide-react';
export default function AccountsTable({ data = [] }) { export default function AccountsTable({ data = [], pagination = null, onPageChange }) {
const formatCurrency = (val) => { const formatCurrency = (val) => {
return new Intl.NumberFormat('en-AE', { style: 'currency', currency: 'AED' }).format(val); return new Intl.NumberFormat('en-AE', { style: 'currency', currency: 'AED' }).format(val);
}; };
@ -68,6 +68,44 @@ export default function AccountsTable({ data = [] }) {
</tbody> </tbody>
</table> </table>
</div> </div>
{/* Pagination Controls */}
{pagination && pagination.last_page > 1 && (
<div className="flex items-center justify-between mt-8 pt-6 border-t border-gray-50">
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">
Showing page {pagination.current_page} of {pagination.last_page} ({pagination.total} total)
</p>
<div className="flex items-center gap-2">
<button
disabled={pagination.current_page === 1}
onClick={() => onPageChange(pagination.current_page - 1)}
className="p-2 bg-gray-50 text-gray-400 rounded-lg hover:bg-red-50 hover:text-red-500 transition-all disabled:opacity-30 disabled:hover:bg-gray-50 disabled:hover:text-gray-400"
>
<ChevronLeft size={18} />
</button>
{[...Array(pagination.last_page)].map((_, i) => (
<button
key={i + 1}
onClick={() => onPageChange(i + 1)}
className={`w-9 h-9 flex items-center justify-center rounded-lg text-[10px] font-black transition-all ${
pagination.current_page === i + 1
? 'bg-red-500 text-white shadow-lg shadow-red-200'
: 'bg-gray-50 text-gray-400 hover:bg-gray-100 hover:text-gray-900'
}`}
>
{i + 1}
</button>
))}
<button
disabled={pagination.current_page === pagination.last_page}
onClick={() => onPageChange(pagination.current_page + 1)}
className="p-2 bg-gray-50 text-gray-400 rounded-lg hover:bg-red-50 hover:text-red-500 transition-all disabled:opacity-30 disabled:hover:bg-gray-50 disabled:hover:text-gray-400"
>
<ChevronRight size={18} />
</button>
</div>
</div>
)}
</div> </div>
); );
} }

View File

@ -8,16 +8,15 @@ export default function Dashboard() {
total_income: 0, total_income: 0,
total_expense: 0, total_expense: 0,
net_profit: 0, net_profit: 0,
low_stock_count: 0,
transactions: [] transactions: []
}); });
const [branches, setBranches] = useState([]); const [branches, setBranches] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [filterBranch, setFilterBranch] = useState(''); const [filterBranch, setFilterBranch] = useState('');
const [startDate, setStartDate] = useState(() => { const [currentPage, setCurrentPage] = useState(1);
const d = new Date(); const [paginationData, setPaginationData] = useState(null);
d.setMonth(d.getMonth() - 1); const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
return d.toISOString().split('T')[0];
});
const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]); const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]);
useEffect(() => { useEffect(() => {
@ -32,7 +31,8 @@ export default function Dashboard() {
const params = new URLSearchParams({ const params = new URLSearchParams({
branch_id: filterBranch, branch_id: filterBranch,
start_date: startDate, start_date: startDate,
end_date: endDate end_date: endDate,
page: currentPage
}); });
fetch(`/api/reports/profit?${params}`) fetch(`/api/reports/profit?${params}`)
.then(res => res.json()) .then(res => res.json())
@ -41,14 +41,21 @@ export default function Dashboard() {
total_income: data.total_income, total_income: data.total_income,
total_expense: data.total_expense, total_expense: data.total_expense,
net_profit: data.net_profit, net_profit: data.net_profit,
low_stock_count: data.low_stock_count,
transactions: data.transactions || [] transactions: data.transactions || []
}); });
setPaginationData(data.pagination);
setLoading(false); setLoading(false);
}) })
.catch(err => { .catch(err => {
console.error("Error fetching dashboard stats:", err); console.error("Error fetching dashboard stats:", err);
setLoading(false); setLoading(false);
}); });
}, [filterBranch, startDate, endDate, currentPage]);
// Reset page when filters change
useEffect(() => {
setCurrentPage(1);
}, [filterBranch, startDate, endDate]); }, [filterBranch, startDate, endDate]);
const formatCurrency = (val) => { const formatCurrency = (val) => {
@ -69,10 +76,14 @@ export default function Dashboard() {
<select <select
value={filterBranch} value={filterBranch}
onChange={(e) => setFilterBranch(e.target.value)} onChange={(e) => setFilterBranch(e.target.value)}
className="appearance-none pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-xl text-sm font-bold text-gray-600 focus:border-red-500/30 focus:ring-4 focus:ring-red-500/5 transition-all outline-none" className="appearance-none pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-xl text-sm font-bold text-gray-600 focus:border-red-500/30 focus:ring-4 focus:ring-red-500/5 transition-all outline-none max-w-[200px] truncate"
> >
<option value="">All Branches</option> <option value="">All Branches</option>
{branches.map(b => <option key={b.id} value={b.id}>{b.name}</option>)} {branches.map(b => (
<option key={b.id} value={b.id}>
{b.name.length > 30 ? b.name.substring(0, 30) + '...' : b.name}
</option>
))}
</select> </select>
<ChevronDown size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-red-500 transition-all" /> <ChevronDown size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-red-500 transition-all" />
</div> </div>
@ -104,33 +115,44 @@ export default function Dashboard() {
</div> </div>
{/* Stat Cards Grid */} {/* Stat Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<StatCard <StatCard
title="Aggregated amount of all credits" title="Total Income Collected"
subtitle="Total Credited" subtitle="Total Credits"
value={loading ? "..." : formatCurrency(stats.total_income)} value={loading ? "..." : formatCurrency(stats.total_income)}
color="green" color="green"
icon={DollarSign} icon={DollarSign}
/> />
<StatCard <StatCard
title="Aggregated amount of all debits" title="Total Expenses Paid"
subtitle="Total Debited" subtitle="Total Debits"
value={loading ? "..." : formatCurrency(stats.total_expense)} value={loading ? "..." : formatCurrency(stats.total_expense)}
color="red" color="red"
icon={TrendingDown} icon={TrendingDown}
/> />
<StatCard <StatCard
title={loading ? "Loading..." : formatCurrency(stats.net_profit)} title="Total Net Savings"
subtitle="Net Profit" subtitle="Net Profit"
value={loading ? "..." : formatCurrency(stats.net_profit)} value={loading ? "..." : formatCurrency(stats.net_profit)}
color="blue" color="blue"
icon={TrendingUp} icon={TrendingUp}
/> />
<StatCard
title="Items Below Reorder Level"
subtitle="Low Stock Products"
value={loading ? "..." : stats.low_stock_count}
color="red"
icon={TrendingDown}
/>
</div> </div>
{/* Main Content Area */} {/* Main Content Area */}
<div className="grid grid-cols-1 gap-8"> <div className="grid grid-cols-1 gap-8">
<AccountsTable data={stats.transactions} /> <AccountsTable
data={stats.transactions}
pagination={paginationData}
onPageChange={(p) => setCurrentPage(p)}
/>
</div> </div>
</main> </main>
</> </>

View File

@ -69,7 +69,9 @@ export default function ExpenseList() {
fetchExpenses(); fetchExpenses();
fetchMetadata(); fetchMetadata();
fetchPendingSalaries(); fetchPendingSalaries();
fetchPendingROIs(); if (!isReceptionist) {
fetchPendingROIs();
}
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -185,6 +187,9 @@ export default function ExpenseList() {
if (response.ok) { if (response.ok) {
setToast({ message: 'Salaries released successfully!', type: 'success' }); setToast({ message: 'Salaries released successfully!', type: 'success' });
setIsBulkModalOpen(false); setIsBulkModalOpen(false);
if (!isReceptionist) {
setFilterBranch('');
}
fetchPendingSalaries(); fetchPendingSalaries();
fetchExpenses(); fetchExpenses();
} else { } else {
@ -337,9 +342,10 @@ export default function ExpenseList() {
</div> </div>
</div> </div>
{/* Tabs */}
<div className="flex items-center gap-8 border-b border-gray-100 mb-6"> <div className="flex items-center gap-8 border-b border-gray-100 mb-6">
{['General Expenses', 'Pending Salaries', 'Pending ROIs', 'Salary Release History'].map(tab => ( {['General Expenses', 'Pending Salaries', 'Pending ROIs', 'Salary Release History']
.filter(tab => !isReceptionist || tab !== 'Pending ROIs')
.map(tab => (
<button <button
key={tab} key={tab}
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}

View File

@ -107,7 +107,7 @@ export default function AddProductModal({ isOpen, onClose, onSave, branches, cat
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Category</label> <label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Category</label>
<select <select
required required
className="w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none truncate" className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none truncate"
value={formData.product_category_id} value={formData.product_category_id}
onChange={e => setFormData({...formData, product_category_id: e.target.value})} onChange={e => setFormData({...formData, product_category_id: e.target.value})}
> >
@ -115,7 +115,7 @@ export default function AddProductModal({ isOpen, onClose, onSave, branches, cat
{categories.length === 0 && <option disabled className="whitespace-normal">No active categories found. Please add them in Masters.</option>} {categories.length === 0 && <option disabled className="whitespace-normal">No active categories found. Please add them in Masters.</option>}
{categories.map(c => ( {categories.map(c => (
<option key={c.id} value={c.id} className="whitespace-normal" title={c.name}> <option key={c.id} value={c.id} className="whitespace-normal" title={c.name}>
{c.name.length > 50 ? c.name.substring(0, 50) + '...' : c.name} {c.name.length > 40 ? c.name.substring(0, 40) + '...' : c.name}
</option> </option>
))} ))}
</select> </select>
@ -126,7 +126,7 @@ export default function AddProductModal({ isOpen, onClose, onSave, branches, cat
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Branch</label> <label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Branch</label>
<select <select
required required
className="w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none" className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-red-500/10 focus:border-red-500 transition-all font-bold text-gray-900 appearance-none truncate"
value={formData.branch_id} value={formData.branch_id}
onChange={e => setFormData({...formData, branch_id: e.target.value})} onChange={e => setFormData({...formData, branch_id: e.target.value})}
> >

View File

@ -107,7 +107,7 @@ export default function AdjustStockModal({ isOpen, onClose, onSave, product }) {
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Reason</label> <label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Reason</label>
<select <select
required required
className="w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-bold text-gray-900 appearance-none" className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-bold text-gray-900 appearance-none truncate"
value={formData.reason} value={formData.reason}
onChange={e => setFormData({...formData, reason: e.target.value})} onChange={e => setFormData({...formData, reason: e.target.value})}
> >

View File

@ -160,14 +160,18 @@ export default function NewSaleModal({ isOpen, onClose, onSave, branches, produc
</div> </div>
{window.__APP_DATA__?.role !== 'receptionist' && ( {window.__APP_DATA__?.role !== 'receptionist' && (
<select <select
className="px-5 py-3 bg-white border border-gray-100 rounded-2xl outline-none font-black text-[10px] uppercase tracking-widest cursor-pointer shadow-sm" className="px-5 py-3 bg-white border border-gray-100 rounded-2xl outline-none font-black text-[10px] uppercase tracking-widest cursor-pointer shadow-sm max-w-full truncate"
value={selectedBranch} value={selectedBranch}
onChange={e => { onChange={e => {
setSelectedBranch(e.target.value); setSelectedBranch(e.target.value);
setCart([]); setCart([]);
}} }}
> >
{branches.map(b => <option key={b.id} value={b.id}>{b.name}</option>)} {branches.map(b => (
<option key={b.id} value={b.id}>
{b.name.length > 30 ? b.name.substring(0, 30) + '...' : b.name}
</option>
))}
</select> </select>
)} )}
</div> </div>

View File

@ -18,16 +18,19 @@ import {
Clock, Clock,
Shield, Shield,
Building, Building,
AlertCircle AlertCircle,
RotateCcw
} from 'lucide-react'; } from 'lucide-react';
export default function ReportIndex() { export default function ReportIndex() {
const [activeTab, setActiveTab] = useState('Profit Report'); const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
const [activeTab, setActiveTab] = useState(isReceptionist ? 'Expense Report' : 'Profit Report');
const [profitData, setProfitData] = useState(null); const [profitData, setProfitData] = useState(null);
const [expiryReminders, setExpiryReminders] = useState([]); const [expiryReminders, setExpiryReminders] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [selectedBranch, setSelectedBranch] = useState(''); const [selectedBranch, setSelectedBranch] = useState(isReceptionist ? (window.__APP_DATA__?.branch?.id || '') : '');
const [startDate, setStartDate] = useState(''); const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState(''); const [endDate, setEndDate] = useState('');
const [branches, setBranches] = useState([]); const [branches, setBranches] = useState([]);
@ -43,13 +46,9 @@ export default function ReportIndex() {
const [salaryData, setSalaryData] = useState([]); const [salaryData, setSalaryData] = useState([]);
const [selectedItem, setSelectedItem] = useState(null); const [selectedItem, setSelectedItem] = useState(null);
const tabs = [ const tabs = isReceptionist
'Profit Report', 'Expense Report', 'Collection Report', ? ['Expense Report', 'Collection Report', 'Low Stock Report', 'Inventory Report', 'Product Sales', 'Expiry Reminders']
'Low Stock Report', 'Inventory Report', 'Product Sales', : ['Profit Report', 'Expense Report', 'Collection Report', 'Low Stock Report', 'Inventory Report', 'Product Sales', 'Investment Report', 'Salary Report', 'Expiry Reminders'];
'Investment Report', 'Salary Report', 'Expiry Reminders'
];
const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
const buildQueryString = () => { const buildQueryString = () => {
const params = new URLSearchParams(); const params = new URLSearchParams();
@ -59,6 +58,15 @@ export default function ReportIndex() {
return params.toString(); return params.toString();
}; };
const resetFilters = () => {
setStartDate('');
setEndDate('');
setSearchQuery('');
if (!isReceptionist) {
setSelectedBranch('');
}
};
useEffect(() => { useEffect(() => {
fetchMetadata(); fetchMetadata();
}, []); }, []);
@ -369,28 +377,32 @@ export default function ReportIndex() {
</select> </select>
</div> </div>
)} )}
{activeTab !== 'Expiry Reminders' && ( <div className="min-w-[160px]">
<> <label className="block text-[9px] font-black text-[#A3AED0] uppercase tracking-widest mb-1.5 ml-1">From Date</label>
<div className="min-w-[160px]"> <input
<label className="block text-[9px] font-black text-[#A3AED0] uppercase tracking-widest mb-1.5 ml-1">From Date</label> type="date"
<input className="w-full px-4 py-2.5 bg-[#F4F7FE] border-none rounded-xl text-sm font-bold text-[#1B254B] focus:ring-2 focus:ring-[#E31B1B]/20 outline-none"
type="date" value={startDate}
className="w-full px-4 py-2.5 bg-[#F4F7FE] border-none rounded-xl text-sm font-bold text-[#1B254B] focus:ring-2 focus:ring-[#E31B1B]/20 outline-none" onChange={(e) => setStartDate(e.target.value)}
value={startDate} />
onChange={(e) => setStartDate(e.target.value)} </div>
/> <div className="min-w-[160px]">
</div> <label className="block text-[9px] font-black text-[#A3AED0] uppercase tracking-widest mb-1.5 ml-1">To Date</label>
<div className="min-w-[160px]"> <input
<label className="block text-[9px] font-black text-[#A3AED0] uppercase tracking-widest mb-1.5 ml-1">To Date</label> type="date"
<input className="w-full px-4 py-2.5 bg-[#F4F7FE] border-none rounded-xl text-sm font-bold text-[#1B254B] focus:ring-2 focus:ring-[#E31B1B]/20 outline-none"
type="date" value={endDate}
className="w-full px-4 py-2.5 bg-[#F4F7FE] border-none rounded-xl text-sm font-bold text-[#1B254B] focus:ring-2 focus:ring-[#E31B1B]/20 outline-none" onChange={(e) => setEndDate(e.target.value)}
value={endDate} />
onChange={(e) => setEndDate(e.target.value)} </div>
/> <button
</div> onClick={resetFilters}
</> className="flex items-center gap-2 px-6 py-2.5 mt-auto bg-gray-50 text-gray-400 rounded-xl text-xs font-black uppercase tracking-widest hover:bg-gray-100 hover:text-[#1B254B] transition-all border border-gray-100 mb-0.5"
)} title="Reset Filters"
>
<RotateCcw size={14} />
Reset
</button>
</div> </div>
</div> </div>
@ -954,7 +966,24 @@ export default function ReportIndex() {
<td className="px-10 py-6 text-sm font-bold text-[#1B254B]">{s.payment_method}</td> <td className="px-10 py-6 text-sm font-bold text-[#1B254B]">{s.payment_method}</td>
<td className="px-10 py-6 text-right text-xs font-black text-[#A3AED0]">{parseFloat(s.subtotal_amount || 0).toFixed(2)} AED</td> <td className="px-10 py-6 text-right text-xs font-black text-[#A3AED0]">{parseFloat(s.subtotal_amount || 0).toFixed(2)} AED</td>
<td className="px-10 py-6 text-right text-xs font-black text-[#A3AED0]">{parseFloat(s.vat_amount || 0).toFixed(2)} AED</td> <td className="px-10 py-6 text-right text-xs font-black text-[#A3AED0]">{parseFloat(s.vat_amount || 0).toFixed(2)} AED</td>
<td className="px-10 py-6 text-right text-sm font-black text-emerald-500">{parseFloat(s.total_amount).toLocaleString()} AED</td> <td className="px-10 py-6 text-right text-sm font-black text-emerald-500">
<div className="flex flex-col items-end gap-1">
<span>{parseFloat(s.total_amount).toLocaleString()} AED</span>
{s.is_adjusted && (
<button
onClick={() => setSelectedItem({
transaction_id: s.transaction_id,
original_amount: s.original_amount,
amount: s.total_amount,
remarks: s.remarks
})}
className="px-2 py-0.5 bg-amber-50 text-amber-600 rounded-md text-[9px] font-black uppercase tracking-wider border border-amber-100 hover:bg-amber-100 transition-colors outline-none"
>
Adjusted
</button>
)}
</div>
</td>
</tr> </tr>
)) ))
) : ( ) : (

View File

@ -382,9 +382,20 @@ export default function StaffEdit({ id }) {
<option key={r.id} value={r.name}>{r.name}</option> <option key={r.id} value={r.name}>{r.name}</option>
))} ))}
</select> </select>
</div> </div>
<div> {!isReceptionist && (
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Joining Date *</label> <div>
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Branch *</label>
<select name="branch_id" value={formData.branch_id} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium appearance-none">
<option value="">Select Branch...</option>
{branches.map(branch => (
<option key={branch.id} value={branch.id}>{branch.name}</option>
))}
</select>
</div>
)}
<div>
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Joining Date *</label>
<input required type="date" name="joining_date" value={formData.joining_date} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" /> <input required type="date" name="joining_date" value={formData.joining_date} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" />
</div> </div>
<div> <div>

View File

@ -1,11 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
// Header and SubHeader are now part of the global Layout // Header and SubHeader are now part of the global Layout
import { import {
Wallet,
TrendingUp,
ArrowUpRight,
ArrowDownRight,
DollarSign, DollarSign,
TrendingUp,
Calendar,
ChevronDown,
Activity, Activity,
ShoppingCart, ShoppingCart,
Package, Package,
@ -23,11 +22,29 @@ export default function ReceptionistDashboard() {
}); });
const [transactions, setTransactions] = useState([]); const [transactions, setTransactions] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [filterBranch, setFilterBranch] = useState(window.__APP_DATA__?.branch?.id || '');
const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]);
const [branches, setBranches] = useState([]);
useEffect(() => {
// Fetch branches for the selector (even if disabled for receptionist)
fetch('/api/branches?status=Active')
.then(res => res.json())
.then(data => setBranches(data))
.catch(err => console.error("Error fetching branches:", err));
}, []);
useEffect(() => { useEffect(() => {
const fetchDashboardData = async () => { const fetchDashboardData = async () => {
setLoading(true);
try { try {
const response = await fetch('/api/reports/profit'); const query = new URLSearchParams({
branch_id: filterBranch,
start_date: startDate,
end_date: endDate
});
const response = await fetch(`/api/reports/profit?${query}`);
const data = await response.json(); const data = await response.json();
setStats({ setStats({
@ -45,7 +62,7 @@ export default function ReceptionistDashboard() {
}; };
fetchDashboardData(); fetchDashboardData();
}, []); }, [filterBranch, startDate, endDate]);
const StatCard = ({ title, amount, icon: Icon, color, trend, iconColor, bgColor, textColor, label }) => ( const StatCard = ({ title, amount, icon: Icon, color, trend, iconColor, bgColor, textColor, label }) => (
<div className={`${bgColor} p-6 rounded-[1.5rem] shadow-sm relative overflow-hidden group`}> <div className={`${bgColor} p-6 rounded-[1.5rem] shadow-sm relative overflow-hidden group`}>
@ -103,11 +120,56 @@ export default function ReceptionistDashboard() {
<div className="animate-in fade-in duration-700"> <div className="animate-in fade-in duration-700">
<main className="p-8 max-w-[1600px] mx-auto space-y-12 animate-in fade-in duration-700"> <main className="p-8 max-w-[1600px] mx-auto space-y-12 animate-in fade-in duration-700">
{/* Welcome Section */} {/* Welcome Section & Filters */}
<div className="flex flex-col gap-2"> <div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<h1 className="text-4xl font-black text-gray-900 tracking-tight flex items-center gap-4"> <div className="flex flex-col gap-2">
Receptionist Dashboard <h1 className="text-4xl font-black text-gray-900 tracking-tight flex items-center gap-4">
</h1> Receptionist Dashboard
</h1>
<p className="text-sm font-bold text-gray-400 uppercase tracking-widest">Branch Operations Overview</p>
</div>
<div className="flex items-center gap-3">
{/* Branch Selector (Hardcoded to their branch for receptionists) */}
<div className="relative group">
<select
value={filterBranch}
disabled={true}
className="appearance-none pl-4 pr-10 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm font-bold text-gray-400 cursor-not-allowed outline-none max-w-[200px] truncate"
>
{branches.map(b => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
</select>
<ChevronDown size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-300" />
</div>
{/* Date Range */}
<div className="flex items-center bg-white border border-gray-200 rounded-xl p-1 gap-1">
<div className="flex items-center gap-2 px-3 py-1.5 text-xs font-bold text-gray-500 border-r border-gray-100">
<span className="text-gray-400 font-medium whitespace-nowrap">From</span>
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="bg-transparent border-none p-0 focus:ring-0 text-gray-700 outline-none w-28"
/>
<Calendar size={14} className="text-gray-400" />
</div>
<div className="flex items-center gap-2 px-3 py-1.5 text-xs font-bold text-gray-500">
<span className="text-gray-400 font-medium whitespace-nowrap">To</span>
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="bg-transparent border-none p-0 focus:ring-0 text-gray-700 outline-none w-28"
/>
<Calendar size={14} className="text-gray-400" />
</div>
</div>
</div>
</div> </div>
{/* Stats Grid */} {/* Stats Grid */}
@ -188,7 +250,7 @@ export default function ReceptionistDashboard() {
</thead> </thead>
<tbody className="divide-y divide-gray-50"> <tbody className="divide-y divide-gray-50">
{transactions.length > 0 ? ( {transactions.length > 0 ? (
transactions.map((tx, idx) => ( transactions.slice(0, 10).map((tx, idx) => (
<tr key={idx} className="hover:bg-gray-50/30 transition-colors"> <tr key={idx} className="hover:bg-gray-50/30 transition-colors">
<td className="px-8 py-5"> <td className="px-8 py-5">
<div className="flex flex-col"> <div className="flex flex-col">
@ -201,15 +263,15 @@ export default function ReceptionistDashboard() {
</td> </td>
<td className="px-8 py-5"> <td className="px-8 py-5">
<span className={`px-2.5 py-1 rounded-full text-[10px] font-black shadow-sm uppercase tracking-wider ${ <span className={`px-2.5 py-1 rounded-full text-[10px] font-black shadow-sm uppercase tracking-wider ${
tx.debit > 0 ? 'bg-red-50 text-red-600' : 'bg-emerald-50 text-emerald-600' tx.type === 'Expense' ? 'bg-red-50 text-red-600' : 'bg-emerald-50 text-emerald-600'
}`}> }`}>
{tx.debit > 0 ? 'DEBITED' : 'RECEIVED'} {tx.type === 'Expense' ? 'DEBITED' : 'RECEIVED'}
</span> </span>
</td> </td>
<td className="px-8 py-5 text-right"> <td className="px-8 py-5 text-right">
<span className={`text-sm font-black ${tx.debit > 0 ? 'text-red-500' : 'text-emerald-500'}`}> <span className={`text-sm font-black ${tx.type === 'Expense' ? 'text-red-500' : 'text-emerald-500'}`}>
{tx.debit > 0 ? '-' : '+'} {tx.type === 'Expense' ? '-' : '+'}
{(tx.debit || tx.credit || 0).toLocaleString('en-AE', { minimumFractionDigits: 2 })} {(tx.amount || 0).toLocaleString('en-AE', { minimumFractionDigits: 2 })}
<span className="text-[10px] ml-1 uppercase">AED</span> <span className="text-[10px] ml-1 uppercase">AED</span>
</span> </span>
</td> </td>

View File

@ -28,6 +28,7 @@ export default function POS() {
const [branches, setBranches] = useState([]); const [branches, setBranches] = useState([]);
const [selectedBranch, setSelectedBranch] = useState(window.__APP_DATA__?.user?.branch_id || ''); const [selectedBranch, setSelectedBranch] = useState(window.__APP_DATA__?.user?.branch_id || '');
const [toast, setToast] = useState(null); const [toast, setToast] = useState(null);
const [showSuccessModal, setShowSuccessModal] = useState(false);
const showToast = (message, type = 'success') => { const showToast = (message, type = 'success') => {
setToast({ message, type }); setToast({ message, type });
@ -168,6 +169,7 @@ export default function POS() {
if (response.ok) { if (response.ok) {
setSuccess(true); setSuccess(true);
setShowSuccessModal(true);
setCart([]); setCart([]);
setAdjustmentRemarks(''); setAdjustmentRemarks('');
setTimeout(() => setSuccess(false), 3000); setTimeout(() => setSuccess(false), 3000);
@ -416,6 +418,25 @@ export default function POS() {
</div> </div>
</div> </div>
</main> </main>
{/* Success Modal */}
{showSuccessModal && (
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-gray-900/60 backdrop-blur-sm animate-in fade-in duration-300">
<div className="bg-white rounded-[2.5rem] p-10 max-w-md w-full mx-4 shadow-2xl border border-gray-100 animate-in zoom-in-95 duration-300 text-center">
<div className="w-20 h-20 bg-emerald-50 rounded-full flex items-center justify-center mx-auto mb-6 text-emerald-500 shadow-inner">
<CheckCircle2 size={40} />
</div>
<h3 className="text-2xl font-black text-gray-900 mb-2">Sale Successful!</h3>
<p className="text-gray-500 font-bold mb-8">The transaction has been processed and recorded successfully.</p>
<button
onClick={() => setShowSuccessModal(false)}
className="w-full py-4 bg-gray-900 text-white rounded-2xl font-black uppercase tracking-widest text-xs hover:bg-gray-800 transition-all active:scale-95 shadow-lg shadow-gray-200"
>
Got it, thanks!
</button>
</div>
</div>
)}
</> </>
); );
} }

View File

@ -13,8 +13,8 @@ export default function ReceptionistReportIndex() {
const [activeTab, setActiveTab] = useState('Collections'); const [activeTab, setActiveTab] = useState('Collections');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [fromDate, setFromDate] = useState('2026-02-06'); // Defaulting based on screenshot const [fromDate, setFromDate] = useState(new Date().toISOString().split('T')[0]);
const [toDate, setToDate] = useState('2026-03-08'); const [toDate, setToDate] = useState(new Date().toISOString().split('T')[0]);
const [method, setMethod] = useState('All Methods'); const [method, setMethod] = useState('All Methods');
const [type, setType] = useState('All Types'); const [type, setType] = useState('All Types');
const [expenseType, setExpenseType] = useState('All Types'); const [expenseType, setExpenseType] = useState('All Types');

View File

@ -169,7 +169,7 @@ function MainApp() {
const id = path.split('/').pop(); const id = path.split('/').pop();
component = <InvestorView id={id} />; component = <InvestorView id={id} />;
} else if (path === '/receptionist/reports') { } else if (path === '/receptionist/reports') {
component = <ReceptionistReportIndex />; component = <ReportIndex />;
} }
if (component) { if (component) {

View File

@ -118,5 +118,8 @@
Route::get('/receptionist/expenses', [OwnerController::class, 'index']); Route::get('/receptionist/expenses', [OwnerController::class, 'index']);
Route::get('/receptionist/inventory', [OwnerController::class, 'index']); Route::get('/receptionist/inventory', [OwnerController::class, 'index']);
Route::get('/receptionist/staff', [OwnerController::class, 'index']); Route::get('/receptionist/staff', [OwnerController::class, 'index']);
Route::get('/receptionist/staff/add', [OwnerController::class, 'index']);
Route::get('/receptionist/staff/edit/{id}', [OwnerController::class, 'index']);
Route::get('/receptionist/staff/view/{id}', [OwnerController::class, 'index']);
Route::get('/receptionist/reports', [OwnerController::class, 'index']); Route::get('/receptionist/reports', [OwnerController::class, 'index']);
}); });