Files
ERP/src/components/admin/ActivityFeedMatrix.tsx
T
denis_ireri 472573a1a8 Make the terminal work offline end to end, and add stock screens
Terminal (PRD 5.3/5.5):
- Opens with no network: a service worker caches the app shell and build
  assets, and the last confirmed session and product list are kept on the
  phone. Only a definitive 401/403 sends the cashier to the login page.
- The offline queue is keyed by cashier and branch, sends only the
  signed-in cashier's sales, chunks to the server's batch limit, and acts
  on the per-sale result: synced, or rejected with the reason shown and
  never retried. Rows from the old queue are adopted on first sync.
- Several products per sale (tap to add), optional customer details, and
  a printable receipt. The banner shows sales waiting to sync and any
  that need attention; sign-out warns about unsynced sales.
- M-Pesa only appears when FEATURE_MPESA is on. A timed-out prompt now
  tells the cashier not to take cash until the payment is checked,
  instead of reporting it as failed.

Admin:
- New Stock page: stock per branch with low/out flags, physical counts
  and adjustments, inter-branch transfers, morning dispatch and evening
  count entry, and recent movements.
- Dashboard shows stock on hand per branch and links to Stock.
- Staff page can deactivate and reactivate accounts.
- Products page is read-only for branch managers and edits the low-stock
  alert level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:46:23 +03:00

73 lines
3.2 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.
"use client";
import { Transaction } from "@/lib/types";
import { formatDistanceToNow } from "date-fns";
import EmptyState from "@/components/ui/EmptyState";
interface Props {
transactions: Transaction[];
branchNames: Record<string, string>;
connected: boolean;
scope: "global" | "branch";
}
const statusStyles: Record<string, string> = {
SUCCESS: "bg-confirm-500/15 text-confirm-600",
PENDING: "bg-cash-500/15 text-cash-600",
FAILED: "bg-alert-500/15 text-alert-600",
};
export default function ActivityFeedMatrix({ transactions, branchNames, connected, scope }: Props) {
return (
<div className="rounded-xl2 bg-white shadow-soft">
<div className="flex items-center justify-between border-b border-depth-100 px-4 py-3">
<h3 className="font-display font-semibold text-depth-900">Live activity feed</h3>
<span className={"flex items-center gap-1.5 text-xs " + (connected ? "text-confirm-600" : "text-alert-600")}>
<span className={"h-1.5 w-1.5 rounded-full " + (connected ? "animate-pulse bg-confirm-500" : "bg-alert-500")} />
{connected ? "Live" : "Reconnecting…"}
</span>
</div>
<ul className="max-h-[32rem] divide-y divide-depth-100 overflow-y-auto">
{transactions.length === 0 && (
<li>
<EmptyState
icon="📡"
title="No sales yet"
description="As soon as a delivery worker records a sale anywhere in your network, it'll appear here in real time."
/>
</li>
)}
{transactions.map((t) => {
const branchId = typeof t.branchId === "string" ? t.branchId : t.branchId._id;
const branchLabel = typeof t.branchId === "object" ? t.branchId.branchName : branchNames[branchId] ?? "—";
return (
<li key={t._id} className="flex items-center justify-between gap-3 px-4 py-3 animate-fade-up">
<div className="min-w-0">
<div className="flex items-center gap-2">
<p className="truncate text-sm font-medium text-depth-900">{t.customerName || t.customerPhone || "Walk-in customer"}</p>
{scope === "global" && (
<span className="whitespace-nowrap rounded-full bg-depth-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-depth-600">
{branchLabel}
</span>
)}
</div>
<p className="text-xs text-depth-500">
{t.quantity} × {t.sizeLiters}L · {t.paymentMethod === "CASH" ? "Cash" : "M-Pesa"} ·{" "}
{formatDistanceToNow(new Date(t.createdAt), { addSuffix: true })}
</p>
</div>
<div className="flex flex-shrink-0 flex-col items-end gap-1">
<span className="font-mono text-sm font-semibold text-depth-900">KES {t.amountTotal.toLocaleString()}</span>
<span className={"rounded-full px-2 py-0.5 text-[11px] font-medium " + statusStyles[t.paymentStatus]}>
{t.paymentStatus}
</span>
</div>
</li>
);
})}
</ul>
</div>
);
}