updates 2
This commit is contained in:
parent
e1e55959e1
commit
50ba5c23e8
@ -17,7 +17,7 @@ public function index(Request $request)
|
||||
}
|
||||
$branches = $query->get();
|
||||
|
||||
// Attach is_deletable flag to each branch
|
||||
// Attach is_deletable flag and total revenue to each branch
|
||||
$branches->each(function ($branch) {
|
||||
$inUse = \App\Models\Staff::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\Receptionist::where('branch_id', $branch->id)->exists();
|
||||
$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);
|
||||
@ -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')]);
|
||||
}
|
||||
|
||||
|
||||
@ -104,8 +104,18 @@ public function getSalaryHistory(Request $request)
|
||||
$query = Expense::with(['branch'])
|
||||
->where('expense_category_id', $salaryCategory->id);
|
||||
|
||||
if ($user->isReceptionist()) {
|
||||
$query->where('branch_id', $user->branch_id);
|
||||
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('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();
|
||||
|
||||
@ -145,7 +145,14 @@ public function getSales(Request $request)
|
||||
$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)
|
||||
|
||||
@ -22,8 +22,12 @@ public function getProfitReport(Request $request)
|
||||
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
|
||||
$startDate = $request->query('start_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) {
|
||||
$query->where('branch_id', $branchId);
|
||||
}
|
||||
@ -34,24 +38,18 @@ public function getProfitReport(Request $request)
|
||||
$query->where('date', '<=', $endDate);
|
||||
}
|
||||
|
||||
// Stats from the same filtered query
|
||||
$totalCredits = (clone $query)->sum('credit');
|
||||
$totalDebits = (clone $query)->sum('debit');
|
||||
|
||||
// Fetch All Ledger Transactions for the breakdown
|
||||
$accounts = Account::where(function($q) {
|
||||
// Fetch Paginated Transactions from the same filtered query
|
||||
$allTransactions = $query->where(function($q) {
|
||||
$q->where('credit', '>', 0)->orWhere('debit', '>', 0);
|
||||
});
|
||||
|
||||
if ($branchId) {
|
||||
$accounts->where('branch_id', $branchId);
|
||||
}
|
||||
if ($startDate) {
|
||||
$accounts->where('date', '>=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$accounts->where('date', '<=', $endDate);
|
||||
}
|
||||
$accounts = $accounts->get()
|
||||
})
|
||||
->orderBy('date', 'desc')
|
||||
->orderBy('time', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->get()
|
||||
->map(function($a) {
|
||||
$isAdjusted = false;
|
||||
$originalAmount = $a->credit > 0 ? $a->credit : $a->debit;
|
||||
@ -68,41 +66,22 @@ public function getProfitReport(Request $request)
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $a->id,
|
||||
'date' => $a->date,
|
||||
'time' => $a->time || '00:00:00',
|
||||
'type' => $a->credit > 0 ? 'Income' : 'Expense',
|
||||
'category' => $a->type,
|
||||
'description' => $a->description,
|
||||
'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,
|
||||
'original_amount' => $originalAmount,
|
||||
'remarks' => $remarks
|
||||
];
|
||||
});
|
||||
|
||||
$expensesQuery = Expense::with('category', 'branch');
|
||||
if ($branchId) {
|
||||
$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();
|
||||
$totalTransactions = $allTransactions->count();
|
||||
$paginatedTransactions = $allTransactions->forPage($page, $perPage)->values();
|
||||
|
||||
$lowStockCount = \App\Models\Product::query();
|
||||
if ($branchId) {
|
||||
@ -121,9 +100,11 @@ public function getProfitReport(Request $request)
|
||||
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
|
||||
->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()])
|
||||
->sum('amount');
|
||||
->sum('debit');
|
||||
|
||||
$trend[] = [
|
||||
'month' => $monthStart->format('M'),
|
||||
@ -139,7 +120,13 @@ public function getProfitReport(Request $request)
|
||||
'total_expense' => $totalDebits,
|
||||
'net_profit' => $totalCredits - $totalDebits,
|
||||
'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
|
||||
]);
|
||||
}
|
||||
@ -151,6 +138,8 @@ public function getExpiryReminders(Request $request)
|
||||
if (!$user) return response()->json(['message' => 'Unauthenticated'], 401);
|
||||
|
||||
$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')
|
||||
->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()
|
||||
->filter(function($doc) use ($today) {
|
||||
$expiryDate = Carbon::parse($doc->expiry_date)->startOfDay();
|
||||
@ -192,6 +188,13 @@ public function getExpiryReminders(Request $request)
|
||||
$branchDocsQuery->where('branch_id', $branchId);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$branchDocsQuery->where('expiry_date', '>=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$branchDocsQuery->where('expiry_date', '<=', $endDate);
|
||||
}
|
||||
|
||||
$branchReminders = $branchDocsQuery->get()
|
||||
->filter(function($doc) use ($today) {
|
||||
$expiryDate = Carbon::parse($doc->expiry_date);
|
||||
@ -216,7 +219,7 @@ public function getExpiryReminders(Request $request)
|
||||
});
|
||||
|
||||
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([
|
||||
'total_invested' => $totalInvested,
|
||||
'total_roi_returned' => $totalROIReturned,
|
||||
'investors' => $reportData
|
||||
'investors' => collect($reportData)->sortByDesc('investment_date')->values()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ export default function List() {
|
||||
{ header: 'Manager', key: 'manager_name' },
|
||||
{
|
||||
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',
|
||||
|
||||
@ -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>
|
||||
<select
|
||||
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}
|
||||
onChange={e => setFormData({...formData, branch_id: e.target.value, items: [], amount: ''})}
|
||||
>
|
||||
<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>
|
||||
</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>
|
||||
<select
|
||||
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}
|
||||
onChange={e => {
|
||||
const typeId = e.target.value;
|
||||
@ -250,7 +254,11 @@ export default function AddCollectionModal({ isOpen, onClose, onSave, branches,
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</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>
|
||||
<select
|
||||
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}
|
||||
onChange={e => setFormData({...formData, payment_method: e.target.value})}
|
||||
>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { MoreVertical, ArrowDownRight, ArrowUpRight } from 'lucide-react';
|
||||
|
||||
export default function AccountsTable({ data = [] }) {
|
||||
import { MoreVertical, ArrowDownRight, ArrowUpRight, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
export default function AccountsTable({ data = [], pagination = null, onPageChange }) {
|
||||
const formatCurrency = (val) => {
|
||||
return new Intl.NumberFormat('en-AE', { style: 'currency', currency: 'AED' }).format(val);
|
||||
};
|
||||
@ -68,6 +68,44 @@ export default function AccountsTable({ data = [] }) {
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,16 +8,15 @@ export default function Dashboard() {
|
||||
total_income: 0,
|
||||
total_expense: 0,
|
||||
net_profit: 0,
|
||||
low_stock_count: 0,
|
||||
transactions: []
|
||||
});
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filterBranch, setFilterBranch] = useState('');
|
||||
const [startDate, setStartDate] = useState(() => {
|
||||
const d = new Date();
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
return d.toISOString().split('T')[0];
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [paginationData, setPaginationData] = useState(null);
|
||||
const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -32,7 +31,8 @@ export default function Dashboard() {
|
||||
const params = new URLSearchParams({
|
||||
branch_id: filterBranch,
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
end_date: endDate,
|
||||
page: currentPage
|
||||
});
|
||||
fetch(`/api/reports/profit?${params}`)
|
||||
.then(res => res.json())
|
||||
@ -41,14 +41,21 @@ export default function Dashboard() {
|
||||
total_income: data.total_income,
|
||||
total_expense: data.total_expense,
|
||||
net_profit: data.net_profit,
|
||||
low_stock_count: data.low_stock_count,
|
||||
transactions: data.transactions || []
|
||||
});
|
||||
setPaginationData(data.pagination);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Error fetching dashboard stats:", err);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [filterBranch, startDate, endDate, currentPage]);
|
||||
|
||||
// Reset page when filters change
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [filterBranch, startDate, endDate]);
|
||||
|
||||
const formatCurrency = (val) => {
|
||||
@ -69,10 +76,14 @@ export default function Dashboard() {
|
||||
<select
|
||||
value={filterBranch}
|
||||
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>
|
||||
{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>
|
||||
<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>
|
||||
@ -104,33 +115,44 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
title="Aggregated amount of all credits"
|
||||
subtitle="Total Credited"
|
||||
title="Total Income Collected"
|
||||
subtitle="Total Credits"
|
||||
value={loading ? "..." : formatCurrency(stats.total_income)}
|
||||
color="green"
|
||||
icon={DollarSign}
|
||||
/>
|
||||
<StatCard
|
||||
title="Aggregated amount of all debits"
|
||||
subtitle="Total Debited"
|
||||
title="Total Expenses Paid"
|
||||
subtitle="Total Debits"
|
||||
value={loading ? "..." : formatCurrency(stats.total_expense)}
|
||||
color="red"
|
||||
icon={TrendingDown}
|
||||
/>
|
||||
<StatCard
|
||||
title={loading ? "Loading..." : formatCurrency(stats.net_profit)}
|
||||
title="Total Net Savings"
|
||||
subtitle="Net Profit"
|
||||
value={loading ? "..." : formatCurrency(stats.net_profit)}
|
||||
color="blue"
|
||||
icon={TrendingUp}
|
||||
/>
|
||||
<StatCard
|
||||
title="Items Below Reorder Level"
|
||||
subtitle="Low Stock Products"
|
||||
value={loading ? "..." : stats.low_stock_count}
|
||||
color="red"
|
||||
icon={TrendingDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
<AccountsTable data={stats.transactions} />
|
||||
<AccountsTable
|
||||
data={stats.transactions}
|
||||
pagination={paginationData}
|
||||
onPageChange={(p) => setCurrentPage(p)}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
|
||||
@ -69,7 +69,9 @@ export default function ExpenseList() {
|
||||
fetchExpenses();
|
||||
fetchMetadata();
|
||||
fetchPendingSalaries();
|
||||
fetchPendingROIs();
|
||||
if (!isReceptionist) {
|
||||
fetchPendingROIs();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -185,6 +187,9 @@ export default function ExpenseList() {
|
||||
if (response.ok) {
|
||||
setToast({ message: 'Salaries released successfully!', type: 'success' });
|
||||
setIsBulkModalOpen(false);
|
||||
if (!isReceptionist) {
|
||||
setFilterBranch('');
|
||||
}
|
||||
fetchPendingSalaries();
|
||||
fetchExpenses();
|
||||
} else {
|
||||
@ -337,9 +342,10 @@ export default function ExpenseList() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<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
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
|
||||
@ -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>
|
||||
<select
|
||||
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}
|
||||
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.map(c => (
|
||||
<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>
|
||||
))}
|
||||
</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>
|
||||
<select
|
||||
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}
|
||||
onChange={e => setFormData({...formData, branch_id: e.target.value})}
|
||||
>
|
||||
|
||||
@ -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>
|
||||
<select
|
||||
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}
|
||||
onChange={e => setFormData({...formData, reason: e.target.value})}
|
||||
>
|
||||
|
||||
@ -160,14 +160,18 @@ export default function NewSaleModal({ isOpen, onClose, onSave, branches, produc
|
||||
</div>
|
||||
{window.__APP_DATA__?.role !== 'receptionist' && (
|
||||
<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}
|
||||
onChange={e => {
|
||||
setSelectedBranch(e.target.value);
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -18,16 +18,19 @@ import {
|
||||
Clock,
|
||||
Shield,
|
||||
Building,
|
||||
AlertCircle
|
||||
AlertCircle,
|
||||
RotateCcw
|
||||
} from 'lucide-react';
|
||||
|
||||
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 [expiryReminders, setExpiryReminders] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedBranch, setSelectedBranch] = useState('');
|
||||
const [selectedBranch, setSelectedBranch] = useState(isReceptionist ? (window.__APP_DATA__?.branch?.id || '') : '');
|
||||
const [startDate, setStartDate] = useState('');
|
||||
const [endDate, setEndDate] = useState('');
|
||||
const [branches, setBranches] = useState([]);
|
||||
@ -43,13 +46,9 @@ export default function ReportIndex() {
|
||||
const [salaryData, setSalaryData] = useState([]);
|
||||
const [selectedItem, setSelectedItem] = useState(null);
|
||||
|
||||
const tabs = [
|
||||
'Profit Report', 'Expense Report', 'Collection Report',
|
||||
'Low Stock Report', 'Inventory Report', 'Product Sales',
|
||||
'Investment Report', 'Salary Report', 'Expiry Reminders'
|
||||
];
|
||||
|
||||
const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
|
||||
const tabs = isReceptionist
|
||||
? ['Expense Report', 'Collection Report', 'Low Stock Report', 'Inventory Report', 'Product Sales', 'Expiry Reminders']
|
||||
: ['Profit Report', 'Expense Report', 'Collection Report', 'Low Stock Report', 'Inventory Report', 'Product Sales', 'Investment Report', 'Salary Report', 'Expiry Reminders'];
|
||||
|
||||
const buildQueryString = () => {
|
||||
const params = new URLSearchParams();
|
||||
@ -59,6 +58,15 @@ export default function ReportIndex() {
|
||||
return params.toString();
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
setStartDate('');
|
||||
setEndDate('');
|
||||
setSearchQuery('');
|
||||
if (!isReceptionist) {
|
||||
setSelectedBranch('');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchMetadata();
|
||||
}, []);
|
||||
@ -369,28 +377,32 @@ export default function ReportIndex() {
|
||||
</select>
|
||||
</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>
|
||||
<input
|
||||
type="date"
|
||||
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"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-[160px]">
|
||||
<label className="block text-[9px] font-black text-[#A3AED0] uppercase tracking-widest mb-1.5 ml-1">To Date</label>
|
||||
<input
|
||||
type="date"
|
||||
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"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<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>
|
||||
<input
|
||||
type="date"
|
||||
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"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-[160px]">
|
||||
<label className="block text-[9px] font-black text-[#A3AED0] uppercase tracking-widest mb-1.5 ml-1">To Date</label>
|
||||
<input
|
||||
type="date"
|
||||
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"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
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>
|
||||
|
||||
@ -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-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-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>
|
||||
))
|
||||
) : (
|
||||
|
||||
@ -382,9 +382,20 @@ export default function StaffEdit({ id }) {
|
||||
<option key={r.id} value={r.name}>{r.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Joining Date *</label>
|
||||
</div>
|
||||
{!isReceptionist && (
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
// Header and SubHeader are now part of the global Layout
|
||||
import {
|
||||
Wallet,
|
||||
TrendingUp,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
DollarSign,
|
||||
TrendingUp,
|
||||
Calendar,
|
||||
ChevronDown,
|
||||
Activity,
|
||||
ShoppingCart,
|
||||
Package,
|
||||
@ -23,11 +22,29 @@ export default function ReceptionistDashboard() {
|
||||
});
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
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(() => {
|
||||
const fetchDashboardData = async () => {
|
||||
setLoading(true);
|
||||
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();
|
||||
|
||||
setStats({
|
||||
@ -45,7 +62,7 @@ export default function ReceptionistDashboard() {
|
||||
};
|
||||
|
||||
fetchDashboardData();
|
||||
}, []);
|
||||
}, [filterBranch, startDate, endDate]);
|
||||
|
||||
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`}>
|
||||
@ -103,11 +120,56 @@ export default function ReceptionistDashboard() {
|
||||
<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">
|
||||
{/* Welcome Section */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-4xl font-black text-gray-900 tracking-tight flex items-center gap-4">
|
||||
Receptionist Dashboard
|
||||
</h1>
|
||||
{/* Welcome Section & Filters */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-4xl font-black text-gray-900 tracking-tight flex items-center gap-4">
|
||||
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>
|
||||
|
||||
{/* Stats Grid */}
|
||||
@ -188,7 +250,7 @@ export default function ReceptionistDashboard() {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
{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">
|
||||
<td className="px-8 py-5">
|
||||
<div className="flex flex-col">
|
||||
@ -201,15 +263,15 @@ export default function ReceptionistDashboard() {
|
||||
</td>
|
||||
<td className="px-8 py-5">
|
||||
<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>
|
||||
</td>
|
||||
<td className="px-8 py-5 text-right">
|
||||
<span className={`text-sm font-black ${tx.debit > 0 ? 'text-red-500' : 'text-emerald-500'}`}>
|
||||
{tx.debit > 0 ? '-' : '+'}
|
||||
{(tx.debit || tx.credit || 0).toLocaleString('en-AE', { minimumFractionDigits: 2 })}
|
||||
<span className={`text-sm font-black ${tx.type === 'Expense' ? 'text-red-500' : 'text-emerald-500'}`}>
|
||||
{tx.type === 'Expense' ? '-' : '+'}
|
||||
{(tx.amount || 0).toLocaleString('en-AE', { minimumFractionDigits: 2 })}
|
||||
<span className="text-[10px] ml-1 uppercase">AED</span>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@ -28,6 +28,7 @@ export default function POS() {
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [selectedBranch, setSelectedBranch] = useState(window.__APP_DATA__?.user?.branch_id || '');
|
||||
const [toast, setToast] = useState(null);
|
||||
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
||||
|
||||
const showToast = (message, type = 'success') => {
|
||||
setToast({ message, type });
|
||||
@ -168,6 +169,7 @@ export default function POS() {
|
||||
|
||||
if (response.ok) {
|
||||
setSuccess(true);
|
||||
setShowSuccessModal(true);
|
||||
setCart([]);
|
||||
setAdjustmentRemarks('');
|
||||
setTimeout(() => setSuccess(false), 3000);
|
||||
@ -416,6 +418,25 @@ export default function POS() {
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ export default function ReceptionistReportIndex() {
|
||||
const [activeTab, setActiveTab] = useState('Collections');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [fromDate, setFromDate] = useState('2026-02-06'); // Defaulting based on screenshot
|
||||
const [toDate, setToDate] = useState('2026-03-08');
|
||||
const [fromDate, setFromDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [toDate, setToDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [method, setMethod] = useState('All Methods');
|
||||
const [type, setType] = useState('All Types');
|
||||
const [expenseType, setExpenseType] = useState('All Types');
|
||||
|
||||
@ -169,7 +169,7 @@ function MainApp() {
|
||||
const id = path.split('/').pop();
|
||||
component = <InvestorView id={id} />;
|
||||
} else if (path === '/receptionist/reports') {
|
||||
component = <ReceptionistReportIndex />;
|
||||
component = <ReportIndex />;
|
||||
}
|
||||
|
||||
if (component) {
|
||||
|
||||
@ -118,5 +118,8 @@
|
||||
Route::get('/receptionist/expenses', [OwnerController::class, 'index']);
|
||||
Route::get('/receptionist/inventory', [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']);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user