update 3
This commit is contained in:
parent
50ba5c23e8
commit
e5c47e177b
@ -111,19 +111,9 @@ public function update(Request $request, $id)
|
||||
]);
|
||||
|
||||
if ($validated['status'] === 'Inactive') {
|
||||
$staffAction = $request->input('staff_action');
|
||||
if ($staffAction === 'move') {
|
||||
$targetBranchId = $request->input('move_to_branch_id');
|
||||
if ($targetBranchId) {
|
||||
\App\Models\Staff::where('branch_id', $id)
|
||||
->where('status', 'Active')
|
||||
->update(['branch_id' => $targetBranchId]);
|
||||
}
|
||||
} elseif ($staffAction === 'inactivate') {
|
||||
\App\Models\Staff::where('branch_id', $id)
|
||||
->where('status', 'Active')
|
||||
->update(['status' => 'Inactive']);
|
||||
}
|
||||
\App\Models\Staff::where('branch_id', $id)
|
||||
->where('status', 'Active')
|
||||
->update(['status' => 'Inactive']);
|
||||
}
|
||||
|
||||
// Process new documents if provided
|
||||
|
||||
@ -26,7 +26,7 @@ public function getProfitReport(Request $request)
|
||||
$perPage = $request->query('per_page', 10);
|
||||
|
||||
// Base Query from Account table (Ledger)
|
||||
$query = Account::query()->with('accountable');
|
||||
$query = Account::query()->with(['accountable', 'branch']);
|
||||
|
||||
if ($branchId) {
|
||||
$query->where('branch_id', $branchId);
|
||||
@ -41,6 +41,19 @@ public function getProfitReport(Request $request)
|
||||
// Stats from the same filtered query
|
||||
$totalCredits = (clone $query)->sum('credit');
|
||||
$totalDebits = (clone $query)->sum('debit');
|
||||
|
||||
// Today's Stats (always based on today's date, but respecting branch filter)
|
||||
$todayIncome = Account::where('date', Carbon::today()->toDateString())
|
||||
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
||||
->sum('credit');
|
||||
|
||||
// Monthly Stats (always based on current month, but respecting branch filter)
|
||||
$monthlyExpense = Account::whereBetween('date', [
|
||||
Carbon::now()->startOfMonth()->toDateString(),
|
||||
Carbon::now()->endOfMonth()->toDateString()
|
||||
])
|
||||
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
||||
->sum('debit');
|
||||
|
||||
// Fetch Paginated Transactions from the same filtered query
|
||||
$allTransactions = $query->where(function($q) {
|
||||
@ -73,7 +86,7 @@ public function getProfitReport(Request $request)
|
||||
'category' => $a->type,
|
||||
'description' => $a->description,
|
||||
'amount' => $a->credit > 0 ? $a->credit : $a->debit,
|
||||
'branch' => 'N/A', // Branch name if needed can be added via relation
|
||||
'branch' => $a->branch->name ?? 'N/A',
|
||||
'is_adjusted' => $isAdjusted,
|
||||
'original_amount' => $originalAmount,
|
||||
'remarks' => $remarks
|
||||
@ -95,13 +108,12 @@ public function getProfitReport(Request $request)
|
||||
$monthStart = Carbon::now()->subMonths($i)->startOfMonth();
|
||||
$monthEnd = Carbon::now()->subMonths($i)->endOfMonth();
|
||||
|
||||
$monthIncome = Account::where('branch_id', $branchId ?: '!=', 0)
|
||||
$monthIncome = Account::query()
|
||||
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
|
||||
->sum('credit');
|
||||
|
||||
// For trend, we can also use Account table for expenses
|
||||
$monthExpense = Account::where('branch_id', $branchId ?: '!=', 0)
|
||||
$monthExpense = Account::query()
|
||||
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
|
||||
->sum('debit');
|
||||
@ -118,6 +130,8 @@ public function getProfitReport(Request $request)
|
||||
return response()->json([
|
||||
'total_income' => $totalCredits,
|
||||
'total_expense' => $totalDebits,
|
||||
'today_income' => $todayIncome,
|
||||
'monthly_expense' => $monthlyExpense,
|
||||
'net_profit' => $totalCredits - $totalDebits,
|
||||
'low_stock_count' => $lowStockCount,
|
||||
'transactions' => $paginatedTransactions,
|
||||
|
||||
@ -380,13 +380,36 @@ public function getSettlementDetails(Request $request, $id)
|
||||
if ($user && $user->isReceptionist() && $staff->branch_id != $user->branch_id) {
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
$targetMonthKey = $request->query('month');
|
||||
$targetMonthKey = $request->input('month') ?? $request->query('month') ?? $request->input('settlement_month');
|
||||
$branch = $staff->branch;
|
||||
|
||||
// Find the next unpaid month logically for chronological check
|
||||
$lastPayment = \App\Models\StaffPayment::where('staff_id', $id)
|
||||
->where('payment_type', 'Salary Settlement')
|
||||
->orderBy('settlement_month', 'desc')
|
||||
->first();
|
||||
|
||||
$joiningDate = Carbon::parse($staff->joining_date);
|
||||
if ($lastPayment) {
|
||||
$logicalNextMonth = Carbon::parse($lastPayment->settlement_month . '-01')->addMonth();
|
||||
} else {
|
||||
$logicalNextMonth = $joiningDate->copy()->startOfMonth();
|
||||
}
|
||||
|
||||
if ($targetMonthKey) {
|
||||
$nextSettlementMonth = Carbon::parse($targetMonthKey . '-01');
|
||||
$requestedMonth = Carbon::parse($targetMonthKey . '-01');
|
||||
|
||||
// Verify if already paid
|
||||
// 1. Chronological Check: Cannot settle a future month if a previous one is unpaid
|
||||
if ($requestedMonth->startOfMonth()->greaterThan($logicalNextMonth->startOfMonth())) {
|
||||
return response()->json([
|
||||
'can_settle' => false,
|
||||
'message' => "Please settle " . $logicalNextMonth->format('F Y') . " before settling " . $requestedMonth->format('F Y') . ".",
|
||||
]);
|
||||
}
|
||||
|
||||
$nextSettlementMonth = $requestedMonth;
|
||||
|
||||
// 2. Verify if already paid
|
||||
$existing = \App\Models\StaffPayment::where('staff_id', $id)
|
||||
->where('payment_type', 'Salary Settlement')
|
||||
->where('settlement_month', $targetMonthKey)
|
||||
@ -399,31 +422,15 @@ public function getSettlementDetails(Request $request, $id)
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Find the next unpaid month logically
|
||||
$lastPayment = \App\Models\StaffPayment::where('staff_id', $id)
|
||||
->where('payment_type', 'Salary Settlement')
|
||||
->orderBy('settlement_month', 'desc')
|
||||
->first();
|
||||
$nextSettlementMonth = $logicalNextMonth;
|
||||
}
|
||||
|
||||
$joiningDate = Carbon::parse($staff->joining_date);
|
||||
$currentMonth = Carbon::now()->startOfMonth();
|
||||
|
||||
if ($lastPayment) {
|
||||
$nextSettlementMonth = Carbon::parse($lastPayment->settlement_month . '-01')->addMonth();
|
||||
} else {
|
||||
$nextSettlementMonth = $joiningDate->copy()->startOfMonth();
|
||||
}
|
||||
|
||||
// Only allow settlement if the generation date for the month has arrived
|
||||
$genDay = $branch->salary_generation_day ?? 2;
|
||||
$generationDate = $nextSettlementMonth->copy()->addMonth()->day(min($genDay, $nextSettlementMonth->copy()->addMonth()->daysInMonth));
|
||||
|
||||
if (Carbon::now()->lessThan($generationDate)) {
|
||||
return response()->json([
|
||||
'can_settle' => false,
|
||||
'message' => "Salary for {$nextSettlementMonth->format('F Y')} will be available for settlement on " . $generationDate->format('jS F Y'),
|
||||
]);
|
||||
}
|
||||
// 3. Status Check: Only allow settlement if the month has completed
|
||||
if (Carbon::now()->lessThan($nextSettlementMonth->copy()->addMonth()->startOfMonth())) {
|
||||
return response()->json([
|
||||
'can_settle' => false,
|
||||
'message' => "Salary for {$nextSettlementMonth->format('F Y')} is still in progress.",
|
||||
]);
|
||||
}
|
||||
|
||||
// Calculate Cycle and Pro-rata
|
||||
@ -657,7 +664,30 @@ public function getAdvanceHistory($id)
|
||||
}
|
||||
$history = SalaryAdvanceDeduction::where('staff_id', $id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
->get()
|
||||
->map(function($h) {
|
||||
$schedule = [];
|
||||
$months = $h->total_months ?: 1;
|
||||
$paid = $h->paid_amount ?: 0;
|
||||
$installment = $h->monthly_deduction ?: ($h->advance_amount / $months);
|
||||
|
||||
// Assuming repayment starts from the month following the advance
|
||||
$startDate = Carbon::parse($h->created_at)->startOfMonth();
|
||||
|
||||
for ($i = 0; $i < $months; $i++) {
|
||||
$monthDate = $startDate->copy()->addMonths($i + 1);
|
||||
$isPaid = $paid >= (($i + 1) * $installment - 0.01);
|
||||
|
||||
$schedule[] = [
|
||||
'month' => $monthDate->format('F Y'),
|
||||
'amount' => round($installment, 2),
|
||||
'status' => $isPaid ? 'Paid' : 'Pending'
|
||||
];
|
||||
}
|
||||
|
||||
$h->installment_schedule = $schedule;
|
||||
return $h;
|
||||
});
|
||||
return response()->json($history);
|
||||
}
|
||||
|
||||
@ -687,16 +717,16 @@ public function getAllPendingSalaries(Request $request)
|
||||
|
||||
$staffMonths = [];
|
||||
|
||||
// Loop while generation date for the month has passed
|
||||
while (true) {
|
||||
$currentMonth = Carbon::now()->startOfMonth();
|
||||
|
||||
// Loop while the month has completed
|
||||
while ($tempMonth->lessThan($currentMonth)) {
|
||||
$monthKey = $tempMonth->format('Y-m');
|
||||
|
||||
// Calculate generation date for this month
|
||||
$genDay = $branch->salary_generation_day ?? 2;
|
||||
$generationDate = $tempMonth->copy()->addMonth()->day(min($genDay, $tempMonth->copy()->addMonth()->daysInMonth));
|
||||
|
||||
if (Carbon::now()->lessThan($generationDate)) {
|
||||
break;
|
||||
// Safety break for extremely old joining dates (limit to 10 years or similar)
|
||||
if ($tempMonth->diffInYears(Carbon::now()) > 10) {
|
||||
$tempMonth->addMonth();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($settlements[$monthKey])) {
|
||||
|
||||
34
config/cors.php
Normal file
34
config/cors.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your settings for cross-origin resource sharing
|
||||
| or "CORS". This determines what cross-origin operations may execute
|
||||
| in web browsers. You are free to adjust these settings as needed.
|
||||
|
|
||||
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
|
|
||||
*/
|
||||
|
||||
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout', 'receptionist/login'],
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
'allowed_origins' => ['*'],
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => [],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
'supports_credentials' => false,
|
||||
|
||||
];
|
||||
@ -1,133 +1,134 @@
|
||||
# Flutter Mobile App API Documentation (Owner Role)
|
||||
# Flutter Mobile App API Documentation
|
||||
|
||||
Base URL: http://127.0.0.1:8000/api
|
||||
Note: For testing on a real physical mobile device, replace 127.0.0.1 with your computer's local IP address (e.g., 192.168.1.5).
|
||||
## Connection Details
|
||||
- **Base URL**: `http://127.0.0.1:8002/api`
|
||||
- **Mobile Testing**: For testing on a physical device, replace `127.0.0.1` with your computer's local IP (e.g., `http://192.168.1.5:8002/api`).
|
||||
- **Headers**:
|
||||
- `Accept: application/json`
|
||||
- `Content-Type: application/json`
|
||||
- `X-Requested-With: XMLHttpRequest`
|
||||
|
||||
## Authentication
|
||||
- POST /login
|
||||
- Params: email, password
|
||||
- Returns: CSRF cookie and session (for web-based auth) or Auth token (if configured).
|
||||
---
|
||||
|
||||
- POST /logout
|
||||
- Action: End session
|
||||
## 1. Authentication Module
|
||||
Manage user sessions and profiles.
|
||||
|
||||
- GET /profile
|
||||
- Returns: Current logged-in user details and role.
|
||||
- **POST /login**
|
||||
- Params: `email`, `password`
|
||||
- Response: `{ "user": { ... }, "redirect": "/owner/dashboard" }`
|
||||
- **POST /receptionist/login**
|
||||
- Params: `email`, `password`
|
||||
- Response: `{ "user": { ... }, "redirect": "/receptionist/dashboard" }`
|
||||
- **GET /api/profile**
|
||||
- Query: `context` (owner/receptionist)
|
||||
- Returns: Current user details and role.
|
||||
- **POST /logout**
|
||||
- Action: Terminates session.
|
||||
|
||||
## Branch Management
|
||||
- GET /branches
|
||||
- List all branches.
|
||||
- POST /branches
|
||||
- Create new branch (Multipart/form-data for documents).
|
||||
- GET /branches/{id}
|
||||
- View specific branch details.
|
||||
- PUT /branches/{id}
|
||||
- Update branch details.
|
||||
- DELETE /branches/{id}
|
||||
- Delete a branch.
|
||||
---
|
||||
|
||||
- GET /branches/{branch}/receptionist
|
||||
- View receptionist for a branch.
|
||||
- POST /branches/{branch}/receptionist
|
||||
- Create/Update receptionist credentials.
|
||||
- DELETE /branches/{branch}/receptionist
|
||||
- Remove receptionist.
|
||||
## 2. Branch Management
|
||||
Manage business locations and their documents.
|
||||
|
||||
## Staff Management
|
||||
- GET /staff
|
||||
- List all staff members.
|
||||
- POST /staff
|
||||
- Add new staff (Multipart/form-data for documents).
|
||||
- GET /staff/{id}
|
||||
- View staff profile.
|
||||
- PUT /staff/{id}
|
||||
- Update staff profile.
|
||||
- DELETE /staff/{id}
|
||||
- Delete staff member.
|
||||
- **GET /api/branches**
|
||||
- Query: `status` (Active/Inactive)
|
||||
- Returns: List of all branches with document counts and revenue.
|
||||
- **GET /api/branches/{id}**
|
||||
- Returns: Detailed branch info and documents.
|
||||
- **POST /api/branches**
|
||||
- Type: `multipart/form-data`
|
||||
- Params: `name`, `location`, `manager_name`, `operational_start_date`, `payroll_from_day`, `payroll_to_day`, `salary_generation_day`
|
||||
- Files: `docs[0][file]`, `docs[0][name]`, `docs[0][expiry_date]`...
|
||||
- **PUT /api/branches/{id}**
|
||||
- Params: Same as POST, plus `status`.
|
||||
- **DELETE /api/branches/{id}**
|
||||
- Note: Only deletable if not used in staff/inventory/accounts.
|
||||
- **GET /api/branches/{id}/active-staff**
|
||||
- Returns: List of active staff in that branch.
|
||||
|
||||
- GET /staff/pending-salaries
|
||||
- List all pending salaries across branches.
|
||||
- POST /staff/bulk-settle
|
||||
- Params: staff_ids[]
|
||||
- Settle multiple salaries at once.
|
||||
- GET /staff/{id}/payments
|
||||
- Salary payment history.
|
||||
- GET /staff/{id}/payroll-status
|
||||
- Current month's payroll calculation.
|
||||
- POST /staff/{id}/settle
|
||||
- Settle individual salary for a month.
|
||||
- GET /staff/{id}/advance-history
|
||||
- List of advance payments and deductions.
|
||||
---
|
||||
|
||||
## Investor & ROI Management
|
||||
- GET /investors
|
||||
- List all investors.
|
||||
- POST /investors
|
||||
- Add new investor (Multipart/form-data for documents).
|
||||
- GET /investors/{id}
|
||||
- View investor details.
|
||||
- PUT /investors/{id}
|
||||
- Update investor.
|
||||
- DELETE /investors/{id}
|
||||
- Delete investor.
|
||||
## 3. Staff & Payroll Module
|
||||
Employee management and salary settlements.
|
||||
|
||||
- GET /investors/pending-roi
|
||||
- List all pending ROI settlements.
|
||||
- GET /investors/{id}/roi-status
|
||||
- Monthly ROI status breakdown (Base ROI, Carry Over, Paid, Net Due).
|
||||
- POST /investors/{id}/settle-roi
|
||||
- Params: payout_month, amount, payout_date, payment_method, remarks.
|
||||
- Settle a month's ROI.
|
||||
- **GET /api/staff**
|
||||
- Query: `branch_id`
|
||||
- Returns: Complete staff list with documents.
|
||||
- **POST /api/staff**
|
||||
- Type: `multipart/form-data`
|
||||
- Params: `full_name`, `email`, `phone`, `role`, `branch_id`, `joining_date`, `status`, `salary_type`, `salary_amount`.
|
||||
- Optional: `advance_enabled`, `advance_amount`, `commission_enabled`, `documents[]`.
|
||||
- **GET /api/staff/{id}/payroll-status**
|
||||
- Returns: Month-by-month payment history and unpaid months.
|
||||
- **GET /api/staff/{id}/settlement**
|
||||
- Returns: Pro-rated salary calculation, commissions, and advance deductions due.
|
||||
- **POST /api/staff/{id}/settle**
|
||||
- Params: `month` (Y-m), `remarks`
|
||||
- Action: Processes salary payment and records expense.
|
||||
- **GET /api/staff/pending-salaries**
|
||||
- Returns: List of all staff with pending settlements across branches.
|
||||
- **POST /api/staff/bulk-settle**
|
||||
- Params: `settlements` (array of staff_id/month_key), `remarks`.
|
||||
|
||||
## Financials & Expenses
|
||||
- GET /accounts
|
||||
- General ledger of all credit/debit transactions.
|
||||
- GET /expenses
|
||||
- List of all business expenses.
|
||||
- POST /expenses
|
||||
- Params: date, branch_id, expense_category_id, expense_type (Account/Petty Cash), amount, remarks.
|
||||
- Record a new expense.
|
||||
- GET /expense-categories
|
||||
- List of master expense categories.
|
||||
---
|
||||
|
||||
## Inventory Management
|
||||
- GET /inventory/products
|
||||
- List all products and stock levels.
|
||||
- POST /inventory/products
|
||||
- Add new product with image.
|
||||
- POST /inventory/products/{id}/adjust
|
||||
- Adjuts stock (Add/Remove) with remarks.
|
||||
- GET /inventory/products/{id}/history
|
||||
- Stock movement history for a specific product.
|
||||
- GET /inventory/sales
|
||||
- List of all POS sales.
|
||||
- POST /inventory/sales
|
||||
- Record a new product sale.
|
||||
- GET /inventory/movements
|
||||
- Global stock movement log.
|
||||
## 4. Investor & ROI Module
|
||||
Manage investments and monthly payouts.
|
||||
|
||||
## Collections
|
||||
- GET /collections
|
||||
- List all daily collections.
|
||||
- POST /collections
|
||||
- Record new collection.
|
||||
- GET /collections/{id}
|
||||
- View collection details.
|
||||
- **GET /api/investors**
|
||||
- Returns: List of all investors and their linked branches.
|
||||
- **POST /api/investors**
|
||||
- Params: `name`, `investment_date`, `investment_amount`, `roi_type` (Percentage/Fixed Amount), `roi_value`, `roi_period` (Monthly/Quarterly/Yearly).
|
||||
- **GET /api/investors/{id}/roi-status**
|
||||
- Returns: Breakdown of ROI due, paid, and carry-over for each period.
|
||||
- **POST /api/investors/{id}/settle-roi**
|
||||
- Params: `payout_month`, `amount`, `payout_date`, `payment_method`, `remarks`.
|
||||
|
||||
## Reports
|
||||
- GET /reports/profit
|
||||
- Profit & Loss report (Income vs Expenses).
|
||||
- GET /reports/expiry-reminders
|
||||
- Documents (Trade license, staff IDs, etc.) expiring soon.
|
||||
- GET /reports/investments
|
||||
- Summary of total investments and ROI distributed.
|
||||
---
|
||||
|
||||
## Master Settings
|
||||
- GET /masters/{type}
|
||||
- Types: expense_categories, product_categories, payment_methods, etc.
|
||||
- POST /masters/{type}
|
||||
- Add master entry.
|
||||
- PUT /masters/{type}/{id}
|
||||
- Edit master entry.
|
||||
- DELETE /masters/{type}/{id}
|
||||
- Delete master entry.
|
||||
## 5. Inventory & POS Module
|
||||
Product management and sales tracking.
|
||||
|
||||
- **GET /api/inventory/products**
|
||||
- Query: `branch_id`, `status` (In Stock/Low Stock/Out of Stock).
|
||||
- **POST /api/inventory/products**
|
||||
- Params: `name`, `sku`, `product_category_id`, `branch_id`, `cost_price`, `selling_price`, `current_stock`, `reorder_level`.
|
||||
- **POST /api/inventory/products/{id}/adjust**
|
||||
- Params: `adjustment_qty` (+/-), `reason`, `adjustment_date`.
|
||||
- **POST /api/inventory/sales**
|
||||
- Params: `branch_id`, `payment_method`, `items` (array of product_id/quantity/unit_price).
|
||||
- Action: Deducts stock and records revenue.
|
||||
|
||||
---
|
||||
|
||||
## 6. Collections & Expenses
|
||||
Financial tracking.
|
||||
|
||||
- **GET /api/collections**
|
||||
- Query: `start_date`, `end_date`, `branch_id`.
|
||||
- **POST /api/collections**
|
||||
- Params: `date`, `branch_id`, `collection_type_id`, `amount`, `payment_method`, `items[]`.
|
||||
- **POST /api/expenses**
|
||||
- Params: `date`, `branch_id`, `expense_category_id`, `expense_type` (Account/Petty Cash), `amount`, `remarks`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Reports
|
||||
Data analysis and reminders.
|
||||
|
||||
- **GET /api/reports/profit**
|
||||
- Returns: Total income, total expense, net profit, and 6-month trend.
|
||||
- **GET /api/reports/expiry-reminders**
|
||||
- Returns: Document expiry alerts for both Staff and Branches.
|
||||
- **GET /api/reports/investments**
|
||||
- Returns: Summary of total investments and total ROI returned.
|
||||
|
||||
---
|
||||
|
||||
## 8. Master Settings
|
||||
Manage dropdown options.
|
||||
|
||||
- **GET /api/masters/{type}**
|
||||
- Types: `collection`, `expense`, `product`, `payment_method`, `staff_role`.
|
||||
- **POST /api/masters/{type}**
|
||||
- Params: `name`, `status`.
|
||||
|
||||
@ -16,9 +16,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch })
|
||||
|
||||
const [newDocs, setNewDocs] = useState([]);
|
||||
const [activeStaff, setActiveStaff] = useState([]);
|
||||
const [branches, setBranches] = useState([]); // All branches for "Move to" option
|
||||
const [staffAction, setStaffAction] = useState('move'); // 'move' or 'inactivate'
|
||||
const [moveToBranchId, setMoveToBranchId] = useState('');
|
||||
const [loadingStaff, setLoadingStaff] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -35,8 +32,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch })
|
||||
});
|
||||
setNewDocs([]);
|
||||
setActiveStaff([]);
|
||||
setStaffAction('move');
|
||||
setMoveToBranchId('');
|
||||
}
|
||||
}, [branch]);
|
||||
|
||||
@ -49,11 +44,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch })
|
||||
const res = await fetch(`/api/branches/${branch.id}/active-staff`);
|
||||
const data = await res.json();
|
||||
setActiveStaff(data);
|
||||
|
||||
// Fetch other branches for movement
|
||||
const bRes = await fetch('/api/branches?status=Active');
|
||||
const bData = await bRes.json();
|
||||
setBranches(bData.filter(b => b.id !== branch.id));
|
||||
} catch (error) {
|
||||
console.error('Error fetching active staff:', error);
|
||||
} finally {
|
||||
@ -95,13 +85,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch })
|
||||
data.append('payroll_to_day', formData.payroll_to_day);
|
||||
data.append('salary_generation_day', formData.salary_generation_day);
|
||||
|
||||
if (formData.status === 'Inactive' && activeStaff.length > 0) {
|
||||
data.append('staff_action', staffAction);
|
||||
if (staffAction === 'move') {
|
||||
data.append('move_to_branch_id', moveToBranchId);
|
||||
}
|
||||
}
|
||||
|
||||
newDocs.forEach((doc, index) => {
|
||||
if (doc.file) {
|
||||
data.append(`new_docs[${index}][file]`, doc.file);
|
||||
@ -257,44 +240,11 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch })
|
||||
<User size={16} />
|
||||
<h4 className="text-sm font-bold mt-1">Active Staff Detected ({activeStaff.length})</h4>
|
||||
</div>
|
||||
<p className="text-[11px] text-orange-700 leading-relaxed">
|
||||
This branch has active staff members. Please choose what to do with them before inactivating the branch.
|
||||
<p className="text-[11px] text-orange-700 font-bold leading-relaxed">
|
||||
Warning: Inactivating this branch will automatically inactivate all {activeStaff.length} active staff members.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStaffAction('move')}
|
||||
className={`flex-1 py-2 text-[10px] font-bold rounded-lg border transition-all ${staffAction === 'move' ? 'bg-orange-500 text-white border-orange-500 shadow-sm' : 'bg-white text-orange-500 border-orange-200 hover:bg-orange-50'}`}
|
||||
>
|
||||
Move to Branch
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStaffAction('inactivate')}
|
||||
className={`flex-1 py-2 text-[10px] font-bold rounded-lg border transition-all ${staffAction === 'inactivate' ? 'bg-orange-500 text-white border-orange-500 shadow-sm' : 'bg-white text-orange-500 border-orange-200 hover:bg-orange-50'}`}
|
||||
>
|
||||
Inactivate All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{staffAction === 'move' && (
|
||||
<select
|
||||
required={staffAction === 'move'}
|
||||
className="w-full px-3 py-2 bg-white border border-orange-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500/20 focus:border-orange-500 transition-all text-xs font-bold"
|
||||
value={moveToBranchId}
|
||||
onChange={(e) => setMoveToBranchId(e.target.value)}
|
||||
>
|
||||
<option value="">Select Target Branch *</option>
|
||||
{branches.map(b => (
|
||||
<option key={b.id} value={b.id}>{b.name}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="max-h-32 overflow-y-auto no-scrollbar space-y-1 pr-1">
|
||||
<div className="max-h-32 overflow-y-auto no-scrollbar space-y-1 pr-1 border-t border-orange-100/50 pt-2">
|
||||
{activeStaff.map(s => (
|
||||
<div key={s.id} className="px-3 py-1.5 bg-white/50 rounded flex items-center justify-between text-[10px] text-orange-900 border border-orange-100/50">
|
||||
<span className="font-bold">{s.full_name}</span>
|
||||
|
||||
@ -50,6 +50,19 @@ function ReceptionistForm({ branchId }) {
|
||||
setError('');
|
||||
setSuccess('');
|
||||
|
||||
if (editingId === null || formData.password !== '') {
|
||||
if (formData.password.length < 6) {
|
||||
setError('Password must be at least 6 characters.');
|
||||
setSaving(false);
|
||||
return;
|
||||
}
|
||||
if (formData.password !== formData.password_confirmation) {
|
||||
setError('Passwords do not match. Please make it proper.');
|
||||
setSaving(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
||||
const res = await fetch(`/api/branches/${branchId}/receptionist`, {
|
||||
@ -66,7 +79,12 @@ function ReceptionistForm({ branchId }) {
|
||||
setSuccess(editingId ? 'Account updated successfully!' : 'Account created successfully!');
|
||||
fetchReceptionists();
|
||||
} else {
|
||||
setError(data.message || 'Failed to save receptionist.');
|
||||
if (data.errors) {
|
||||
const firstError = Object.values(data.errors)[0][0];
|
||||
setError(firstError);
|
||||
} else {
|
||||
setError(data.message || 'Failed to save receptionist.');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError('An error occurred. Please try again.');
|
||||
@ -189,6 +207,49 @@ function ReceptionistForm({ branchId }) {
|
||||
required={formData.password !== ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password Criteria Feedback */}
|
||||
<div className="col-span-2 space-y-3 px-1">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-1.5 h-1.5 rounded-full transition-colors ${formData.password.length >= 6 ? 'bg-emerald-500' : (formData.password.length > 0 ? 'bg-red-500' : 'bg-gray-300')}`}></div>
|
||||
<span className={`text-[9px] font-black uppercase tracking-widest transition-colors ${
|
||||
(formData.password.length >= 6 || (editingId && formData.password === ''))
|
||||
? 'text-emerald-600'
|
||||
: (formData.password.length > 0 ? 'text-red-500' : 'text-gray-400')}`}>
|
||||
Min 6 characters
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-1.5 h-1.5 rounded-full transition-colors ${
|
||||
(formData.password !== '' && formData.password === formData.password_confirmation)
|
||||
? 'bg-emerald-500'
|
||||
: (formData.password_confirmation !== '' ? 'bg-red-500' : 'bg-gray-300')}`}></div>
|
||||
<span className={`text-[9px] font-black uppercase tracking-widest transition-colors ${
|
||||
(formData.password !== '' && formData.password === formData.password_confirmation)
|
||||
? 'text-emerald-600'
|
||||
: (formData.password_confirmation !== '' ? 'text-red-500' : 'text-gray-400')}`}>
|
||||
Passwords match
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Explicit Error Messages */}
|
||||
<div className="space-y-1">
|
||||
{formData.password.length > 0 && formData.password.length < 6 && (
|
||||
<p className="text-[10px] text-red-500 font-bold uppercase tracking-widest flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
|
||||
<span className="w-1 h-1 rounded-full bg-red-500"></span>
|
||||
Password is too short (min 6 characters)
|
||||
</p>
|
||||
)}
|
||||
{formData.password_confirmation !== '' && formData.password !== formData.password_confirmation && (
|
||||
<p className="text-[10px] text-red-500 font-bold uppercase tracking-widest flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
|
||||
<span className="w-1 h-1 rounded-full bg-red-500"></span>
|
||||
Please make it proper (Passwords mismatch)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 pt-6">
|
||||
|
||||
@ -672,68 +672,80 @@ export default function ExpenseList() {
|
||||
</div>
|
||||
|
||||
<div className="p-8 overflow-y-auto space-y-6">
|
||||
<div className="space-y-4">
|
||||
{pendingSalaries.map(staff => (
|
||||
<div key={staff.staff_id} className="p-4 rounded-2xl bg-gray-50/50 border border-gray-100">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<p className="text-sm font-black text-gray-900">{staff.staff_name}</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
const current = bulkData.selectedMonths[staff.staff_id] || [];
|
||||
const allMonths = staff.pending_months.map(m => m.month_key);
|
||||
const allSelected = allMonths.every(key => current.includes(key));
|
||||
|
||||
setBulkData({
|
||||
...bulkData,
|
||||
selectedMonths: {
|
||||
...bulkData.selectedMonths,
|
||||
[staff.staff_id]: allSelected ? [] : allMonths
|
||||
}
|
||||
});
|
||||
}}
|
||||
className="text-[9px] font-black text-emerald-600 uppercase tracking-tighter hover:underline"
|
||||
>
|
||||
{ (bulkData.selectedMonths[staff.staff_id]?.length === staff.pending_months.length) ? 'Deselect All' : 'Select All Months' }
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs font-bold text-emerald-600">
|
||||
{ (staff.pending_months.filter(m => bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key)).reduce((sum, m) => sum + m.net_payable, 0) || 0).toLocaleString() } AED
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{staff.pending_months.map(m => {
|
||||
const isSelected = bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key);
|
||||
return (
|
||||
{pendingSalaries.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{pendingSalaries.map(staff => (
|
||||
<div key={staff.staff_id} className="p-4 rounded-2xl bg-gray-50/50 border border-gray-100">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<p className="text-sm font-black text-gray-900">{staff.staff_name}</p>
|
||||
<button
|
||||
key={m.month_key}
|
||||
onClick={() => {
|
||||
const current = bulkData.selectedMonths[staff.staff_id] || [];
|
||||
const next = isSelected
|
||||
? current.filter(key => key !== m.month_key)
|
||||
: [...current, m.month_key];
|
||||
const allMonths = staff.pending_months.map(m => m.month_key);
|
||||
const allSelected = allMonths.every(key => current.includes(key));
|
||||
|
||||
setBulkData({
|
||||
...bulkData,
|
||||
selectedMonths: {
|
||||
...bulkData.selectedMonths,
|
||||
[staff.staff_id]: next
|
||||
[staff.staff_id]: allSelected ? [] : allMonths
|
||||
}
|
||||
});
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${
|
||||
isSelected
|
||||
? 'bg-emerald-500 text-white shadow-md shadow-emerald-100'
|
||||
: 'bg-white text-gray-400 border border-gray-100 hover:border-emerald-200'
|
||||
}`}
|
||||
className="text-[9px] font-black text-emerald-600 uppercase tracking-tighter hover:underline"
|
||||
>
|
||||
{m.month_name}
|
||||
{ (bulkData.selectedMonths[staff.staff_id]?.length === staff.pending_months.length) ? 'Deselect All' : 'Select All Months' }
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="text-xs font-bold text-emerald-600">
|
||||
{ (staff.pending_months.filter(m => bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key)).reduce((sum, m) => sum + m.net_payable, 0) || 0).toLocaleString() } AED
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{staff.pending_months.map(m => {
|
||||
const isSelected = bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key);
|
||||
return (
|
||||
<button
|
||||
key={m.month_key}
|
||||
onClick={() => {
|
||||
const current = bulkData.selectedMonths[staff.staff_id] || [];
|
||||
const next = isSelected
|
||||
? current.filter(key => key !== m.month_key)
|
||||
: [...current, m.month_key];
|
||||
setBulkData({
|
||||
...bulkData,
|
||||
selectedMonths: {
|
||||
...bulkData.selectedMonths,
|
||||
[staff.staff_id]: next
|
||||
}
|
||||
});
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${
|
||||
isSelected
|
||||
? 'bg-emerald-500 text-white shadow-md shadow-emerald-100'
|
||||
: 'bg-white text-gray-400 border border-gray-100 hover:border-emerald-200'
|
||||
}`}
|
||||
>
|
||||
{m.month_name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-20 text-center space-y-4">
|
||||
<div className="w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mx-auto text-gray-300">
|
||||
<CreditCard size={32} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-black text-gray-900 uppercase tracking-widest">No Pending Salaries</p>
|
||||
<p className="text-xs text-gray-400 font-bold">All staff members in this branch are fully paid for previous months.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 block">Release Remarks</label>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Search, ShoppingCart, Plus, Minus, CreditCard, DollarSign, Globe, Trash2 } from 'lucide-react';
|
||||
import { X, Search, ShoppingCart, Plus, Minus, CreditCard, DollarSign, Globe, Trash2, Banknote } from 'lucide-react';
|
||||
|
||||
export default function NewSaleModal({ isOpen, onClose, onSave, branches, products }) {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@ -323,9 +323,7 @@ export default function NewSaleModal({ isOpen, onClose, onSave, branches, produc
|
||||
<button
|
||||
disabled={cart.length === 0 || loading || (parseFloat(adjustedTotal) !== totalWithVat && !adjustmentRemarks.trim())}
|
||||
onClick={handleSubmit}
|
||||
className="w-full py-2.5 mt-2 rounded-lg font-black uppercase tracking-[0.15em] text-[10px] text-white shadow-lg transition-all flex items-center justify-center gap-2 active:scale-95 ${
|
||||
success ? 'bg-emerald-500 shadow-emerald-200' : 'bg-[#EF4444] hover:bg-red-600 shadow-red-200'
|
||||
} disabled:opacity-50 disabled:scale-100 disabled:shadow-none"
|
||||
className={`w-full py-2.5 mt-2 rounded-lg font-black uppercase tracking-[0.15em] text-[10px] text-white shadow-lg transition-all flex items-center justify-center gap-2 active:scale-95 bg-[#EF4444] hover:bg-red-600 shadow-red-200 disabled:opacity-50 disabled:scale-100 disabled:shadow-none`}
|
||||
>
|
||||
{loading ? 'Processing...' : 'Process Payment'}
|
||||
</button>
|
||||
|
||||
@ -48,6 +48,7 @@ export default function StaffView({ id }) {
|
||||
const [loadingPayroll, setLoadingPayroll] = useState(false);
|
||||
const [advanceHistory, setAdvanceHistory] = useState([]);
|
||||
const [loadingAdvanceHistory, setLoadingAdvanceHistory] = useState(false);
|
||||
const [isAdvanceHistoryModalOpen, setIsAdvanceHistoryModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStaff();
|
||||
@ -426,21 +427,32 @@ export default function StaffView({ id }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{advanceHistory.length > 0 && (
|
||||
{advanceHistory.length > 0 && (
|
||||
<div className="mt-6 pt-6 border-t border-orange-50/50 space-y-3">
|
||||
<p className="text-[9px] font-black text-orange-400 uppercase tracking-widest mb-2">Advance History Summary</p>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<p className="text-[9px] font-black text-orange-400 uppercase tracking-widest">Monthly Repayment Schedule</p>
|
||||
{advanceHistory.flatMap(h => h.installment_schedule || []).length > 8 && (
|
||||
<button
|
||||
onClick={() => setIsAdvanceHistoryModalOpen(true)}
|
||||
className="text-[8px] font-black text-orange-600 hover:underline uppercase tracking-widest"
|
||||
>
|
||||
View Full History
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{advanceHistory.map((h, i) => (
|
||||
<div key={i} className={`p-2 rounded-xl border flex items-center justify-between transition-all ${h.status === 'Closed' ? 'bg-white/50 border-gray-100 opacity-60' : 'bg-white border-orange-100 shadow-sm animate-pulse'}`}>
|
||||
{advanceHistory.flatMap(h => h.installment_schedule || [])
|
||||
.slice(0, 8)
|
||||
.map((item, idx) => (
|
||||
<div key={idx} className={`p-2 rounded-xl border flex items-center justify-between transition-all ${item.status === 'Paid' ? 'bg-emerald-50/30 border-emerald-100 opacity-60' : 'bg-white border-orange-100 shadow-sm'}`}>
|
||||
<div>
|
||||
<p className="text-[10px] font-black text-gray-900">{(h.advance_amount || 0).toLocaleString()} AED</p>
|
||||
<p className="text-[8px] text-gray-400 font-medium">Taken {new Date(h.created_at).toLocaleDateString()}</p>
|
||||
<p className="text-[10px] font-black text-gray-900">{item.month}</p>
|
||||
<p className="text-[8px] text-gray-400 font-medium">AED {item.amount.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className={`px-2 py-0.5 rounded-full text-[8px] font-black uppercase tracking-widest ${h.status === 'Closed' ? 'bg-gray-100 text-gray-400' : 'bg-orange-50 text-orange-600'}`}>
|
||||
{h.status}
|
||||
<span className={`px-2 py-0.5 rounded-full text-[8px] font-black uppercase tracking-widest ${item.status === 'Paid' ? 'bg-emerald-100 text-emerald-600' : 'bg-orange-50 text-orange-600'}`}>
|
||||
{item.status}
|
||||
</span>
|
||||
<p className="text-[8px] font-bold text-emerald-500 mt-0.5">{parseFloat(h.paid_amount || 0).toLocaleString()} Paid</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -993,6 +1005,63 @@ export default function StaffView({ id }) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Full Advance History Modal */}
|
||||
{isAdvanceHistoryModalOpen && (
|
||||
<div className="fixed inset-0 z-[80] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
|
||||
<div className="bg-white rounded-[2.5rem] w-full max-w-2xl max-h-[85vh] overflow-hidden flex flex-col shadow-2xl animate-in zoom-in-95 duration-200">
|
||||
<div className="p-8 flex items-center justify-between border-b border-gray-100">
|
||||
<div>
|
||||
<h3 className="text-xl font-black text-gray-900 uppercase tracking-tight">Full Advance History</h3>
|
||||
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-1">Repayment Schedule & Status</p>
|
||||
</div>
|
||||
<button onClick={() => setIsAdvanceHistoryModalOpen(false)} className="w-10 h-10 flex items-center justify-center rounded-full hover:bg-gray-100 text-gray-400 transition-all">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-8">
|
||||
<div className="space-y-4">
|
||||
{advanceHistory.map((h, hIdx) => (
|
||||
<div key={hIdx} className="space-y-3">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<p className="text-[10px] font-black text-orange-500 uppercase tracking-widest">
|
||||
Advance taken on {new Date(h.created_at).toLocaleDateString()} — {parseFloat(h.advance_amount).toLocaleString()} AED
|
||||
</p>
|
||||
<span className={`px-2 py-0.5 rounded-full text-[8px] font-black uppercase tracking-widest ${h.status === 'Closed' ? 'bg-gray-100 text-gray-400' : 'bg-orange-50 text-orange-600'}`}>
|
||||
{h.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{(h.installment_schedule || []).map((item, iIdx) => (
|
||||
<div key={iIdx} className={`p-4 rounded-2xl border flex items-center justify-between transition-all ${item.status === 'Paid' ? 'bg-emerald-50/30 border-emerald-50 opacity-60' : 'bg-gray-50/50 border-gray-100 shadow-sm'}`}>
|
||||
<div>
|
||||
<p className="text-sm font-black text-gray-900">{item.month}</p>
|
||||
<p className="text-[10px] text-gray-400 font-bold uppercase tracking-wider">AED {item.amount.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className={`px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest ${item.status === 'Paid' ? 'bg-emerald-100 text-emerald-600' : 'bg-white text-orange-600 border border-orange-100'}`}>
|
||||
{item.status}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{hIdx < advanceHistory.length - 1 && <div className="h-px bg-gray-100 my-6" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-8 border-t border-gray-100 bg-gray-50/30 flex justify-end">
|
||||
<button
|
||||
onClick={() => setIsAdvanceHistoryModalOpen(false)}
|
||||
className="px-8 py-3 bg-gray-900 text-white rounded-xl text-sm font-bold hover:bg-black transition-all shadow-lg shadow-gray-200"
|
||||
>
|
||||
Close History
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -23,7 +23,9 @@ 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]);
|
||||
|
||||
// Default to current month for a better overview
|
||||
const [startDate, setStartDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0]);
|
||||
const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [branches, setBranches] = useState([]);
|
||||
|
||||
@ -48,8 +50,8 @@ export default function ReceptionistDashboard() {
|
||||
const data = await response.json();
|
||||
|
||||
setStats({
|
||||
total_income: data.total_income || 0,
|
||||
total_expenses: data.total_expense || 0,
|
||||
total_income: data.today_income || 0, // Using today's actual income
|
||||
total_expenses: data.monthly_expense || 0, // Using monthly actual expenses
|
||||
net_profit: data.net_profit || 0,
|
||||
low_stock_count: data.low_stock_count || 0
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user