This commit is contained in:
seb
2026-10-04 19:12:55 +02:00
parent 9146ebaba1
commit 669954b496
7 changed files with 145 additions and 114 deletions

View File

@@ -84,7 +84,7 @@ flow:
| **Build & Dev** | Vite 8 (`type: module`) | Dev server on `4209`, HTTPS on `4308`, proxies `/api` → `http://localhost:4208` |
| **Framework** | React 19 (`react`, `react-dom`) | Uses `PureComponent` (with Automats & dashgui) and modern function components |
| **Routing** | `react-router-dom` v7 | Configured in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx) with HMR router preservation |
| **UI & Theming** | MUI 9 (`@mui/material`, Emotion) | Custom theme in `src/theme/`, base background `#c5ddd8` in `src/colors.js` |
| **UI & Theming** | MUI 9 (`@mui/material`, Emotion) | Custom theme in `src/theme/`, base background `#85b7ad` in `src/colors.js` |
| **Shared GUI** | `dashgui` (`file:../dashgui`) | `PanelTile`, `RecordList`, `Tile`, `SingleLineTextEdit`, `ElevatedPill` |
| **State Management** | `react-automat` | Single state layer for all domain & configuration data (articles, categories, company, brands, units, POS terminals, session) |
| **Notifications** | `notistack` | `useSnackbar()` / `<SnackbarProvider>` in [src/main.jsx](file:///home/seb/src/dashfront/src/main.jsx) |

View File

@@ -1,6 +1,7 @@
<!doctype html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
@@ -12,7 +13,7 @@
body,
#root {
min-height: 100%;
background-color: #c5ddd8;
background-color: #85b7ad;
}
html,
@@ -25,8 +26,9 @@
scrollbar-gutter: stable;
}
</style>
</head>
<body>
</head>
<body>
<div id="root"></div>
<script>
(function () {
@@ -52,5 +54,6 @@
})();
</script>
<script type="module" src="/src/main.jsx"></script>
</body>
</body>
</html>

View File

@@ -1,2 +1,2 @@
// Mirrored in index.html and index.css for paint before JS loads.
export const MAIN_BACKGROUND = '#c5ddd8';
export const MAIN_BACKGROUND = '#85b7ad';

View File

@@ -112,6 +112,7 @@ export default class TextInput extends Component {
const mergedSlotProps = {
...restSlotProps,
...(resolvedInputProps ? { input: resolvedInputProps } : {}),
inputLabel: {
...(label ? { shrink: true } : {}),
...InputLabelProps,
@@ -128,7 +129,6 @@ export default class TextInput extends Component {
autoComplete="one-time-code"
onFocus={this.handleFocus}
onMouseDown={this.handleMouseDown}
InputProps={resolvedInputProps}
slotProps={mergedSlotProps}
sx={resolveSx(sx)}
{...rest}

View File

@@ -246,20 +246,19 @@ export default class ArticleAttributesTab extends PureComponent {
const isExclusive = Boolean(targetAttr.exclusive);
const targetValId = Number(effectiveItemId);
const isAlreadyAssigned = assignedAttributes.some((a) => a.attributeValueId === targetValId);
const isMerkmalAlreadyAssigned = assignedAttributes.some((a) => a.attributeId === targetAttr.id);
if (isExclusive && isMerkmalAlreadyAssigned) {
enqueueSnackbar('Eindeutiges Merkmal ist bereits zugeordnet.', { variant: 'info' });
return;
}
if (isAlreadyAssigned) {
enqueueSnackbar('Bereits zugeordnet.', { variant: 'info' });
return;
}
const nextValues = isExclusive
? [
...assignedAttributes
.filter((a) => a.attributeId !== targetAttr.id)
.map((a) => a.attributeValueId),
targetValId,
]
: [...assignedAttributes.map((a) => a.attributeValueId), targetValId];
const nextValues = [...assignedAttributes.map((a) => a.attributeValueId), targetValId];
this.setState({ saving: true });
try {
@@ -282,6 +281,13 @@ export default class ArticleAttributesTab extends PureComponent {
if (!targetProp || !val) return;
const isExclusive = Boolean(targetProp.exclusive);
const isMerkmalAlreadyAssigned = assignedProperties.some((p) => p.propertyId === propId);
if (isExclusive && isMerkmalAlreadyAssigned) {
enqueueSnackbar('Eindeutiges Merkmal ist bereits zugeordnet.', { variant: 'info' });
return;
}
if (!isExclusive) {
const isAlreadyAssigned = assignedProperties.some(
(p) => p.propertyId === propId && p.value.trim().toLowerCase() === val.toLowerCase()
@@ -291,11 +297,8 @@ export default class ArticleAttributesTab extends PureComponent {
return;
}
}
const sourceProps = isExclusive
? assignedProperties.filter((p) => p.propertyId !== propId)
: assignedProperties;
const nextProps = [
...sourceProps.map((p) => ({ propertyId: p.propertyId, value: p.value, sort: p.sort })),
...assignedProperties.map((p) => ({ propertyId: p.propertyId, value: p.value, sort: p.sort })),
{ propertyId: propId, value: val },
];
@@ -470,6 +473,36 @@ export default class ArticleAttributesTab extends PureComponent {
assignedGroups.sort((a, b) => a.sort - b.sort || a.name.localeCompare(b.name));
const isExclusive = Boolean(selectedOption?.exclusive);
const isMerkmalAlreadyAssigned = isAttr
? assignedAttributes.some((a) => a.attributeId === selectedOption?.id)
: assignedProperties.some((p) => p.propertyId === selectedOption?.id);
const isValueAlreadyAssigned = isAttr
? assignedAttributes.some((a) => a.attributeValueId === Number(currentItemId))
: (Boolean(customValueText.trim()) && assignedProperties.some(
(p) => p.propertyId === selectedOption?.id && p.value.trim().toLowerCase() === customValueText.trim().toLowerCase()
));
const isUniqueAlreadyAdded = isExclusive && isMerkmalAlreadyAssigned;
const isAddDisabled =
saving ||
isVariantBlocked ||
!selectedMerkmalKey ||
(isAttr ? !currentItemId || availableItems.length === 0 : !customValueText.trim()) ||
isUniqueAlreadyAdded ||
isValueAlreadyAssigned;
let addTooltipTitle = 'Hinzufügen';
if (isUniqueAlreadyAdded) {
addTooltipTitle = 'Eindeutiges Merkmal ist bereits zugeordnet';
} else if (isValueAlreadyAssigned) {
addTooltipTitle = 'Bereits zugeordnet';
} else if (isVariantBlocked) {
addTooltipTitle = 'Varianten-Merkmal kann nicht zugeordnet werden';
}
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{/* Unified Add Bar: [Merkmal ▼] [Wert ▼ / Input] [+] */}
@@ -531,16 +564,11 @@ export default class ArticleAttributesTab extends PureComponent {
/>
)}
<Tooltip title="Hinzufügen">
<Tooltip title={addTooltipTitle}>
<span>
<IconButton
color="primary"
disabled={
saving ||
isVariantBlocked ||
!selectedMerkmalKey ||
(isAttr ? !selectedItemId || availableItems.length === 0 : !customValueText.trim())
}
disabled={isAddDisabled}
onClick={() => void this.handleAddItem()}
sx={{
bgcolor: 'primary.main',

View File

@@ -5,7 +5,7 @@ html,
body,
#root {
min-height: 100%;
background-color: #c5ddd8;
background-color: #85b7ad;
}
html,

View File

@@ -298,7 +298,7 @@ export default defineConfig(({ command }) => ({
start_url: '/',
scope: '/',
display: 'standalone',
background_color: '#c5ddd8',
background_color: '#85b7ad',
theme_color: '#3b5bdb',
icons: [
{