WFDASH-WEBAPP/src/components/markdown.tsx
Dave aed3f5c861 feat: implement discount functionality in POS system
Added support for discounts in the POS checkout process, allowing operators to apply either a fixed amount or a percentage off the gross total. Updated the state management to include discount details and modified the receipt and cart rendering to display pre- and post-discount values. Enhanced the checkout API to handle discount inputs and ensure proper calculations. Updated relevant tests to cover new discount logic.
2026-09-03 12:19:21 +02:00

195 lines
5.4 KiB
TypeScript

import ReactMarkdown, { type Components } from "react-markdown";
import remarkGfm from "remark-gfm";
/**
* Server-rendered markdown for SOPs and the SOP README.
*
* The WFDASH app doesn't depend on `@tailwindcss/typography`, so we
* hand-style the elements that show up in the SOPs (headings,
* paragraphs, lists, tables, code blocks, blockquotes, hr).
*
* The `components` map below is the single source of truth for those
* styles. If a new markdown shape shows up in an SOP, add a class
* here rather than introducing the typography plugin.
*/
export function Markdown({ children }: { children: string }) {
return (
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
{children}
</ReactMarkdown>
);
}
const components: Components = {
h1: ({ children, ...props }) => (
<h1
className="text-2xl font-semibold tracking-tight font-heading-theme mt-6 mb-3 first:mt-0"
{...props}
>
{children}
</h1>
),
h2: ({ children, ...props }) => (
<h2
className="text-lg font-semibold tracking-tight mt-8 mb-3 pb-2 border-b"
{...props}
>
{children}
</h2>
),
h3: ({ children, ...props }) => (
<h3 className="text-base font-semibold mt-6 mb-2" {...props}>
{children}
</h3>
),
h4: ({ children, ...props }) => (
<h4 className="text-sm font-semibold mt-4 mb-1.5" {...props}>
{children}
</h4>
),
p: ({ children, ...props }) => (
<p className="text-sm leading-relaxed my-3" {...props}>
{children}
</p>
),
ul: ({ children, ...props }) => (
<ul
className="text-sm leading-relaxed my-3 ml-6 list-disc space-y-1.5"
{...props}
>
{children}
</ul>
),
ol: ({ children, ...props }) => (
<ol
className="text-sm leading-relaxed my-3 ml-6 list-decimal space-y-1.5"
{...props}
>
{children}
</ol>
),
li: ({ children, ...props }) => (
<li className="pl-0.5" {...props}>
{children}
</li>
),
a: ({ children, href, ...props }) => (
<a
href={href}
className="text-primary underline underline-offset-2 hover:no-underline"
target={href?.startsWith("http") ? "_blank" : undefined}
rel={href?.startsWith("http") ? "noopener noreferrer" : undefined}
{...props}
>
{children}
</a>
),
blockquote: ({ children, ...props }) => (
<blockquote
className="my-4 border-l-4 border-muted-foreground/30 bg-muted/40 px-4 py-2 text-sm italic text-muted-foreground"
{...props}
>
{children}
</blockquote>
),
hr: (props) => (
<hr className="my-6 border-border" {...props} />
),
// GFM tables. The Markdown plugin emits these as a `table` element
// with thead/tbody/tr/th/td children. Wrap in a scrollable container
// so wide KPI tables don't blow out the layout.
table: ({ children, ...props }) => (
<div className="my-4 overflow-x-auto rounded-md border">
<table className="w-full text-sm" {...props}>
{children}
</table>
</div>
),
thead: ({ children, ...props }) => (
<thead className="bg-muted/50" {...props}>
{children}
</thead>
),
tbody: ({ children, ...props }) => (
<tbody className="divide-y" {...props}>
{children}
</tbody>
),
tr: ({ children, ...props }) => (
<tr {...props}>{children}</tr>
),
th: ({ children, ...props }) => (
<th
className="px-3 py-2 text-left font-medium text-foreground text-xs uppercase tracking-wider"
{...props}
>
{children}
</th>
),
td: ({ children, ...props }) => (
<td
className="px-3 py-2 align-top text-sm text-foreground/90"
{...props}
>
{children}
</td>
),
code: ({ children, className, ...props }) => {
// `react-markdown` puts fenced code blocks in a <pre><code> pair
// and inline code in a bare <code>. The presence of a `className`
// (set by `react-markdown` for fenced blocks, e.g. "language-ts")
// is the discriminator. For inline code we just style the chip.
const isBlock = typeof className === "string" && className.length > 0;
if (isBlock) {
return (
<code className={className} {...props}>
{children}
</code>
);
}
return (
<code
className="rounded bg-muted px-1.5 py-0.5 font-mono text-[0.85em] text-foreground/90"
{...props}
>
{children}
</code>
);
},
pre: ({ children, ...props }) => (
<pre
className="my-4 overflow-x-auto rounded-md border bg-muted/30 p-3 text-xs leading-relaxed"
{...props}
>
{children}
</pre>
),
strong: ({ children, ...props }) => (
<strong className="font-semibold text-foreground" {...props}>
{children}
</strong>
),
em: ({ children, ...props }) => (
<em className="italic" {...props}>
{children}
</em>
),
input: ({ type, checked, disabled, ...props }) => {
// GFM task-list checkboxes render as disabled checkboxes. Just
// pass them through — we don't need a custom checkbox component,
// the default browser style is fine and conveys "completed task".
if (type === "checkbox") {
return (
<input
type="checkbox"
checked={checked}
disabled={disabled}
readOnly
className="mr-1.5 align-middle"
{...props}
/>
);
}
return <input type={type} {...props} />;
},
};