39419-vm/frontend/src/pages/cashier.tsx
2026-04-01 19:44:21 +00:00

749 lines
36 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { mdiChartTimelineVariant } from '@mdi/js';
import Head from 'next/head';
import Link from 'next/link';
import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
import axios from 'axios';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import LoadingSpinner from '../components/LoadingSpinner';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import { getPageTitle } from '../config';
import { formatSalesInvoicePaymentMethod } from '../helpers/salesInvoiceLabels';
import { hasPermission } from '../helpers/userPermissions';
import LayoutAuthenticated from '../layouts/Authenticated';
import { useAppSelector } from '../stores/hooks';
type CartItem = {
productId: string;
quantity: number;
};
type WorkspaceData = {
shops: any[];
selectedShop: any;
categories: any[];
products: any[];
summary: {
totalSales: number;
totalProfit: number;
invoiceCount: number;
};
recentInvoices: any[];
latestPriceChange: any;
};
const formatMoney = (value: number) => `${new Intl.NumberFormat('ar-IQ').format(value || 0)} د.ع`;
const formatUsd = (value: number | null) => {
if (value == null || Number.isNaN(value)) {
return '--';
}
return `${value.toFixed(2)} $`;
};
const formatDateTime = (value?: string | Date) => {
if (!value) {
return '--';
}
return new Date(value).toLocaleString('ar-IQ', {
hour: '2-digit',
minute: '2-digit',
year: 'numeric',
month: 'short',
day: 'numeric',
});
};
const initialWorkspace: WorkspaceData = {
shops: [],
selectedShop: null,
categories: [],
products: [],
summary: {
totalSales: 0,
totalProfit: 0,
invoiceCount: 0,
},
recentInvoices: [],
latestPriceChange: null,
};
const CashierPage = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const [workspace, setWorkspace] = useState<WorkspaceData>(initialWorkspace);
const [selectedShopId, setSelectedShopId] = useState('');
const [query, setQuery] = useState('');
const [activeCategoryId, setActiveCategoryId] = useState('all');
const [cart, setCart] = useState<CartItem[]>([]);
const [paymentMethod, setPaymentMethod] = useState('cash');
const [notes, setNotes] = useState('');
const [usdRateInput, setUsdRateInput] = useState('');
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [pricingBusy, setPricingBusy] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [successInvoice, setSuccessInvoice] = useState<any>(null);
const canCheckout = Boolean(currentUser && hasPermission(currentUser, 'CREATE_SALES_INVOICES'));
const canManagePricing = Boolean(currentUser && hasPermission(currentUser, 'UPDATE_SHOPS'));
const canCreateProducts = Boolean(currentUser && hasPermission(currentUser, 'CREATE_PRODUCTS'));
const canCreateShops = Boolean(currentUser && hasPermission(currentUser, 'CREATE_SHOPS'));
const loadWorkspace = useCallback(
async (shopId?: string) => {
setLoading(true);
setErrorMessage('');
try {
const { data } = await axios.get('/pos/workspace', {
params: shopId ? { shopId } : undefined,
});
setWorkspace(data);
const resolvedShopId = shopId || data.selectedShop?.id || '';
setSelectedShopId(resolvedShopId);
setUsdRateInput(data.selectedShop?.usd_rate ? String(data.selectedShop.usd_rate) : '');
} catch (error: any) {
console.error('POS workspace load failed:', error);
setErrorMessage(error?.response?.data || 'تعذر تحميل شاشة الكاشير حالياً.');
} finally {
setLoading(false);
}
},
[],
);
useEffect(() => {
loadWorkspace();
}, [loadWorkspace]);
useEffect(() => {
setSuccessInvoice(null);
}, [selectedShopId]);
const productMap = useMemo(
() =>
new Map(
(workspace.products || []).map((product) => [product.id, product]),
),
[workspace.products],
);
const filteredProducts = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return (workspace.products || []).filter((product) => {
const matchesCategory = activeCategoryId === 'all' || product.categoryId === activeCategoryId;
const searchableText = [product.product_name, product.sku, product.barcode, product.category_name]
.filter(Boolean)
.join(' ')
.toLowerCase();
const matchesQuery = !normalizedQuery || searchableText.includes(normalizedQuery);
return matchesCategory && matchesQuery;
});
}, [activeCategoryId, query, workspace.products]);
const suggestions = useMemo(() => filteredProducts.slice(0, 8), [filteredProducts]);
const cartDetails = useMemo(() => {
return cart
.map((item) => {
const product = productMap.get(item.productId);
if (!product) {
return null;
}
const lineTotal = (product.sale_price || 0) * item.quantity;
const lineProfit = ((product.sale_price || 0) - (product.cost_price || 0)) * item.quantity;
return {
...item,
product,
lineTotal,
lineProfit,
};
})
.filter(Boolean) as any[];
}, [cart, productMap]);
const cartSummary = useMemo(() => {
return cartDetails.reduce(
(acc, item) => ({
quantity: acc.quantity + item.quantity,
total: acc.total + item.lineTotal,
profit: acc.profit + item.lineProfit,
}),
{ quantity: 0, total: 0, profit: 0 },
);
}, [cartDetails]);
const addProductToCart = (productId: string) => {
setCart((current) => {
const existing = current.find((item) => item.productId === productId);
if (existing) {
return current.map((item) =>
item.productId === productId ? { ...item, quantity: item.quantity + 1 } : item,
);
}
return [...current, { productId, quantity: 1 }];
});
setSuccessInvoice(null);
};
const updateCartQuantity = (productId: string, nextQuantity: number) => {
setCart((current) =>
current
.map((item) => (item.productId === productId ? { ...item, quantity: nextQuantity } : item))
.filter((item) => item.quantity > 0),
);
};
const handleCheckout = async () => {
if (!selectedShopId || !cart.length) {
setErrorMessage('أضف منتجاً واحداً على الأقل قبل حفظ الفاتورة.');
return;
}
setSubmitting(true);
setErrorMessage('');
try {
const { data } = await axios.post('/pos/checkout', {
shopId: selectedShopId,
paymentMethod,
notes,
items: cart.map((item) => ({
productId: item.productId,
quantity: item.quantity,
})),
});
setSuccessInvoice(data);
setCart([]);
setNotes('');
setQuery('');
setActiveCategoryId('all');
await loadWorkspace(selectedShopId);
} catch (error: any) {
console.error('POS checkout failed:', error);
setErrorMessage(error?.response?.data || 'حدث خطأ أثناء حفظ الفاتورة.');
} finally {
setSubmitting(false);
}
};
const handlePricingAction = async (action: 'set_rate' | 'apply_prices' | 'restore_prices') => {
if (!selectedShopId) {
return;
}
setPricingBusy(true);
setErrorMessage('');
setSuccessInvoice(null);
try {
const payload: Record<string, string> = {
shopId: selectedShopId,
action,
};
if (action !== 'restore_prices') {
payload.usdRate = usdRateInput;
}
const { data } = await axios.post('/pos/pricing', payload);
await loadWorkspace(selectedShopId);
setErrorMessage('');
setSuccessInvoice({
invoice_number: data.message,
total_amount: 0,
total_profit_amount: 0,
});
} catch (error: any) {
console.error('POS pricing action failed:', error);
setErrorMessage(error?.response?.data || 'تعذر تنفيذ تحديث الأسعار.');
} finally {
setPricingBusy(false);
}
};
const emptyProductState = (
<CardBox className="border-dashed border-2 border-sky-100 bg-white/80">
<div className="space-y-3 py-6 text-center text-slate-600">
<p className="text-lg font-bold text-slate-900">لا توجد منتجات جاهزة للبيع بعد</p>
<p>ابدأ بإضافة منتجات وأقسام من لوحة الإدارة ليظهر الكاشير بشكل كامل.</p>
<div className="flex flex-wrap items-center justify-center gap-3">
{canCreateProducts && <BaseButton href="/products/products-new" color="success" label="إضافة منتج" />}
<BaseButton href="/products/products-list" color="info" label="عرض المنتجات" />
</div>
</div>
</CardBox>
);
return (
<>
<Head>
<title>{getPageTitle('الكاشير')}</title>
</Head>
<SectionMain>
<div className="app-rtl space-y-6" dir="rtl">
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="شاشة الكاشير وتقارير اليوم" main>
{''}
</SectionTitleLineWithButton>
<CardBox className="overflow-hidden border-0 bg-gradient-to-l from-emerald-500 via-emerald-600 to-sky-500 text-white shadow-xl shadow-emerald-100/70">
<div className="grid gap-6 px-2 py-2 lg:grid-cols-[1.35fr,0.65fr] lg:items-center">
<div className="space-y-3">
<span className="inline-flex items-center rounded-full bg-white/20 px-4 py-1 text-sm font-bold">
نظام بيع عربي سريع لمحل المنظفات
</span>
<div>
<h1 className="text-3xl font-extrabold leading-tight lg:text-4xl">بيع أسرع، فواتير أوضح، وربح يومي محسوب بدقة</h1>
<p className="mt-3 max-w-3xl text-base text-emerald-50 lg:text-lg">
ابحث عن المنتج فوراً، أضفه للفاتورة بضغطة واحدة، وراقب المبيعات والأرباح اليومية مع تحديث أسعار الدولار من نفس الشاشة.
</p>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-3 lg:grid-cols-1">
<div className="rounded-2xl bg-white/14 p-4 backdrop-blur-sm">
<div className="text-sm text-emerald-50">مبيعات اليوم</div>
<div className="mt-2 text-2xl font-extrabold">{formatMoney(workspace.summary.totalSales)}</div>
</div>
<div className="rounded-2xl bg-white/14 p-4 backdrop-blur-sm">
<div className="text-sm text-emerald-50">أرباح اليوم</div>
<div className="mt-2 text-2xl font-extrabold">{formatMoney(workspace.summary.totalProfit)}</div>
</div>
<div className="rounded-2xl bg-white/14 p-4 backdrop-blur-sm">
<div className="text-sm text-emerald-50">عدد الفواتير</div>
<div className="mt-2 text-2xl font-extrabold">{workspace.summary.invoiceCount}</div>
</div>
</div>
</div>
</CardBox>
{errorMessage ? (
<div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{errorMessage}</div>
) : null}
{successInvoice ? (
<div className="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-4 text-sm text-emerald-800">
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="font-bold text-emerald-900">تمت العملية بنجاح</p>
<p className="mt-1">
{successInvoice.id ? `تم إنشاء الفاتورة رقم ${successInvoice.invoice_number}.` : successInvoice.invoice_number}
</p>
</div>
{successInvoice.id ? (
<Link
href={`/sales_invoices/sales_invoices-view/?id=${successInvoice.id}`}
className="font-bold text-emerald-700 underline decoration-emerald-300 underline-offset-4"
>
فتح تفاصيل الفاتورة
</Link>
) : null}
</div>
</div>
) : null}
{loading ? (
<CardBox>
<LoadingSpinner />
</CardBox>
) : !workspace.shops.length ? (
<CardBox>
<div className="space-y-4 py-8 text-center">
<h2 className="text-2xl font-bold text-slate-900">لا يوجد محل مرتبط بحسابك بعد</h2>
<p className="text-slate-600">أنشئ أول محل ليتم تفعيل شاشة الكاشير وتقارير اليوم.</p>
<div className="flex flex-wrap items-center justify-center gap-3">
{canCreateShops && <BaseButton href="/shops/shops-new" color="success" label="إضافة محل" />}
<BaseButton href="/shops/shops-list" color="info" label="عرض المحلات" />
</div>
</div>
</CardBox>
) : (
<div className="grid gap-6 xl:grid-cols-[1.35fr,0.65fr]">
<div className="space-y-6">
<CardBox className="border-0 bg-white shadow-lg shadow-sky-100/60">
<div className="grid gap-4 lg:grid-cols-[0.7fr,1.3fr] lg:items-end">
<div>
<label className="mb-2 block text-sm font-bold text-slate-700">المحل الحالي</label>
<select
value={selectedShopId}
onChange={(event) => loadWorkspace(event.target.value)}
className={`h-12 w-full border border-slate-200 bg-white px-4 text-right text-slate-800 transition ${focusRing} ${corners}`}
>
{(workspace.shops || []).map((shop) => (
<option key={shop.id} value={shop.id}>
{shop.shop_name}
</option>
))}
</select>
</div>
<div className="grid gap-3 md:grid-cols-3">
<div className="rounded-2xl border border-slate-100 bg-slate-50 px-4 py-3">
<div className="text-xs font-bold text-slate-500">العملة</div>
<div className="mt-1 text-lg font-bold text-slate-900">{workspace.selectedShop?.currency_name || 'دينار عراقي'}</div>
</div>
<div className="rounded-2xl border border-slate-100 bg-slate-50 px-4 py-3">
<div className="text-xs font-bold text-slate-500">سعر الدولار الحالي</div>
<div className="mt-1 text-lg font-bold text-slate-900">{workspace.selectedShop?.usd_rate || 0}</div>
</div>
<div className="rounded-2xl border border-slate-100 bg-slate-50 px-4 py-3">
<div className="text-xs font-bold text-slate-500">آخر تحديث</div>
<div className="mt-1 text-sm font-bold text-slate-900">{formatDateTime(workspace.latestPriceChange?.changed_at)}</div>
</div>
</div>
</div>
</CardBox>
<CardBox className="border-0 bg-white shadow-lg shadow-sky-100/60">
<div className="space-y-4">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<h2 className="text-xl font-bold text-slate-900">بحث سريع عن المنتجات</h2>
<p className="text-sm text-slate-500">اكتب اسم المنتج أو الباركود أو رمز المنتج، وستظهر النتائج فوراً بدون إعادة تحميل.</p>
</div>
<div className="flex flex-wrap gap-2">
<BaseButton href="/products/products-list" color="info" label="إدارة المنتجات" />
<BaseButton href="/categories/categories-list" color="info" label="إدارة الأقسام" />
</div>
</div>
<div className="grid gap-4 lg:grid-cols-[1.3fr,0.7fr]">
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="ابحث باسم المنتج أو الباركود..."
className={`h-14 w-full border border-slate-200 bg-slate-50 px-4 text-right text-lg text-slate-900 transition ${focusRing} ${corners}`}
/>
<div className="rounded-2xl border border-sky-100 bg-sky-50 px-4 py-3 text-sm text-sky-800">
<div className="font-bold">اقتراحات مباشرة</div>
<div className="mt-2 flex flex-wrap gap-2">
{suggestions.length ? (
suggestions.map((product) => (
<button
key={product.id}
type="button"
onClick={() => addProductToCart(product.id)}
className="rounded-full bg-white px-3 py-1.5 font-bold text-slate-700 transition hover:-translate-y-0.5 hover:text-emerald-700"
>
{product.product_name}
</button>
))
) : (
<span>ابدأ الكتابة لعرض الاقتراحات.</span>
)}
</div>
</div>
</div>
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => setActiveCategoryId('all')}
className={`rounded-full px-4 py-2 text-sm font-bold transition ${
activeCategoryId === 'all'
? 'bg-emerald-600 text-white shadow-lg shadow-emerald-100'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
كل الأقسام
</button>
{(workspace.categories || []).map((category) => (
<button
key={category.id}
type="button"
onClick={() => setActiveCategoryId(category.id)}
className={`rounded-full px-4 py-2 text-sm font-bold transition ${
activeCategoryId === category.id
? 'bg-sky-600 text-white shadow-lg shadow-sky-100'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
{category.category_name}
</button>
))}
</div>
</div>
</CardBox>
{(workspace.products || []).length ? (
<div className="grid gap-4 sm:grid-cols-2 2xl:grid-cols-3">
{filteredProducts.map((product) => {
const dollarPrice = product.usd_price ?? ((product.sale_price || 0) / (workspace.selectedShop?.usd_rate || 1));
const lowStock = product.stock_quantity != null && product.stock_quantity <= 3;
return (
<button
key={product.id}
type="button"
onClick={() => addProductToCart(product.id)}
className="group rounded-3xl border border-slate-100 bg-white p-5 text-right shadow-md shadow-slate-100/70 transition duration-200 hover:-translate-y-1 hover:border-emerald-200 hover:shadow-xl"
>
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-lg font-extrabold text-slate-900">{product.product_name}</div>
<div className="mt-1 text-sm text-slate-500">{product.category_name || 'بدون قسم'}</div>
</div>
<span className={`rounded-full px-3 py-1 text-xs font-bold ${lowStock ? 'bg-amber-100 text-amber-700' : 'bg-emerald-50 text-emerald-700'}`}>
{product.stock_quantity == null ? 'مخزون مفتوح' : `المخزون ${product.stock_quantity}`}
</span>
</div>
<div className="mt-6 grid gap-3 sm:grid-cols-2">
<div className="rounded-2xl bg-slate-50 px-3 py-3">
<div className="text-xs font-bold text-slate-500">سعر البيع</div>
<div className="mt-1 text-xl font-extrabold text-slate-900">{formatMoney(product.sale_price || 0)}</div>
</div>
<div className="rounded-2xl bg-sky-50 px-3 py-3">
<div className="text-xs font-bold text-sky-600">السعر بالدولار</div>
<div className="mt-1 text-xl font-extrabold text-sky-900">{formatUsd(dollarPrice)}</div>
</div>
</div>
<div className="mt-4 flex items-center justify-between text-sm text-slate-500">
<span>{product.sku || product.barcode || 'منتج سريع البيع'}</span>
<span className="font-bold text-emerald-700 transition group-hover:text-emerald-800">أضف للفاتورة</span>
</div>
</button>
);
})}
</div>
) : (
emptyProductState
)}
</div>
<div className="space-y-6">
<CardBox className="border-0 bg-white shadow-lg shadow-emerald-100/60">
<div className="space-y-4">
<div>
<h2 className="text-xl font-bold text-slate-900">الفاتورة الحالية</h2>
<p className="text-sm text-slate-500">أزرار كبيرة وواضحة مناسبة للاستخدام داخل المحل.</p>
</div>
<div className="space-y-3">
{cartDetails.length ? (
cartDetails.map((item) => (
<div key={item.productId} className="rounded-2xl border border-slate-100 bg-slate-50 p-4">
<div className="flex items-start justify-between gap-4">
<div>
<div className="font-bold text-slate-900">{item.product.product_name}</div>
<div className="mt-1 text-sm text-slate-500">{item.product.category_name}</div>
</div>
<button
type="button"
onClick={() => updateCartQuantity(item.productId, 0)}
className="text-sm font-bold text-red-500 transition hover:text-red-700"
>
حذف
</button>
</div>
<div className="mt-4 flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => updateCartQuantity(item.productId, item.quantity - 1)}
className="h-10 w-10 rounded-2xl bg-white text-xl font-bold text-slate-700 shadow-sm transition hover:bg-slate-100"
>
-
</button>
<div className="min-w-14 rounded-2xl bg-white px-3 py-2 text-center text-lg font-extrabold text-slate-900 shadow-sm">
{item.quantity}
</div>
<button
type="button"
onClick={() => updateCartQuantity(item.productId, item.quantity + 1)}
className="h-10 w-10 rounded-2xl bg-emerald-600 text-xl font-bold text-white shadow-lg shadow-emerald-100 transition hover:bg-emerald-700"
>
+
</button>
</div>
<div className="text-left">
<div className="text-sm text-slate-500">إجمالي السطر</div>
<div className="text-lg font-extrabold text-slate-900">{formatMoney(item.lineTotal)}</div>
</div>
</div>
</div>
))
) : (
<div className="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-slate-500">
اختر منتجات من القائمة لتكوين الفاتورة.
</div>
)}
</div>
<div className="rounded-3xl bg-slate-900 p-5 text-white shadow-xl shadow-slate-200/80">
<div className="grid gap-3 text-sm">
<div className="flex items-center justify-between">
<span className="text-slate-300">عدد القطع</span>
<span className="text-xl font-extrabold">{cartSummary.quantity}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-300">الإجمالي</span>
<span className="text-2xl font-extrabold text-emerald-300">{formatMoney(cartSummary.total)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-300">الربح المتوقع</span>
<span className="text-lg font-bold text-sky-300">{formatMoney(cartSummary.profit)}</span>
</div>
</div>
</div>
<div>
<label className="mb-2 block text-sm font-bold text-slate-700">طريقة الدفع</label>
<select
value={paymentMethod}
onChange={(event) => setPaymentMethod(event.target.value)}
className={`h-12 w-full border border-slate-200 bg-white px-4 text-right text-slate-800 transition ${focusRing} ${corners}`}
>
<option value="cash">نقدي</option>
<option value="card">بطاقة</option>
<option value="transfer">تحويل</option>
<option value="mixed">مختلط</option>
</select>
</div>
<div>
<label className="mb-2 block text-sm font-bold text-slate-700">ملاحظات الفاتورة</label>
<textarea
value={notes}
onChange={(event) => setNotes(event.target.value)}
placeholder="مثال: زبون دائم - طلب سريع"
className={`min-h-28 w-full border border-slate-200 bg-white px-4 py-3 text-right text-slate-800 transition ${focusRing} ${corners}`}
/>
</div>
<BaseButton
color="success"
label={submitting ? 'جارٍ حفظ الفاتورة...' : canCheckout ? 'تأكيد البيع وحفظ الفاتورة' : 'لا تملك صلاحية إنشاء الفواتير'}
onClick={handleCheckout}
disabled={submitting || !cartDetails.length || !canCheckout}
className="!flex h-14 w-full !items-center !justify-center text-lg font-bold"
/>
</div>
</CardBox>
<CardBox className="border-0 bg-white shadow-lg shadow-sky-100/60">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-slate-900">أدوات سعر الدولار</h2>
<p className="text-sm text-slate-500">تحديث سعر اليوم ثم تطبيق الزيادة أو الرجوع للسعر السابق.</p>
</div>
<span className="rounded-full bg-sky-50 px-3 py-1 text-xs font-bold text-sky-700">
آخر حركة: {workspace.latestPriceChange?.summary || 'لا توجد حركات بعد'}
</span>
</div>
<div>
<label className="mb-2 block text-sm font-bold text-slate-700">سعر الدولار اليومي</label>
<input
value={usdRateInput}
onChange={(event) => setUsdRateInput(event.target.value)}
placeholder="مثال: 1470"
className={`h-12 w-full border border-slate-200 bg-white px-4 text-right text-slate-800 transition ${focusRing} ${corners}`}
/>
</div>
<div className="grid gap-3">
<BaseButton
color="info"
label={pricingBusy ? 'جارٍ الحفظ...' : 'حفظ سعر الدولار'}
onClick={() => handlePricingAction('set_rate')}
disabled={pricingBusy || !canManagePricing}
className="!flex h-12 w-full !items-center !justify-center font-bold"
/>
<BaseButton
color="success"
label={pricingBusy ? 'جارٍ تحديث الأسعار...' : 'تطبيق الأسعار حسب الدولار'}
onClick={() => handlePricingAction('apply_prices')}
disabled={pricingBusy || !canManagePricing}
className="!flex h-12 w-full !items-center !justify-center font-bold"
/>
<BaseButton
color="warning"
label={pricingBusy ? 'جارٍ الاسترجاع...' : 'إرجاع الأسعار السابقة'}
onClick={() => handlePricingAction('restore_prices')}
disabled={pricingBusy || !canManagePricing}
className="!flex h-12 w-full !items-center !justify-center font-bold"
/>
</div>
{!canManagePricing ? <p className="text-xs text-slate-500">هذه الأدوات متاحة لمدير المحل أو من يملك صلاحية تحديث المحلات.</p> : null}
</div>
</CardBox>
<CardBox className="border-0 bg-white shadow-lg shadow-slate-100/80">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-slate-900">فواتير اليوم</h2>
<p className="text-sm text-slate-500">قائمة مباشرة بآخر الفواتير المدفوعة مع الربح المحسوب.</p>
</div>
<BaseButton href="/sales_invoices/sales_invoices-list" color="info" label="كل الفواتير" />
</div>
<div className="space-y-3">
{(workspace.recentInvoices || []).length ? (
workspace.recentInvoices.map((invoice) => (
<Link
key={invoice.id}
href={`/sales_invoices/sales_invoices-view/?id=${invoice.id}`}
className="block rounded-2xl border border-slate-100 bg-slate-50 px-4 py-4 transition hover:-translate-y-0.5 hover:border-emerald-200 hover:bg-white"
>
<div className="flex items-start justify-between gap-4">
<div>
<div className="font-extrabold text-slate-900">{invoice.invoice_number}</div>
<div className="mt-1 text-sm text-slate-500">{formatDateTime(invoice.sold_at)}</div>
</div>
<span className="rounded-full bg-white px-3 py-1 text-xs font-bold text-slate-600 shadow-sm">
{formatSalesInvoicePaymentMethod(invoice.payment_method)}
</span>
</div>
<div className="mt-4 grid gap-2 sm:grid-cols-3">
<div>
<div className="text-xs text-slate-500">إجمالي الفاتورة</div>
<div className="font-bold text-slate-900">{formatMoney(invoice.total_amount)}</div>
</div>
<div>
<div className="text-xs text-slate-500">الربح</div>
<div className="font-bold text-emerald-700">{formatMoney(invoice.total_profit_amount)}</div>
</div>
<div>
<div className="text-xs text-slate-500">عدد القطع</div>
<div className="font-bold text-slate-900">{invoice.item_count}</div>
</div>
</div>
</Link>
))
) : (
<div className="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-slate-500">
لم تُسجَّل أي فاتورة مدفوعة اليوم بعد.
</div>
)}
</div>
</div>
</CardBox>
</div>
</div>
)}
</div>
</SectionMain>
</>
);
};
CashierPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission="READ_PRODUCTS">{page}</LayoutAuthenticated>;
};
export default CashierPage;