feat(kds): filter the kitchen display by station (kitchen / bar)
CI/CD / CI · API (dotnet build + test) (push) Successful in 44s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 30s
CI/CD / CI · Dashboard (tsc) (push) Has been cancelled
CI/CD / CI · Admin Web (tsc) (push) Has been cancelled
CI/CD / CI · Website (tsc) (push) Has been cancelled
CI/CD / CI · Koja (tsc) (push) Has been cancelled
CI/CD / Deploy · all services (push) Has been cancelled
CI/CD / CI · API (dotnet build + test) (push) Successful in 44s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 30s
CI/CD / CI · Dashboard (tsc) (push) Has been cancelled
CI/CD / CI · Admin Web (tsc) (push) Has been cancelled
CI/CD / CI · Website (tsc) (push) Has been cancelled
CI/CD / CI · Koja (tsc) (push) Has been cancelled
CI/CD / Deploy · all services (push) Has been cancelled
Complements the separate kitchen/bar printers with an on-screen split. The live order DTO now carries each item's prep station (MenuItem → Category → KitchenStation), and the KDS shows station tabs (All / Kitchen / Bar / …) that appear only once ≥2 stations are in play. Selecting a station shows just the tickets — and just the items within each ticket — for that station, so bar staff see drinks and kitchen staff see food. Single-station cafés see the board unchanged. fa/en/ar strings added. Note: order status is still per-order (one advance button); the split is for viewing/printing, not per-item status. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -716,6 +716,8 @@
|
||||
"loading": "جاري التحميل...",
|
||||
"live": "مباشر",
|
||||
"polling": "تحديث دوري",
|
||||
"allStations": "الكل",
|
||||
"defaultStation": "المطبخ",
|
||||
"advance": "المرحلة التالية",
|
||||
"status": {
|
||||
"Pending": "قيد الانتظار",
|
||||
|
||||
@@ -750,6 +750,8 @@
|
||||
"loading": "Loading...",
|
||||
"live": "Live",
|
||||
"polling": "Polling",
|
||||
"allStations": "All",
|
||||
"defaultStation": "Kitchen",
|
||||
"advance": "Next step",
|
||||
"status": {
|
||||
"Pending": "Pending",
|
||||
|
||||
@@ -750,6 +750,8 @@
|
||||
"loading": "در حال بارگذاری...",
|
||||
"live": "زنده",
|
||||
"polling": "بهروزرسانی دورهای",
|
||||
"allStations": "همه",
|
||||
"defaultStation": "آشپزخانه",
|
||||
"advance": "مرحله بعد",
|
||||
"status": {
|
||||
"Pending": "در انتظار",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { useEffect, useState, useCallback, useMemo } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
import * as signalR from "@microsoft/signalr";
|
||||
@@ -13,6 +13,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const DEFAULT_STATION = "__default__";
|
||||
|
||||
const STATUS_FLOW: Record<string, string> = {
|
||||
Pending: "Confirmed",
|
||||
Confirmed: "Preparing",
|
||||
@@ -70,6 +72,7 @@ export function KdsScreen() {
|
||||
const cafeId = useAuthStore((s) => s.user?.cafeId);
|
||||
const queryClient = useQueryClient();
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [station, setStation] = useState<string>("all");
|
||||
|
||||
const { data: orders = [], isLoading } = useQuery({
|
||||
queryKey: ["orders-live", cafeId],
|
||||
@@ -120,6 +123,25 @@ export function KdsScreen() {
|
||||
onSuccess: () => refresh(),
|
||||
});
|
||||
|
||||
// Group live items by their prep station (Kitchen/Bar/…). Items with no station
|
||||
// bucket under a default "Kitchen" tab. Tabs only appear once ≥2 stations are
|
||||
// actually in play, so single-station cafés see the board unchanged.
|
||||
const stationOptions = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
for (const o of orders)
|
||||
for (const it of o.items) {
|
||||
const id = it.stationId ?? DEFAULT_STATION;
|
||||
if (!map.has(id)) map.set(id, it.stationName ?? t("defaultStation"));
|
||||
}
|
||||
return Array.from(map, ([id, name]) => ({ id, name }));
|
||||
}, [orders, t]);
|
||||
const showStationTabs = stationOptions.length > 1;
|
||||
|
||||
const itemsForStation = (items: LiveOrder["items"]) =>
|
||||
station === "all" || !showStationTabs
|
||||
? items
|
||||
: items.filter((it) => (it.stationId ?? DEFAULT_STATION) === station);
|
||||
|
||||
if (!cafeId) return null;
|
||||
|
||||
const columns = [
|
||||
@@ -137,6 +159,26 @@ export function KdsScreen() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{showStationTabs ? (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{[{ id: "all", name: t("allStations") }, ...stationOptions].map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
onClick={() => setStation(s.id)}
|
||||
className={cn(
|
||||
"rounded-lg border px-3 py-1.5 text-sm font-medium transition",
|
||||
station === s.id
|
||||
? "border-[#0F6E56] bg-[#E1F5EE] text-[#0F6E56]"
|
||||
: "border-border bg-card text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
{s.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isLoading ? (
|
||||
<p className="text-muted-foreground">{t("loading")}</p>
|
||||
) : orders.length === 0 ? (
|
||||
@@ -148,6 +190,7 @@ export function KdsScreen() {
|
||||
<h3 className="font-semibold">{col.label}</h3>
|
||||
{orders
|
||||
.filter((o) => col.statuses.includes(o.status))
|
||||
.filter((o) => itemsForStation(o.items).length > 0)
|
||||
.map((order) => {
|
||||
const nextStatus = STATUS_FLOW[order.status];
|
||||
return (
|
||||
@@ -174,7 +217,7 @@ export function KdsScreen() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<ul className="text-sm">
|
||||
{order.items.map((item) => (
|
||||
{itemsForStation(order.items).map((item) => (
|
||||
<li key={item.id}>
|
||||
{formatNumber(item.quantity, numberLocale)}×{" "}
|
||||
{item.menuItemName}
|
||||
|
||||
@@ -82,6 +82,9 @@ export interface OrderItemLine {
|
||||
notes?: string;
|
||||
isVoided?: boolean;
|
||||
voidedAt?: string | null;
|
||||
/** Prep station (Kitchen/Bar) the item routes to; only present on live/KDS data. */
|
||||
stationId?: string | null;
|
||||
stationName?: string | null;
|
||||
}
|
||||
|
||||
export interface PaymentLine {
|
||||
|
||||
Reference in New Issue
Block a user