diff --git a/src/admin/pages/InvoiceDetail.tsx b/src/admin/pages/InvoiceDetail.tsx index a767590..ccebc2c 100644 --- a/src/admin/pages/InvoiceDetail.tsx +++ b/src/admin/pages/InvoiceDetail.tsx @@ -1140,7 +1140,11 @@ export default function InvoiceDetail() { )} {hasPermission("invoices.delete") && ( - )} @@ -1710,7 +1714,11 @@ export default function InvoiceDetail() { ))} {hasPermission("invoices.delete") && ( - )} diff --git a/src/admin/pages/OfferDetail.tsx b/src/admin/pages/OfferDetail.tsx index 96fc44a..c45780c 100644 --- a/src/admin/pages/OfferDetail.tsx +++ b/src/admin/pages/OfferDetail.tsx @@ -1184,7 +1184,11 @@ export default function OfferDetail() { )} {isEdit && hasPermission("offers.delete") && ( - )} diff --git a/src/admin/pages/WarehouseInventory.tsx b/src/admin/pages/WarehouseInventory.tsx index c15a8b8..9509950 100644 --- a/src/admin/pages/WarehouseInventory.tsx +++ b/src/admin/pages/WarehouseInventory.tsx @@ -157,7 +157,7 @@ export default function WarehouseInventory() { /> {statusFilter && ( - )} diff --git a/src/admin/pages/WarehouseIssues.tsx b/src/admin/pages/WarehouseIssues.tsx index 88e3ffb..30c6bb3 100644 --- a/src/admin/pages/WarehouseIssues.tsx +++ b/src/admin/pages/WarehouseIssues.tsx @@ -248,7 +248,7 @@ export default function WarehouseIssues() { /> {hasActiveFilters && ( - )} diff --git a/src/admin/pages/WarehouseReceipts.tsx b/src/admin/pages/WarehouseReceipts.tsx index ab56396..840bf53 100644 --- a/src/admin/pages/WarehouseReceipts.tsx +++ b/src/admin/pages/WarehouseReceipts.tsx @@ -247,7 +247,7 @@ export default function WarehouseReceipts() { /> {hasActiveFilters && ( - )} diff --git a/src/admin/pages/WarehouseReservations.tsx b/src/admin/pages/WarehouseReservations.tsx index 5ea3ba6..8827fdb 100644 --- a/src/admin/pages/WarehouseReservations.tsx +++ b/src/admin/pages/WarehouseReservations.tsx @@ -376,7 +376,7 @@ export default function WarehouseReservations() { )} {hasActiveFilters && ( - )} diff --git a/src/admin/theme.ts b/src/admin/theme.ts index a340268..d5e5a99 100644 --- a/src/admin/theme.ts +++ b/src/admin/theme.ts @@ -7,6 +7,19 @@ const FONT_MONO = "'DM Mono', Menlo, monospace"; // Standard Material easing (matches the legacy --transition cubic-bezier). const EASE = "cubic-bezier(0.4, 0, 0.2, 1)"; +// Dark-mode fills for FILLED colored surfaces (contained buttons, filled chips, +// StatCard badges). The palette .main stays BRIGHT in dark mode (it drives text, +// outlined buttons and row tints), but a bright fill makes WHITE text/glyphs +// illegible — so filled surfaces drop to these darker shades (≈ the light-scheme +// values) in dark mode. One rule everywhere: filled colored = white text, +// readable in both themes. Light mode needs no override (its .main is dark). +export const FILLED_DARK_BG = { + error: { bg: "#dc2626", hover: "#b91c1c" }, + success: { bg: "#15803d", hover: "#166534" }, + warning: { bg: "#b45309", hover: "#92400e" }, + info: { bg: "#1d4ed8", hover: "#1e40af" }, +} as const; + export const theme = createTheme({ cssVariables: { colorSchemeSelector: "[data-theme='%s']", @@ -80,6 +93,47 @@ export const theme = createTheme({ }, "&:active": { transform: "translateY(0) scale(0.97)" }, }, + // Filled colored buttons: WHITE text in BOTH themes (was per-scheme + // contrastText → near-black text on colored fills in dark mode, which + // also clashed with the always-white primary — "black text on one red + // button, white on another"). In dark mode the fill drops to a darker + // shade so white stays legible. + containedError: ({ theme }) => ({ + color: "#fff", + ...theme.applyStyles("dark", { + "&:not(.Mui-disabled)": { + backgroundColor: FILLED_DARK_BG.error.bg, + "&:hover": { backgroundColor: FILLED_DARK_BG.error.hover }, + }, + }), + }), + containedSuccess: ({ theme }) => ({ + color: "#fff", + ...theme.applyStyles("dark", { + "&:not(.Mui-disabled)": { + backgroundColor: FILLED_DARK_BG.success.bg, + "&:hover": { backgroundColor: FILLED_DARK_BG.success.hover }, + }, + }), + }), + containedWarning: ({ theme }) => ({ + color: "#fff", + ...theme.applyStyles("dark", { + "&:not(.Mui-disabled)": { + backgroundColor: FILLED_DARK_BG.warning.bg, + "&:hover": { backgroundColor: FILLED_DARK_BG.warning.hover }, + }, + }), + }), + containedInfo: ({ theme }) => ({ + color: "#fff", + ...theme.applyStyles("dark", { + "&:not(.Mui-disabled)": { + backgroundColor: FILLED_DARK_BG.info.bg, + "&:hover": { backgroundColor: FILLED_DARK_BG.info.hover }, + }, + }), + }), }, }, MuiCard: { @@ -99,6 +153,54 @@ export const theme = createTheme({ fontWeight: 700, transition: `background-color 200ms ${EASE}, box-shadow 200ms ${EASE}`, }, + // Filled colored chips follow the same rule as filled buttons: white + // label in both themes, darker fill in dark mode so white stays legible. + // (Chip has no per-color `filledX` key, so target the color class and + // scope to the filled variant.) + colorError: ({ theme }) => ({ + "&.MuiChip-filled": { + color: "#fff", + ...theme.applyStyles("dark", { + backgroundColor: FILLED_DARK_BG.error.bg, + "&.MuiChip-clickable:hover": { + backgroundColor: FILLED_DARK_BG.error.hover, + }, + }), + }, + }), + colorSuccess: ({ theme }) => ({ + "&.MuiChip-filled": { + color: "#fff", + ...theme.applyStyles("dark", { + backgroundColor: FILLED_DARK_BG.success.bg, + "&.MuiChip-clickable:hover": { + backgroundColor: FILLED_DARK_BG.success.hover, + }, + }), + }, + }), + colorWarning: ({ theme }) => ({ + "&.MuiChip-filled": { + color: "#fff", + ...theme.applyStyles("dark", { + backgroundColor: FILLED_DARK_BG.warning.bg, + "&.MuiChip-clickable:hover": { + backgroundColor: FILLED_DARK_BG.warning.hover, + }, + }), + }, + }), + colorInfo: ({ theme }) => ({ + "&.MuiChip-filled": { + color: "#fff", + ...theme.applyStyles("dark", { + backgroundColor: FILLED_DARK_BG.info.bg, + "&.MuiChip-clickable:hover": { + backgroundColor: FILLED_DARK_BG.info.hover, + }, + }), + }, + }), }, }, MuiOutlinedInput: { diff --git a/src/admin/ui/StatCard.tsx b/src/admin/ui/StatCard.tsx index ec9cdaa..ff5d55a 100644 --- a/src/admin/ui/StatCard.tsx +++ b/src/admin/ui/StatCard.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Card from "./Card"; +import { FILLED_DARK_BG } from "../theme"; export type StatCardColor = | "default" @@ -31,16 +32,19 @@ export default function StatCard({ footer, }: StatCardProps) { // Solid tile + white glyph: high-contrast in both themes (the old light tint - // left the icon dark/low-visibility, esp. for the default variant). + // left the icon dark/low-visibility, esp. for the default variant). In dark + // mode the bright .main fill is darkened (same rule as filled buttons/chips) + // so the white glyph stays legible on success/warning/info/error tiles. const iconBg = color === "default" ? "grey.600" : `${color}.main`; - const iconColor = "common.white"; + const iconDarkBg = (FILLED_DARK_BG as Record)[color] + ?.bg; return ( {icon && ( ({ display: "flex", alignItems: "center", justifyContent: "center", @@ -48,9 +52,12 @@ export default function StatCard({ height: 40, borderRadius: 2, bgcolor: iconBg, - color: iconColor, + color: "common.white", flexShrink: 0, - }} + ...(iconDarkBg + ? theme.applyStyles("dark", { backgroundColor: iconDarkBg }) + : {}), + })} > {icon}