Compare commits
2 Commits
a855cf1d80
...
456a446850
| Author | SHA1 | Date | |
|---|---|---|---|
| 456a446850 | |||
| 4523c8861f |
@@ -1,5 +1,9 @@
|
||||
import { BranchesScreen } from "@/components/branches/branches-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "شعب" };
|
||||
|
||||
export default function BranchesPage() {
|
||||
return <BranchesScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { CouponsScreen } from "@/components/coupons/coupons-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "کوپنها" };
|
||||
|
||||
export default function CouponsPage() {
|
||||
return <CouponsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { CrmScreen } from "@/components/crm/crm-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "مشتریان" };
|
||||
|
||||
export default function CrmPage() {
|
||||
return <CrmScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ExpensesScreen } from "@/components/expenses/expenses-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "هزینهها" };
|
||||
|
||||
export default function ExpensesPage() {
|
||||
return <ExpensesScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { HrScreen } from "@/components/hr/hr-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "منابع انسانی" };
|
||||
|
||||
export default function HrPage() {
|
||||
return <HrScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { KdsScreen } from "@/components/kds/kds-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "آشپزخانه" };
|
||||
|
||||
export default function KdsPage() {
|
||||
return <KdsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { MenuAdminScreen } from "@/components/menu/menu-admin-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "منو" };
|
||||
|
||||
export default function MenuPage() {
|
||||
return <MenuAdminScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { NotificationsScreen } from "@/components/notifications/notifications-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "اعلانها" };
|
||||
|
||||
export default function NotificationsPage() {
|
||||
return <NotificationsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { OverviewScreen } from "@/components/overview/overview-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "داشبورد" };
|
||||
|
||||
export default function HomePage() {
|
||||
return <OverviewScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { QueueScreen } from "@/components/queue/queue-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "نوبتدهی" };
|
||||
|
||||
export default function QueuePage() {
|
||||
return <QueueScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ReportsScreen } from "@/components/reports/reports-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "گزارشها" };
|
||||
|
||||
export default function ReportsPage() {
|
||||
return <ReportsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ReservationsScreen } from "@/components/reservations/reservations-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "رزرو" };
|
||||
|
||||
export default function ReservationsPage() {
|
||||
return <ReservationsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ReviewsScreen } from "@/components/reviews/reviews-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "نظرات" };
|
||||
|
||||
export default function ReviewsPage() {
|
||||
return <ReviewsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Suspense } from "react";
|
||||
import { SettingsScreen } from "@/components/settings/settings-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "تنظیمات" };
|
||||
|
||||
export default function SettingsPage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ShiftsScreen } from "@/components/shifts/shifts-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "بستن شیفت" };
|
||||
|
||||
export default function ShiftsPage() {
|
||||
return <ShiftsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { SmsScreen } from "@/components/sms/sms-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "پیامک" };
|
||||
|
||||
export default function SmsPage() {
|
||||
return <SmsScreen />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Suspense } from "react";
|
||||
import { CheckoutScreen } from "@/components/subscription/checkout-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "تسویه اشتراک" };
|
||||
|
||||
export default function SubscriptionCheckoutPage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Suspense } from "react";
|
||||
import { SubscriptionScreen } from "@/components/subscription/subscription-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "اشتراک و پلن" };
|
||||
|
||||
export default function SubscriptionPage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { SupportTicketDetailScreen } from "@/components/support/support-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "تیکت پشتیبانی" };
|
||||
|
||||
export default function SupportTicketPage() {
|
||||
return <SupportTicketDetailScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { SupportScreen } from "@/components/support/support-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "پشتیبانی" };
|
||||
|
||||
export default function SupportPage() {
|
||||
return <SupportScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { TablesScreen } from "@/components/tables/tables-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "میزها" };
|
||||
|
||||
export default function TablesPage() {
|
||||
return <TablesScreen />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { TaxesScreen } from "@/components/taxes/taxes-screen";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "مالیات" };
|
||||
|
||||
export default function TaxesPage() {
|
||||
return <TaxesScreen />;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||
import { notFound } from "next/navigation";
|
||||
@@ -6,6 +7,42 @@ import { routing } from "@/i18n/routing";
|
||||
import { Providers } from "@/components/providers";
|
||||
import "../globals.css";
|
||||
|
||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://app.meezi.ir";
|
||||
|
||||
// Default metadata for the whole panel. `title.template` gives every page a
|
||||
// "<page> — میزی" title; per-page `metadata.title` fills in the page name.
|
||||
// Icons/manifest here also fix pages that previously had no favicon/app icon.
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(SITE_URL),
|
||||
title: {
|
||||
default: "میزی — پنل مدیریت کافه",
|
||||
template: "%s — میزی",
|
||||
},
|
||||
description: "پنل مدیریت کافه و رستوران میزی — صندوق، منو، گزارش و مدیریت.",
|
||||
applicationName: "میزی",
|
||||
manifest: "/manifest.webmanifest",
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
|
||||
{ url: "/icons/icon-512.png", sizes: "512x512", type: "image/png" },
|
||||
],
|
||||
shortcut: "/icons/icon-192.png",
|
||||
apple: "/icons/icon-192.png",
|
||||
},
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
title: "میزی",
|
||||
statusBarStyle: "default",
|
||||
},
|
||||
robots: { index: false, follow: false }, // private merchant panel
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#0F6E56",
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
const vazirmatn = localFont({
|
||||
src: "../../fonts/Vazirmatn-Variable.woff2",
|
||||
variable: "--font-vazirmatn",
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import localFont from "next/font/local";
|
||||
import faMessages from "../../../messages/fa.json";
|
||||
import { MeeziToaster } from "@/components/ui/meezi-toaster";
|
||||
import "../globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "منوی کافه — میزی",
|
||||
description: "مشاهده منو و ثبت سفارش از میز",
|
||||
manifest: "/manifest.webmanifest",
|
||||
icons: { icon: "/icons/icon-192.png", apple: "/icons/icon-192.png" },
|
||||
robots: { index: false, follow: false },
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#0F6E56",
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
const vazirmatn = localFont({
|
||||
src: "../../fonts/Vazirmatn-Variable.woff2",
|
||||
variable: "--font-vazirmatn",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { isoTodayTehran } from "@/lib/reports/analytics";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { MoneyInput } from "@/components/ui/money-input";
|
||||
import { JalaliDateField } from "@/components/ui/jalali-date-field";
|
||||
import { LabeledField } from "@/components/ui/labeled-field";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
@@ -291,14 +292,10 @@ export function ExpensesScreen() {
|
||||
</select>
|
||||
</LabeledField>
|
||||
<LabeledField label={t("amount")} htmlFor="exp-amount">
|
||||
<Input
|
||||
<MoneyInput
|
||||
id="exp-amount"
|
||||
type="number"
|
||||
min={1}
|
||||
dir="ltr"
|
||||
className="text-end"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
onValueChange={setAmount}
|
||||
/>
|
||||
</LabeledField>
|
||||
<LabeledField label={t("note")} htmlFor="exp-note">
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@/lib/api/branch-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { MoneyInput } from "@/components/ui/money-input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MenuItemLabels } from "@/components/menu/menu-item-labels";
|
||||
@@ -164,11 +165,11 @@ export function BranchMenuOverrides({
|
||||
<td className="px-3 py-2">
|
||||
{canOverridePrice ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
<MoneyInput
|
||||
className="h-8 w-28 text-xs"
|
||||
value={priceDraft[row.id] ?? String(row.effectivePrice)}
|
||||
onChange={(e) =>
|
||||
setPriceDraft((d) => ({ ...d, [row.id]: e.target.value }))
|
||||
onValueChange={(raw) =>
|
||||
setPriceDraft((d) => ({ ...d, [row.id]: raw }))
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -32,6 +32,7 @@ import { PageHeader } from "@/components/layout/page-header";
|
||||
import { MediaPairUpload } from "@/components/media/media-pair-upload";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { MoneyInput } from "@/components/ui/money-input";
|
||||
import { LabeledField } from "@/components/ui/labeled-field";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -872,13 +873,10 @@ export function MenuAdminScreen() {
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<LabeledField label={t("price")} htmlFor="modal-item-price">
|
||||
<Input
|
||||
<MoneyInput
|
||||
id="modal-item-price"
|
||||
value={itemForm.price}
|
||||
onChange={(e) => setItemForm((f) => ({ ...f, price: e.target.value }))}
|
||||
inputMode="numeric"
|
||||
dir="ltr"
|
||||
className="text-end"
|
||||
onValueChange={(raw) => setItemForm((f) => ({ ...f, price: raw }))}
|
||||
/>
|
||||
</LabeledField>
|
||||
<LabeledField label={t("discountPercent")} htmlFor="modal-item-discount">
|
||||
|
||||
@@ -11,7 +11,7 @@ import { formatCurrency, formatNumber } from "@/lib/format";
|
||||
import { isoTodayTehran } from "@/lib/reports/analytics";
|
||||
import type { Order, PaymentLine } from "@/lib/api/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { MoneyInput } from "@/components/ui/money-input";
|
||||
import { JalaliDateField } from "@/components/ui/jalali-date-field";
|
||||
import { LabeledField } from "@/components/ui/labeled-field";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
@@ -395,14 +395,11 @@ function CorrectionDialog({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Input
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
min={0}
|
||||
<MoneyInput
|
||||
placeholder={t("amount")}
|
||||
className="h-9 flex-1 tabular-nums"
|
||||
className="h-9 flex-1"
|
||||
value={r.amount}
|
||||
onChange={(e) => patchReplacement(i, { amount: e.target.value })}
|
||||
onValueChange={(raw) => patchReplacement(i, { amount: raw })}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* Money input for Toman amounts: shows grouped thousands separators while the
|
||||
* user types (e.g. 1490000 → 1,490,000) but reports a raw digit string via
|
||||
* onValueChange so callers keep parsing with Number()/parseFloat() unchanged.
|
||||
* Accepts Persian/Arabic digits on input and normalizes them to Latin.
|
||||
*/
|
||||
export type MoneyInputProps = Omit<
|
||||
React.ComponentProps<typeof Input>,
|
||||
"value" | "onChange" | "inputMode" | "type"
|
||||
> & {
|
||||
/** Raw digit string, e.g. "1490000" (no separators). */
|
||||
value: string;
|
||||
/** Called with the normalized raw digit string. */
|
||||
onValueChange: (rawDigits: string) => void;
|
||||
};
|
||||
|
||||
const PERSIAN_DIGITS = "۰۱۲۳۴۵۶۷۸۹";
|
||||
const ARABIC_DIGITS = "٠١٢٣٤٥٦٧٨٩";
|
||||
|
||||
function toRawDigits(input: string): string {
|
||||
let out = "";
|
||||
for (const ch of input) {
|
||||
const fa = PERSIAN_DIGITS.indexOf(ch);
|
||||
const ar = ARABIC_DIGITS.indexOf(ch);
|
||||
if (ch >= "0" && ch <= "9") out += ch;
|
||||
else if (fa !== -1) out += String(fa);
|
||||
else if (ar !== -1) out += String(ar);
|
||||
// ignore separators, spaces, and anything else
|
||||
}
|
||||
// strip leading zeros but keep a single "0"
|
||||
out = out.replace(/^0+(?=\d)/, "");
|
||||
return out;
|
||||
}
|
||||
|
||||
function group(raw: string): string {
|
||||
if (!raw) return "";
|
||||
return Number(raw).toLocaleString("en-US");
|
||||
}
|
||||
|
||||
export const MoneyInput = React.forwardRef<HTMLInputElement, MoneyInputProps>(
|
||||
({ value, onValueChange, className, ...props }, ref) => {
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
value={group(value)}
|
||||
onChange={(e) => onValueChange(toRawDigits(e.target.value))}
|
||||
inputMode="numeric"
|
||||
dir="ltr"
|
||||
className={cn("text-end tabular-nums", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
MoneyInput.displayName = "MoneyInput";
|
||||
Reference in New Issue
Block a user