This commit is contained in:
seb
2026-10-05 01:01:03 +02:00
parent 81b59fd309
commit b766769634
59 changed files with 903 additions and 280 deletions

View File

@@ -13,6 +13,9 @@ import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
import { artikelQueryPath } from './article/artikelQuery';
import { defaultCategoryPath } from './category/categoryRoute';
import { navigationAutomat } from '../automats';
import {
NAV_SURFACE,
} from '../colors';
import { ChatFab, ChatDrawer } from './chat';
@@ -51,7 +54,7 @@ class AppSidebar extends PureComponent {
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.nav.backgroundColor || '#1a3533',
backgroundColor: theme.nav.backgroundColor || NAV_SURFACE,
background: theme.nav.background,
position: 'sticky',
top: 0,

View File

@@ -8,6 +8,13 @@ import Popper from '@mui/material/Popper';
import Typography from '@mui/material/Typography';
import CloseIcon from '@mui/icons-material/Close';
import { helpAutomat } from '../automats';
import {
RED,
RED_HOVER,
SHADOW_BADGE,
SHADOW_POPOVER,
WHITE,
} from '../colors';
let nextHelpBadgeId = 0;
@@ -159,7 +166,7 @@ class HelpBadge extends PureComponent {
borderRadius: 2,
border: '1px solid',
borderColor: 'divider',
boxShadow: '0 8px 28px rgba(0,0,0,0.2)',
boxShadow: SHADOW_POPOVER,
}}
>
<Box
@@ -177,8 +184,8 @@ class HelpBadge extends PureComponent {
width: 18,
height: 18,
borderRadius: '50%',
bgcolor: '#dc2626',
color: '#ffffff',
bgcolor: RED,
color: WHITE,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -246,15 +253,15 @@ class HelpBadge extends PureComponent {
onClick={this.handleBadgeClick}
onKeyDown={this.handleBadgeKeyDown}
sx={{
bgcolor: '#dc2626',
color: '#ffffff',
bgcolor: RED,
color: WHITE,
fontWeight: 800,
fontSize: '0.78rem',
width: 20,
height: 20,
minWidth: 20,
borderRadius: '50%',
boxShadow: '0 2px 5px rgba(0,0,0,0.35)',
boxShadow: SHADOW_BADGE,
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
@@ -262,7 +269,7 @@ class HelpBadge extends PureComponent {
userSelect: 'none',
transition: 'background-color 0.15s ease',
'&:hover': {
bgcolor: '#b91c1c',
bgcolor: RED_HOVER,
},
...badgeSx,
...sx,
@@ -308,20 +315,20 @@ class HelpBadge extends PureComponent {
sx={{
display: 'inline-flex',
'& .MuiBadge-badge': {
bgcolor: '#dc2626',
color: '#ffffff',
bgcolor: RED,
color: WHITE,
fontWeight: 800,
fontSize: '0.78rem',
width: 20,
height: 20,
minWidth: 20,
borderRadius: '50%',
boxShadow: '0 2px 5px rgba(0,0,0,0.35)',
boxShadow: SHADOW_BADGE,
cursor: 'pointer',
p: 0,
transition: 'background-color 0.15s ease',
'&:hover': {
bgcolor: '#b91c1c',
bgcolor: RED_HOVER,
},
...badgeSx,
},

View File

@@ -17,6 +17,9 @@ import {
} from '../auth/googleSignIn';
import quixposLogo from '../assets/quixpos-logo.png';
import {
NAV_SURFACE,
} from '../colors';
class LoginPage extends PureComponent {
constructor(props) {
@@ -127,7 +130,7 @@ class LoginPage extends PureComponent {
<Typography
variant="h5"
component="h1"
sx={{ fontWeight: 700, color: '#1a3533', letterSpacing: '-0.01em' }}
sx={{ fontWeight: 700, color: NAV_SURFACE, letterSpacing: '-0.01em' }}
>
QuixPOS
</Typography>

View File

@@ -29,6 +29,14 @@ import artikelAutomat from '../../automats/artikelAutomat';
import { articleWithUploadedPhoto } from '../../utils/articleDetail';
import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto';
import { requireObject } from '../../utils/apiShape';
import {
CHECKERBOARD,
INFO_RIBBON,
SCRIM_LABEL,
VEIL_CARD,
VEIL_OVERLAY,
WHITE,
} from '../../colors';
function computeDefaultQuery(article) {
@@ -502,9 +510,8 @@ export default class ArticleImageSearchDialog extends PureComponent {
overflow: 'hidden',
border: '1px solid',
borderColor: isThisProcessing ? 'primary.main' : 'divider',
bgcolor: '#fff',
backgroundImage:
'linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%)',
bgcolor: WHITE,
backgroundImage: CHECKERBOARD,
backgroundSize: '16px 16px',
backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
cursor: processingId ? 'default' : 'pointer',
@@ -567,8 +574,8 @@ export default class ArticleImageSearchDialog extends PureComponent {
height: 16,
fontSize: '0.62rem',
fontWeight: 600,
bgcolor: 'rgba(0, 0, 0, 0.65)',
color: '#fff',
bgcolor: SCRIM_LABEL,
color: WHITE,
pointerEvents: 'none',
px: 0.25,
}}
@@ -586,9 +593,9 @@ export default class ArticleImageSearchDialog extends PureComponent {
right: 0,
py: 0.6,
px: 0.8,
bgcolor: 'rgba(25, 118, 210, 0.92)',
bgcolor: INFO_RIBBON,
backdropFilter: 'blur(3px)',
color: '#fff',
color: WHITE,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -615,7 +622,7 @@ export default class ArticleImageSearchDialog extends PureComponent {
sx={{
position: 'absolute',
inset: 0,
bgcolor: 'rgba(255, 255, 255, 0.92)',
bgcolor: VEIL_OVERLAY,
backdropFilter: 'blur(3px)',
display: 'flex',
flexDirection: 'column',
@@ -662,10 +669,10 @@ export default class ArticleImageSearchDialog extends PureComponent {
onClick={(e) => this.handleCopyUrl(img.url, e)}
sx={{
p: 0.4,
bgcolor: 'rgba(255, 255, 255, 0.85)',
bgcolor: VEIL_CARD,
backdropFilter: 'blur(2px)',
boxShadow: 1,
'&:hover': { bgcolor: '#fff' },
'&:hover': { bgcolor: WHITE },
}}
>
<ContentCopyIcon sx={{ fontSize: 13, color: 'text.secondary' }} />
@@ -677,10 +684,10 @@ export default class ArticleImageSearchDialog extends PureComponent {
onClick={(e) => this.handleOpenUrl(img.url, e)}
sx={{
p: 0.4,
bgcolor: 'rgba(255, 255, 255, 0.85)',
bgcolor: VEIL_CARD,
backdropFilter: 'blur(2px)',
boxShadow: 1,
'&:hover': { bgcolor: '#fff' },
'&:hover': { bgcolor: WHITE },
}}
>
<OpenInNewIcon sx={{ fontSize: 13, color: 'text.secondary' }} />

View File

@@ -2,6 +2,9 @@ import { Component } from 'react';
import Box from '@mui/material/Box';
import { thumb } from '../../api';
import ThumbHashImage from './ThumbHashImage';
import {
SCRIM_SOFT,
} from '../../colors';
class ArticlePhoto extends Component {
constructor(props) {
@@ -53,7 +56,7 @@ class ArticlePhoto extends Component {
justifyContent: 'center',
borderRadius: fill ? 0 : 2,
overflow: 'hidden',
bgcolor: 'rgba(0, 0, 0, 0.35)',
bgcolor: SCRIM_SOFT,
...sx,
}}
>

View File

@@ -27,22 +27,30 @@ import {
} from '../../utils/articleDetail';
import { notifyError } from '../../utils/notify';
import { readClipboardImage } from '../../utils/pasteImage';
import {
SCRIM_BUTTON,
SCRIM_LABEL,
SCRIM_SOFT,
SCRIM_STRONG,
VEIL_MUTED,
WHITE,
} from '../../colors';
import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto';
import ArticlePhoto from './ArticlePhoto';
import ArticleImageSearchDialog from './ArticleImageSearchDialog';
const overlayChipSx = {
height: 22,
bgcolor: 'rgba(0, 0, 0, 0.65)',
color: '#fff',
bgcolor: SCRIM_LABEL,
color: WHITE,
'& .MuiChip-label': { px: 0.75, fontSize: 11 },
};
const overlayIconSx = {
color: '#fff',
bgcolor: 'rgba(0, 0, 0, 0.55)',
'&:hover': { bgcolor: 'rgba(0, 0, 0, 0.75)' },
'&.Mui-disabled': { color: 'rgba(255,255,255,0.35)', bgcolor: 'rgba(0,0,0,0.35)' },
color: WHITE,
bgcolor: SCRIM_BUTTON,
'&:hover': { bgcolor: SCRIM_STRONG },
'&.Mui-disabled': { color: VEIL_MUTED, bgcolor: SCRIM_SOFT },
};
class ArticlePhotosDialog extends Component {

View File

@@ -32,6 +32,15 @@ import { formatStock } from './searchFormat';
import { notifyError } from '../../utils/notify';
import artikelAutomat from '../../automats/artikelAutomat';
import warehouseAutomat from '../../automats/warehouseAutomat';
import {
BORDER,
GREEN_BG,
GREEN_PANEL,
RED_BG,
RED_PANEL,
SURFACE_BASE,
SURFACE_RAISED,
} from '../../colors';
const REASON_PRESETS = [
'Inventurdifferenz',
@@ -344,8 +353,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
sx={{
p: 1.5,
borderRadius: 1,
backgroundColor: '#f8fafc',
border: '1px solid #e2e8f0',
backgroundColor: SURFACE_BASE,
border: `1px solid ${BORDER}`,
}}
>
<Typography variant="caption" color="text.secondary" display="block">
@@ -361,8 +370,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
sx={{
p: 1.5,
borderRadius: 1,
backgroundColor: '#f8fafc',
border: '1px solid #e2e8f0',
backgroundColor: SURFACE_BASE,
border: `1px solid ${BORDER}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@@ -437,8 +446,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
p: 1,
px: 2,
borderRadius: 1,
backgroundColor: '#f1f5f9',
border: '1px solid #e2e8f0',
backgroundColor: SURFACE_RAISED,
border: `1px solid ${BORDER}`,
minWidth: 140,
}}
>
@@ -529,8 +538,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
sx={{
p: 1.5,
borderRadius: 1,
backgroundColor: amount > 0 ? '#ecfdf5' : '#fef2f2',
border: `1px solid ${amount > 0 ? '#a7f3d0' : '#fecaca'}`,
backgroundColor: amount > 0 ? GREEN_BG : RED_BG,
border: `1px solid ${amount > 0 ? GREEN_PANEL : RED_PANEL}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',

View File

@@ -31,40 +31,50 @@ import ArticleSeoTab from './tabs/ArticleSeoTab';
import ArticleSettingsTab from './tabs/ArticleSettingsTab';
import ArticleSupplierTab from './tabs/ArticleSupplierTab';
import HelpBadge from '../HelpBadge';
import {
BLUE_PANEL,
BORDER,
GREEN_PANEL,
ORANGE_PANEL,
PETROL_PANEL,
RED_PANEL,
SHADOW_TAB,
YELLOW_PANEL,
} from '../../colors';
const ALWAYS_TABS = [
{
id: ARTICLE_TAB.history,
label: 'History',
color: '#fed7aa',
color: ORANGE_PANEL,
Icon: HistoryIcon,
help: 'Historie aller Änderungen, Buchungen und Ereignisse zu diesem Artikel',
},
{
id: ARTICLE_TAB.supplier,
label: 'Supplier',
color: '#bbf7d0',
color: GREEN_PANEL,
Icon: LocalShippingIcon,
help: 'Zugeordnete Lieferanten, Einkaufspreise und Lieferanten-Artikelnummern',
},
{
id: ARTICLE_TAB.seo,
label: 'SEO & Beschreibung',
color: '#99f6e4',
color: PETROL_PANEL,
Icon: ManageSearchIcon,
help: 'Shop-Texte, Beschreibungen und Suchmaschinen-Metadaten',
},
{
id: ARTICLE_TAB.label,
label: 'Label',
color: '#fde68a',
color: YELLOW_PANEL,
Icon: LabelIcon,
help: 'Barcode- und Preisetiketten für diesen Artikel gestalten und drucken',
},
{
id: ARTICLE_TAB.attributes,
label: 'Attribute und Merkmale',
color: '#bfdbfe',
color: BLUE_PANEL,
Icon: CategoryIcon,
help: 'Zusatzattribute, technische Merkmale und Filtereigenschaften',
},
@@ -73,7 +83,7 @@ const ALWAYS_TABS = [
const SETTINGS_TAB = {
id: ARTICLE_TAB.settings,
label: 'Einstellungen',
color: '#fca5a5',
color: RED_PANEL,
Icon: SettingsIcon,
help: 'Artikeleinstellungen, Einheiten, Steuersatz und Löschen',
};
@@ -220,8 +230,7 @@ class ArticleTileTab extends PureComponent {
display: 'flex',
flexDirection: 'column',
minWidth: 0,
filter:
'drop-shadow(0px 2px 4px rgba(0,0,0,0.2)) drop-shadow(0px 4px 5px rgba(0,0,0,0.14)) drop-shadow(0px 1px 10px rgba(0,0,0,0.12))',
filter: SHADOW_TAB,
}}
>
<Box
@@ -313,7 +322,7 @@ class ArticleTileTab extends PureComponent {
},
'&.Mui-disabled': {
opacity: 0.4,
bgcolor: '#e2e8f0',
bgcolor: BORDER,
cursor: 'not-allowed',
pointerEvents: 'auto',
},

View File

@@ -16,6 +16,9 @@ import ArticleViewBrand from './boxes/ArticleViewBrand';
import ArticleViewEANs from './boxes/ArticleViewEANs';
import ArticleViewPrice from './boxes/ArticleViewPrice';
import ArticleViewLists from './boxes/ArticleViewLists';
import {
YELLOW_PANEL,
} from '../../colors';
/** Stable prop so PureComponent photo tile does not see a new sx each render. */
const PHOTO_TILE_SX = { minHeight: 0 };
@@ -93,7 +96,7 @@ class ArticleView extends PureComponent {
<ArticlePhotoTile
wide={1}
color="#fde68a"
color={YELLOW_PANEL}
sx={PHOTO_TILE_SX}
article={article}
/>

View File

@@ -10,6 +10,9 @@ import { requireObject } from '../../../utils/apiShape';
import { notifyError } from '../../../utils/notify';
import InputAdornment from '@mui/material/InputAdornment';
import { BrandLabel, BrandThumb } from '../../brand';
import {
PURPLE_BG,
} from '../../../colors';
class PanelValue extends Component {
render() {
@@ -174,7 +177,7 @@ export default class ArticleViewBrand extends PureComponent {
title="Marke"
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e9d5ff"
color={PURPLE_BG}
contentAlignItems="flex-start"
value={value}
onSave={this.handleSave}

View File

@@ -9,6 +9,10 @@ import { requireObject } from '../../../utils/apiShape';
import { articleEans } from '../../../utils/articleDetail';
import { notifyError } from '../../../utils/notify';
import JsBarcode from 'jsbarcode';
import {
BORDER,
INK,
} from '../../../colors';
class PanelValue extends Component {
render() {
const { label, children } = this.props;
@@ -61,7 +65,7 @@ class SkuBarcode extends PureComponent {
displayValue: false,
margin: 0,
background: 'transparent',
lineColor: '#1e293b',
lineColor: INK,
});
} catch (e) {
console.warn('Barcode render error:', e);
@@ -151,7 +155,7 @@ export default class ArticleViewEANs extends PureComponent {
title={articleData.sku}
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e2e8f0"
color={BORDER}
contentAlignItems="flex-start"
value={value}
onSave={this.handleSave}

View File

@@ -36,6 +36,10 @@ import attributeAutomat, { selectAttributes } from '../../../automats/attributeA
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
import { notifyError } from '../../../utils/notify';
import articleIndexAutomat from '../../../automats/articleIndexAutomat';
import {
MAGENTA_PANEL,
SHADOW_CARD,
} from '../../../colors';
export default class ArticleViewLists extends PureComponent {
constructor(props) {
@@ -1157,7 +1161,7 @@ export default class ArticleViewLists extends PureComponent {
transition: 'all 0.15s ease-in-out',
'&:hover': {
borderColor: 'primary.main',
boxShadow: '0 1px 4px rgba(0,0,0,0.06)',
boxShadow: SHADOW_CARD,
},
}}
>
@@ -1245,7 +1249,7 @@ export default class ArticleViewLists extends PureComponent {
transition: 'all 0.15s ease-in-out',
'&:hover': {
borderColor: 'primary.main',
boxShadow: '0 1px 4px rgba(0,0,0,0.06)',
boxShadow: SHADOW_CARD,
},
}}
>
@@ -1465,7 +1469,7 @@ export default class ArticleViewLists extends PureComponent {
const parentArticle = parentSku ? getArticle(parentSku) : null;
return (
<PanelTile wide={1} minHeight={200} color="#fbcfe8">
<PanelTile wide={1} minHeight={200} color={MAGENTA_PANEL}>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', gap: 1 }}>
{/* ── Mode 1: Parent Article ──────────────────────────────────────── */}
{isParent ? (
@@ -1640,7 +1644,7 @@ export default class ArticleViewLists extends PureComponent {
transition: 'all 0.15s ease-in-out',
'&:hover': {
borderColor: 'primary.main',
boxShadow: '0 1px 4px rgba(0,0,0,0.06)',
boxShadow: SHADOW_CARD,
},
}}
>

View File

@@ -8,6 +8,9 @@ import { Box, Chip, IconButton } from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import CategoryPickerDialog from '../CategoryPickerDialog';
import { notifyError } from '../../../utils/notify';
import {
WHITE,
} from '../../../colors';
const articleNameTextSx = {
fontWeight: 700,
@@ -327,7 +330,7 @@ export default class ArticleViewName extends PureComponent {
wide={3}
minHeight={0}
sx={headerTileSx}
color="#ffffff"
color={WHITE}
value={value}
onSave={this.handleSave}
onError={notifyError}

View File

@@ -31,6 +31,10 @@ import { notifyError } from '../../../utils/notify';
import { PriceTaxRow } from './PriceTaxRow';
import TierPricePills from './TierPricePills';
import { PriceUnitRow, PriceUnitCorner } from './PriceUnitRow';
import {
BLUE_PANEL,
BORDER,
} from '../../../colors';
const SECOND_ROW_MIN_HEIGHT = 220;
@@ -481,7 +485,7 @@ export default class ArticleViewPrice extends PureComponent {
<PanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e2e8f0"
color={BORDER}
contentAlignItems="flex-start"
>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }}>
@@ -533,7 +537,7 @@ export default class ArticleViewPrice extends PureComponent {
<EditablePanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#bfdbfe"
color={BLUE_PANEL}
contentAlignItems="flex-start"
value={priceValue}
onSave={this.handleSave}

View File

@@ -7,6 +7,14 @@ import Tooltip from '@mui/material/Tooltip';
import { PanelTile } from 'dashgui/panels';
import { formatStock } from '../searchFormat';
import ArticleStockAdjustDialog from '../ArticleStockAdjustDialog';
import {
BORDER,
GREEN_PANEL,
PETROL_PANEL,
SORT_GHOST_BG,
TINT_ROW_HOVER,
VEIL_PANEL,
} from '../../../colors';
const SECOND_ROW_MIN_HEIGHT = 220;
@@ -66,7 +74,7 @@ export default class ArticleViewStock extends PureComponent {
<PanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color={isParent ? '#e2e8f0' : isStueckliste ? '#cffafe' : '#bbf7d0'}
color={isParent ? BORDER : isStueckliste ? PETROL_PANEL : GREEN_PANEL}
action={
isParent || isStueckliste
? null
@@ -171,7 +179,7 @@ export default class ArticleViewStock extends PureComponent {
mx: -0.5,
transition: 'background-color 0.15s ease',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
backgroundColor: TINT_ROW_HOVER,
},
}}
>
@@ -209,7 +217,7 @@ export default class ArticleViewStock extends PureComponent {
mx: -0.5,
transition: 'background-color 0.15s ease',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
backgroundColor: TINT_ROW_HOVER,
},
}}
>
@@ -246,7 +254,7 @@ export default class ArticleViewStock extends PureComponent {
pr: 0.5,
'&::-webkit-scrollbar': { width: 4 },
'&::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(0, 0, 0, 0.15)',
backgroundColor: SORT_GHOST_BG,
borderRadius: 2,
},
}}
@@ -262,7 +270,7 @@ export default class ArticleViewStock extends PureComponent {
py: 0.35,
px: 0.75,
borderRadius: 1,
backgroundColor: 'rgba(255, 255, 255, 0.55)',
backgroundColor: VEIL_PANEL,
}}
>
<Box

View File

@@ -3,6 +3,11 @@ import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import IconButton from '@mui/material/IconButton';
import LayersOutlinedIcon from '@mui/icons-material/LayersOutlined';
import {
TINT_CHIP,
TINT_PILL_ACTIVE,
TINT_STRONG,
} from '../../../colors';
const taxPillSx = {
height: 28,
@@ -27,12 +32,12 @@ export class TaxRatePills extends Component {
sx={{
...taxPillSx,
visibility: active || !readOnly ? 'visible' : 'hidden',
bgcolor: active ? 'rgba(0, 0, 0, 0.14)' : 'rgba(0, 0, 0, 0.06)',
bgcolor: active ? TINT_PILL_ACTIVE : TINT_CHIP,
...(readOnly
? {}
: {
cursor: 'pointer',
'&:hover': { bgcolor: 'rgba(0, 0, 0, 0.12)' },
'&:hover': { bgcolor: TINT_STRONG },
}),
}}
/>

View File

@@ -10,6 +10,9 @@ import StraightenOutlinedIcon from '@mui/icons-material/StraightenOutlined';
import Switch from '@mui/material/Switch';
import TextInput from '../../TextInput';
import { findUnitBySi, unitLabel } from '../../../utils/units';
import {
TINT_PILL,
} from '../../../colors';
const priceUnitCornerSx = {
position: 'absolute',
@@ -49,7 +52,7 @@ export class PriceUnitRow extends Component {
height: 28,
fontSize: 13,
fontWeight: 600,
bgcolor: 'rgba(0, 0, 0, 0.08)',
bgcolor: TINT_PILL,
}}
/>
) : null}

View File

@@ -2,6 +2,9 @@ import { Component } from 'react';
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import { formatPrice, parsePriceInput } from '../../../utils/price';
import {
TINT_PILL,
} from '../../../colors';
const tierPillSx = {
height: 22,
@@ -24,7 +27,7 @@ export default class TierPricePills extends Component {
label={`ab ${tier.minmultiplier} · ${formatPrice(gross)}`}
sx={{
...tierPillSx,
bgcolor: 'rgba(0, 0, 0, 0.08)',
bgcolor: TINT_PILL,
}}
/>
);

View File

@@ -15,6 +15,21 @@ function formatDate(iso) {
return new Date(iso).toLocaleString('de-DE');
}
import {
SCRIM_SOFT,
} from '../../../colors';
const HISTORY_DATE_CELL_BORDER = `1px solid ${SCRIM_SOFT}`;
const dateDividerSx = {
borderBottom: HISTORY_DATE_CELL_BORDER,
};
const dateCellSx = {
borderBottom: HISTORY_DATE_CELL_BORDER,
verticalAlign: 'middle',
};
export default class ArticleHistoryTab extends PureComponent {
constructor(props) {
super(props);
@@ -94,7 +109,7 @@ export default class ArticleHistoryTab extends PureComponent {
<TableRow>
<TableCell
sx={{
borderBottom: '1px solid rgba(0, 0, 0, 0.35)',
...dateDividerSx,
width: '1%',
whiteSpace: 'nowrap',
verticalAlign: 'middle',
@@ -103,10 +118,7 @@ export default class ArticleHistoryTab extends PureComponent {
Datum
</TableCell>
<TableCell
sx={{
borderBottom: '1px solid rgba(0, 0, 0, 0.35)',
verticalAlign: 'middle',
}}
sx={dateCellSx}
>
{showPagination ? (
<ResultsPagination

View File

@@ -28,6 +28,10 @@ import { articleSupplierAutomat } from '../../../automats/articleDetailAutomat';
import sseAutomat from '../../../automats/sseAutomat';
import companyAutomat from '../../../automats/companyAutomat';
import ArticleSupplierDialog from '../ArticleSupplierDialog';
import {
SORT_GHOST_BG,
TINT_FAINT,
} from '../../../colors';
export default class ArticleSupplierTab extends PureComponent {
constructor(props) {
@@ -218,8 +222,8 @@ export default class ArticleSupplierTab extends PureComponent {
py: 6,
px: 2,
borderRadius: 2,
border: '1px dashed rgba(0, 0, 0, 0.15)',
bgcolor: 'rgba(0, 0, 0, 0.01)',
border: `1px dashed ${SORT_GHOST_BG}`,
bgcolor: TINT_FAINT,
textAlign: 'center',
}}
>
@@ -286,8 +290,8 @@ export default class ArticleSupplierTab extends PureComponent {
py: 6,
px: 2,
borderRadius: 2,
border: '1px dashed rgba(0, 0, 0, 0.15)',
bgcolor: 'rgba(0, 0, 0, 0.01)',
border: `1px dashed ${SORT_GHOST_BG}`,
bgcolor: TINT_FAINT,
textAlign: 'center',
}}
>

View File

@@ -27,6 +27,14 @@ import { enqueueSnackbar } from 'notistack';
import { serper } from '../../api';
import brandAutomat from '../../automats/brandAutomat';
import { downloadAndPrepareLogoAvif } from '../../utils/cropLogo';
import {
CHECKERBOARD,
INFO_RIBBON,
SCRIM_LABEL,
VEIL_CARD_STRONG,
VEIL_OVERLAY,
WHITE,
} from '../../colors';
/**
* Dialog that searches Google Images via Serper.dev SSE stream for a brand logo.
@@ -420,9 +428,8 @@ export default class BrandImageSearchDialog extends PureComponent {
overflow: 'hidden',
border: '1px solid',
borderColor: isThisProcessing ? 'primary.main' : 'divider',
bgcolor: '#fff',
backgroundImage:
'linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%)',
bgcolor: WHITE,
backgroundImage: CHECKERBOARD,
backgroundSize: '16px 16px',
backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
cursor: processingId ? 'default' : 'pointer',
@@ -484,8 +491,8 @@ export default class BrandImageSearchDialog extends PureComponent {
height: 16,
fontSize: '0.62rem',
fontWeight: 600,
bgcolor: 'rgba(0, 0, 0, 0.65)',
color: '#fff',
bgcolor: SCRIM_LABEL,
color: WHITE,
pointerEvents: 'none',
px: 0.25,
}}
@@ -503,9 +510,9 @@ export default class BrandImageSearchDialog extends PureComponent {
right: 0,
py: 0.6,
px: 0.8,
bgcolor: 'rgba(25, 118, 210, 0.92)',
bgcolor: INFO_RIBBON,
backdropFilter: 'blur(3px)',
color: '#fff',
color: WHITE,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -532,7 +539,7 @@ export default class BrandImageSearchDialog extends PureComponent {
sx={{
position: 'absolute',
inset: 0,
bgcolor: 'rgba(255, 255, 255, 0.92)',
bgcolor: VEIL_OVERLAY,
backdropFilter: 'blur(3px)',
display: 'flex',
flexDirection: 'column',
@@ -578,9 +585,9 @@ export default class BrandImageSearchDialog extends PureComponent {
onClick={(e) => this.handleCopyUrl(img.url, e)}
sx={{
p: 0.5,
bgcolor: 'rgba(255, 255, 255, 0.9)',
bgcolor: VEIL_CARD_STRONG,
boxShadow: 1,
'&:hover': { bgcolor: '#fff' },
'&:hover': { bgcolor: WHITE },
}}
>
<ContentCopyIcon sx={{ fontSize: 14, color: 'text.primary' }} />
@@ -592,9 +599,9 @@ export default class BrandImageSearchDialog extends PureComponent {
onClick={(e) => this.handleOpenUrl(img.url, e)}
sx={{
p: 0.5,
bgcolor: 'rgba(255, 255, 255, 0.9)',
bgcolor: VEIL_CARD_STRONG,
boxShadow: 1,
'&:hover': { bgcolor: '#fff' },
'&:hover': { bgcolor: WHITE },
}}
>
<OpenInNewIcon sx={{ fontSize: 14, color: 'text.primary' }} />

View File

@@ -1,6 +1,10 @@
import Box from '@mui/material/Box';
import ThumbHashImage, { getThumbHashDimensions } from '../article/ThumbHashImage';
import brandAutomat from '../../automats/brandAutomat';
import {
SHADOW_THUMB_RING,
TINT_ROW_HOVER,
} from '../../colors';
/**
* Extracts thumbHash from various brand representations:
@@ -97,8 +101,8 @@ export default function BrandThumb({
overflow: 'hidden',
flexShrink: 0,
verticalAlign: 'middle',
bgcolor: 'rgba(0, 0, 0, 0.05)',
boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.12)',
bgcolor: TINT_ROW_HOVER,
boxShadow: SHADOW_THUMB_RING,
...sx,
}}
>

View File

@@ -2,9 +2,14 @@ import React, { useState } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import TextInput from '../TextInput';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import SendRoundedIcon from '@mui/icons-material/SendRounded';
import {
ASK_PROMPT_BG_PAIR,
ASK_PROMPT_BORDER_PAIR,
byMode,
} from '../../colors';
export default function AskUserPrompt({ askData, onAnswer, disabled = false }) {
const { toolCallId, question, options = [] } = askData;
@@ -30,8 +35,8 @@ export default function AskUserPrompt({ askData, onAnswer, disabled = false }) {
my: 1,
p: 2,
borderRadius: 3,
bgcolor: theme.palette.mode === 'dark' ? 'rgba(59, 91, 219, 0.12)' : '#eef2ff',
border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(59, 91, 219, 0.3)' : '#c7d2fe'}`,
bgcolor: byMode(theme, ASK_PROMPT_BG_PAIR),
border: `1px solid ${byMode(theme, ASK_PROMPT_BORDER_PAIR)}`,
display: 'flex',
flexDirection: 'column',
gap: 1.5,
@@ -97,28 +102,13 @@ export default function AskUserPrompt({ askData, onAnswer, disabled = false }) {
onSubmit={handleCustomSubmit}
sx={{ display: 'flex', gap: 1, mt: 0.5, alignItems: 'center' }}
>
<TextField
size="small"
<TextInput
fullWidth
autoFocus
placeholder="Antwort eingeben…"
value={customAnswer}
onChange={(e) => setCustomAnswer(e.target.value)}
disabled={disabled}
slotProps={{
htmlInput: {
spellCheck: false,
autoCorrect: 'off',
autoCapitalize: 'off',
},
}}
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2,
bgcolor: 'background.paper',
fontSize: '0.82rem',
},
}}
/>
<Button
type="submit"

View File

@@ -4,7 +4,7 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import TextInput from '../TextInput';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import FormControl from '@mui/material/FormControl';
@@ -20,6 +20,10 @@ import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
import userSettingsAutomat from '../../automats/userSettingsAutomat';
import ChatFlowList from './ChatFlowList';
import AskUserPrompt from './AskUserPrompt';
import {
BLUE_GLOW,
SHADOW_DRAWER,
} from '../../colors';
const DEFAULT_DRAWER_WIDTH = 460;
const MIN_DRAWER_WIDTH = 320;
@@ -489,11 +493,10 @@ export default class ChatDrawer extends PureComponent {
)}
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1 }}>
<TextField
<TextInput
inputRef={this.inputRef}
multiline
maxRows={4}
size="small"
fullWidth
value={inputText}
onChange={(e) => this.setState({ inputText: e.target.value })}
@@ -502,22 +505,8 @@ export default class ChatDrawer extends PureComponent {
placeholder={
pendingAsk
? 'Bitte Frage oben beantworten…'
: 'Nachricht eingeben… (Enter)'
: 'Nachricht eingeben…'
}
slotProps={{
htmlInput: {
spellCheck: false,
autoCorrect: 'off',
autoCapitalize: 'off',
},
}}
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2.5,
bgcolor: 'background.default',
fontSize: '0.875rem',
},
}}
/>
<IconButton
@@ -576,7 +565,7 @@ export default class ChatDrawer extends PureComponent {
maxWidth: '100vw',
display: 'flex',
flexDirection: 'column',
boxShadow: '-8px 0 32px rgba(0, 0, 0, 0.12)',
boxShadow: SHADOW_DRAWER,
position: 'fixed',
overflow: 'visible',
transition: isResizing ? 'none !important' : undefined,
@@ -592,7 +581,7 @@ export default class ChatDrawer extends PureComponent {
maxWidth: '100vw',
display: 'flex',
flexDirection: 'column',
boxShadow: '-8px 0 32px rgba(0, 0, 0, 0.12)',
boxShadow: SHADOW_DRAWER,
position: 'fixed',
overflow: 'visible',
transition: isResizing ? 'none !important' : undefined,
@@ -625,7 +614,7 @@ export default class ChatDrawer extends PureComponent {
height: 56,
width: 4,
opacity: 1,
boxShadow: '0 0 10px rgba(59, 91, 219, 0.6)',
boxShadow: `0 0 10px ${BLUE_GLOW}`,
},
}}
>
@@ -638,7 +627,7 @@ export default class ChatDrawer extends PureComponent {
bgcolor: isResizing ? 'primary.main' : 'divider',
opacity: isResizing ? 1 : 0.6,
transition: 'background-color 0.15s, height 0.15s, width 0.15s, opacity 0.15s, box-shadow 0.15s',
boxShadow: isResizing ? '0 0 10px rgba(59, 91, 219, 0.6)' : 'none',
boxShadow: isResizing ? `0 0 10px ${BLUE_GLOW}` : 'none',
}}
/>
</Box>

View File

@@ -10,6 +10,15 @@ import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import chatAutomat, { selectPendingAskUser, selectHasPendingAsk } from '../../automats/chatAutomat';
import {
CHAT_BUSY,
CHAT_FAB_BUSY,
CHAT_FAB_PENDING, CHAT_FAB_SHADOW_BUSY,
CHAT_FAB_SHADOW_IDLE,
CHAT_FAB_SHADOW_PENDING, RED,
RED_GLOW,
WHITE,
} from '../../colors';
export default class ChatFab extends PureComponent {
constructor(props) {
@@ -86,7 +95,7 @@ export default class ChatFab extends PureComponent {
right: -8,
bottom: -8,
borderRadius: '50%',
bgcolor: '#f59e0b',
bgcolor: CHAT_BUSY,
opacity: 0.5,
animation: 'fabAskPulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'@keyframes fabAskPulse': {
@@ -129,7 +138,7 @@ export default class ChatFab extends PureComponent {
position: 'absolute',
top: -6,
left: -6,
color: '#f59e0b',
color: CHAT_BUSY,
pointerEvents: 'none',
}}
/>
@@ -146,14 +155,14 @@ export default class ChatFab extends PureComponent {
sx={{
'& .MuiBadge-badge': hasPendingAsk
? {
bgcolor: '#ef4444',
color: '#ffffff',
bgcolor: RED,
color: WHITE,
fontWeight: 900,
fontSize: '0.85rem',
minWidth: 22,
height: 22,
borderRadius: '50%',
boxShadow: '0 2px 10px rgba(239, 68, 68, 0.6)',
boxShadow: `0 2px 10px ${RED_GLOW}`,
animation: 'badgePop 1.5s ease infinite',
'@keyframes badgePop': {
'0%, 100%': { transform: 'scale(1)' },
@@ -171,10 +180,10 @@ export default class ChatFab extends PureComponent {
width: 54,
height: 54,
boxShadow: hasPendingAsk
? '0 8px 24px rgba(245, 158, 11, 0.45)'
? CHAT_FAB_SHADOW_PENDING
: isBusy
? '0 8px 24px rgba(245, 158, 11, 0.35)'
: '0 8px 24px rgba(59, 91, 219, 0.35)',
? CHAT_FAB_SHADOW_BUSY
: CHAT_FAB_SHADOW_IDLE,
transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s',
'&:hover': {
transform: 'scale(1.06)',
@@ -183,9 +192,9 @@ export default class ChatFab extends PureComponent {
transform: 'scale(0.96)',
},
background: hasPendingAsk
? 'linear-gradient(135deg, #f59e0b 0%, #ea580c 100%)'
? CHAT_FAB_PENDING
: isBusy
? 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)'
? CHAT_FAB_BUSY
: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 100%)`,
})}
>

View File

@@ -2,6 +2,13 @@ import React, { PureComponent, createRef } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import HealingMarkdown from './HealingMarkdown';
import {
SHADOW_BUBBLE_ASSISTANT,
SHADOW_BUBBLE_USER,
SURFACE_RAISED,
SURFACE_SUBTLE_PAIR,
byMode,
} from '../../colors';
function formatMessageTime(isoString) {
if (!isoString) return '';
@@ -109,7 +116,7 @@ function MessageRow({ msg, showTime = true }) {
<Box sx={{ display: 'flex', justifyContent: 'flex-start', py: 0.35, px: 2 }}>
<Box
sx={(theme) => ({
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)',
bgcolor: byMode(theme, SURFACE_SUBTLE_PAIR),
color: 'text.secondary',
px: 1.25,
py: 0.35,
@@ -193,7 +200,7 @@ function MessageRow({ msg, showTime = true }) {
<Box sx={{ display: 'flex', justifyContent: 'flex-start', py: 0.35, px: 2 }}>
<Box
sx={(theme) => ({
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)',
bgcolor: byMode(theme, SURFACE_SUBTLE_PAIR),
color: 'text.secondary',
px: 1.25,
py: 0.35,
@@ -272,13 +279,13 @@ function MessageRow({ msg, showTime = true }) {
? theme.palette.primary.main
: theme.palette.mode === 'dark'
? 'background.paper'
: '#f4f6f8',
: SURFACE_RAISED,
color: isUser
? theme.palette.primary.contrastText
: theme.palette.text.primary,
boxShadow: isUser
? '0 2px 8px rgba(59, 91, 219, 0.25)'
: '0 1px 4px rgba(0, 0, 0, 0.05)',
? SHADOW_BUBBLE_USER
: SHADOW_BUBBLE_ASSISTANT,
border: isUser ? 'none' : `1px solid ${theme.palette.divider}`,
wordBreak: 'break-word',
whiteSpace: isUser ? 'pre-wrap' : 'normal',

View File

@@ -1,5 +1,12 @@
import React, { memo, useState } from 'react';
import Box from '@mui/material/Box';
import {
CODE_BLOCK_PAIR,
CODE_INLINE_PAIR,
SURFACE_SUBTLE_PAIR,
TABLE_HEADER_PAIR,
byMode,
} from '../../colors';
/**
* Healing markdown renderer.
@@ -336,7 +343,7 @@ const rootSx = (theme) => ({
fontSize: '0.82em',
px: 0.5,
borderRadius: 0.75,
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
bbox: byMode(theme, CODE_INLINE_PAIR),
},
'& pre': {
m: 0,
@@ -344,7 +351,7 @@ const rootSx = (theme) => ({
p: 1.25,
borderRadius: 1.5,
overflowX: 'auto',
bgcolor: theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.35)' : 'rgba(0,0,0,0.06)',
bgcolor: byMode(theme, CODE_BLOCK_PAIR),
},
'& pre code': { p: 0, bgcolor: 'transparent', whiteSpace: 'pre' },
'& hr': { border: 0, borderTop: `1px solid ${theme.palette.divider}`, my: 1.5 },
@@ -357,7 +364,7 @@ const rootSx = (theme) => ({
objectFit: 'contain',
borderRadius: 1.5,
border: `1px solid ${theme.palette.divider}`,
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)',
bbox: byMode(theme, SURFACE_SUBTLE_PAIR),
},
'& .md-image-fallback': {
display: 'inline-block',
@@ -388,7 +395,7 @@ const rootSx = (theme) => ({
'&:last-child': { borderRight: 'none' },
},
'& th': {
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.04)',
bbox: byMode(theme, TABLE_HEADER_PAIR),
fontWeight: 600,
},
'& tr:last-child td': {

View File

@@ -4,7 +4,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import TextInput from '../TextInput';
import MenuItem from '@mui/material/MenuItem';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
@@ -12,6 +12,9 @@ import CircularProgress from '@mui/material/CircularProgress';
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
import { PRESET_MODELS } from '../../automats/chatAutomat';
import {
SHADOW_DIALOG,
} from '../../colors';
export default class NewConversationDialog extends PureComponent {
constructor(props) {
@@ -73,7 +76,7 @@ export default class NewConversationDialog extends PureComponent {
sx: {
borderRadius: 3,
p: 1,
boxShadow: '0 20px 40px rgba(0,0,0,0.2)',
boxShadow: SHADOW_DIALOG,
},
}}
>
@@ -103,23 +106,21 @@ export default class NewConversationDialog extends PureComponent {
Wähle das Sprachmodell für die neue Konversation aus.
</Typography>
<TextField
<TextInput
label="Provider"
value={provider}
onChange={(e) => this.setState({ provider: e.target.value })}
fullWidth
size="small"
disabled={loading}
helperText="Standard: openrouter"
/>
<TextField
<TextInput
select
label="Modell Vorlage"
value={isCustom ? '__custom__' : model}
onChange={this.handleModelChange}
fullWidth
size="small"
disabled={loading}
>
{PRESET_MODELS.map((item) => (
@@ -128,16 +129,15 @@ export default class NewConversationDialog extends PureComponent {
</MenuItem>
))}
<MenuItem value="__custom__">Anderes Modell eingeben…</MenuItem>
</TextField>
</TextInput>
{isCustom && (
<TextField
<TextInput
label="Modell-Bezeichnung"
placeholder="z. B. anthropic/claude-3.5-haiku"
value={customModel}
onChange={(e) => this.setState({ customModel: e.target.value })}
fullWidth
size="small"
autoFocus
disabled={loading}
/>

View File

@@ -23,6 +23,9 @@ import LinkIcon from '@mui/icons-material/Link';
import articleIndexAutomat from '../../automats/articleIndexAutomat';
import ArticlePhoto from '../article/ArticlePhoto';
import { BrandLabel } from '../brand';
import {
BORDER,
} from '../../colors';
export default class ArticleSearchDialog extends PureComponent {
constructor(props) {
@@ -250,7 +253,7 @@ export default class ArticleSearchDialog extends PureComponent {
height: 34,
borderRadius: 0.75,
overflow: 'hidden',
bgcolor: '#e2e8f0',
bgcolor: BORDER,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -10,6 +10,18 @@ import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined
import { tileBaseSx, TILE_SIZE } from 'dashgui/tiles';
import { generateTextThumbnail } from '../../utils/textThumbnail';
import { notifyErrorMessage } from '../../utils/notify';
import {
SCRIM_ACTION,
SCRIM_ATTACHMENT,
SCRIM_HEAVY,
SURFACE_BASE,
SURFACE_FAINT,
SURFACE_RAISED,
SURFACE_SUBTLE,
VEIL_DISABLED,
VEIL_TEXT,
WHITE,
} from '../../colors';
export class Attachment extends PureComponent {
state = {
@@ -120,7 +132,7 @@ export class Attachment extends PureComponent {
position: 'relative',
overflow: 'hidden',
cursor: disabled ? 'default' : 'pointer',
bgcolor: isText ? '#fcfcfc' : '#f8fafc',
bgcolor: isText ? SURFACE_FAINT : SURFACE_BASE,
border: '1px solid',
borderColor: 'divider',
...(disabled ? { opacity: 0.6, pointerEvents: 'none' } : {}),
@@ -135,7 +147,7 @@ export class Attachment extends PureComponent {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: isText ? '#fafafa' : '#f1f5f9',
bgcolor: isText ? SURFACE_SUBTLE : SURFACE_RAISED,
}}
>
{thumbnail ? (
@@ -156,7 +168,7 @@ export class Attachment extends PureComponent {
width: '100%',
height: '100%',
p: 1.25,
bgcolor: '#fafafa',
bgcolor: SURFACE_SUBTLE,
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
@@ -243,13 +255,13 @@ export class Attachment extends PureComponent {
disabled={disabled || hasArticles}
onClick={this.handleRescan}
sx={{
bgcolor: 'rgba(0, 0, 0, 0.6)',
color: hasArticles ? 'rgba(255, 255, 255, 0.3)' : '#fff',
bgcolor: SCRIM_ACTION,
color: hasArticles ? VEIL_DISABLED : WHITE,
p: 0.5,
backdropFilter: 'blur(2px)',
cursor: hasArticles ? 'not-allowed' : 'pointer',
'&:hover': {
bgcolor: hasArticles ? 'rgba(0, 0, 0, 0.6)' : 'rgba(0, 0, 0, 0.85)',
bgcolor: hasArticles ? SCRIM_ACTION : SCRIM_HEAVY,
},
}}
>
@@ -265,8 +277,8 @@ export class Attachment extends PureComponent {
disabled={disabled}
onClick={this.handleDelete}
sx={{
bgcolor: 'rgba(0, 0, 0, 0.6)',
color: '#fff',
bgcolor: SCRIM_ACTION,
color: WHITE,
p: 0.5,
backdropFilter: 'blur(2px)',
'&:hover': { bgcolor: 'error.main' },
@@ -287,7 +299,7 @@ export class Attachment extends PureComponent {
bottom: 0,
px: 1,
py: 0.5,
background: 'linear-gradient(transparent, rgba(0,0,0,0.75) 40%, rgba(0,0,0,0.85))',
background: SCRIM_ATTACHMENT,
textAlign: 'left',
pointerEvents: 'none',
}}
@@ -296,7 +308,7 @@ export class Attachment extends PureComponent {
variant="caption"
fontWeight={600}
noWrap
sx={{ color: '#fff', display: 'block', fontSize: '0.72rem', lineHeight: 1.2 }}
sx={{ color: WHITE, display: 'block', fontSize: '0.72rem', lineHeight: 1.2 }}
>
{name}
</Typography>
@@ -304,7 +316,7 @@ export class Attachment extends PureComponent {
<Typography
variant="caption"
noWrap
sx={{ color: 'rgba(255,255,255,0.8)', fontSize: '0.65rem' }}
sx={{ color: VEIL_TEXT, fontSize: '0.65rem' }}
>
{this.formatFileSize(size)}
</Typography>

View File

@@ -35,6 +35,10 @@ import {
bookDeliveryLines,
} from '../../utils/deliveryLedger';
import warehouseAutomat from '../../automats/warehouseAutomat';
import {
SURFACE_BASE,
SURFACE_RAISED,
} from '../../colors';
export default class BookDeliveryDialog extends PureComponent {
constructor(props) {
@@ -292,7 +296,7 @@ export default class BookDeliveryDialog extends PureComponent {
display: 'flex',
flexWrap: 'wrap',
gap: 3,
backgroundColor: '#f8fafc',
backgroundColor: SURFACE_BASE,
alignItems: 'center',
}}
>
@@ -391,7 +395,7 @@ export default class BookDeliveryDialog extends PureComponent {
return (
<TableContainer component={Paper} variant="outlined" sx={{ mb: 2 }}>
<Table size="small">
<TableHead sx={{ backgroundColor: '#f1f5f9' }}>
<TableHead sx={{ backgroundColor: SURFACE_RAISED }}>
<TableRow>
<TableCell sx={{ fontWeight: 600 }}>SKU</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>

View File

@@ -21,6 +21,9 @@ import { notifyError } from '../../utils/notify';
import BookDeliveryDialog from './BookDeliveryDialog';
import withNavigate from '../withNavigate';
import { formatDate, numberDisplay } from './manifestConfig';
import {
ORANGE_PANEL,
} from '../../colors';
function sortDeliveries(docs) {
if (!Array.isArray(docs)) return [];
@@ -385,7 +388,7 @@ export class DeliveryList extends PureComponent {
onDelete={this.handleDelete}
deleteDisabled={busyId != null}
emptyMessage="Noch keine Lieferungen."
color="#fed7aa"
color={ORANGE_PANEL}
getEditLabel={(row) => `„${row.number}“ bearbeiten`}
getDeleteLabel={(row) => `„${row.number}“ löschen`}
/>

View File

@@ -24,6 +24,9 @@ import { primaryPrice, priceBrutto } from '../../utils/articleDetail';
import artikelAutomat, { createArticleSelector, getArticle } from '../../automats/artikelAutomat';
import { artikelDetailPath } from '../article/articleRoute';
import { BrandLabel } from '../brand';
import {
BORDER,
} from '../../colors';
class ManifestFormLine extends PureComponent {
constructor(props) {
@@ -219,7 +222,6 @@ class ManifestFormLine extends PureComponent {
htmlInput: {
inputMode: type === 'number' ? 'decimal' : 'text',
style: { textAlign: align },
spellCheck: false,
autoComplete: 'off',
step: type === 'number' ? 'any' : undefined,
},
@@ -323,7 +325,7 @@ class ManifestFormLine extends PureComponent {
minWidth: 28,
borderRadius: 0.75,
overflow: 'hidden',
bgcolor: '#e2e8f0',
bgcolor: BORDER,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -15,6 +15,9 @@ import FileDropzone from '../FileDropzone';
import AttachmentList from './AttachmentList';
import { hasArticleLines, manifestConfig, partyDisplayName } from './manifestConfig';
import { notifyErrorMessage } from '../../utils/notify';
import {
SURFACE_RAISED,
} from '../../colors';
class ManifestHeader extends PureComponent {
constructor(props) {
@@ -136,7 +139,6 @@ class ManifestHeader extends PureComponent {
autoComplete="off"
slotProps={{
inputLabel: { shrink: true },
htmlInput: { spellCheck: false },
}}
sx={{
flex: { xs: '1 1 100%', sm: '1 1 150px' },
@@ -165,7 +167,6 @@ class ManifestHeader extends PureComponent {
...params.slotProps,
htmlInput: {
...params.slotProps?.htmlInput,
spellCheck: false,
autoComplete: 'off',
},
}}
@@ -247,7 +248,7 @@ class ManifestHeader extends PureComponent {
<PanelTile
wide={1}
minHeight={160}
color={actionsColor ?? '#f1f5f9'}
color={actionsColor ?? SURFACE_RAISED}
sx={{
gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 1' },
}}

View File

@@ -15,6 +15,11 @@ import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
import { enqueueSnackbar } from 'notistack';
import { PanelTile } from 'dashgui/panels';
import { delivery as deliveryApi } from '../../api';
import {
SURFACE_BASE,
SURFACE_WASH,
WHITE,
} from '../../colors';
/**
* Inline PDF viewer rendered as a panel tile, mirroring TextEditorPanel's
@@ -125,7 +130,7 @@ export class PdfViewerPanel extends PureComponent {
<PanelTile
wide={wide}
minHeight={320}
color="#f8fafc"
color={SURFACE_BASE}
sx={{ height, minHeight: 320 }}
>
<Box
@@ -205,7 +210,7 @@ export class PdfViewerPanel extends PureComponent {
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
bgcolor: '#ffffff',
bgcolor: WHITE,
overflow: 'hidden',
}}
>
@@ -217,7 +222,7 @@ export class PdfViewerPanel extends PureComponent {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'rgba(248, 250, 252, 0.7)',
bgcolor: SURFACE_WASH,
zIndex: 2,
}}
>

View File

@@ -18,6 +18,11 @@ import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
import { enqueueSnackbar } from 'notistack';
import { PanelTile } from 'dashgui/panels';
import { delivery as deliveryApi } from '../../api';
import {
SURFACE_BASE,
SURFACE_WASH,
WHITE,
} from '../../colors';
/**
* Inline text-file viewer/editor rendered as a full-width panel tile below the
@@ -169,7 +174,7 @@ export class TextEditorPanel extends PureComponent {
<PanelTile
wide={wide}
minHeight={320}
color="#f8fafc"
color={SURFACE_BASE}
sx={{ height, minHeight: 320 }}
>
<Box
@@ -264,7 +269,7 @@ export class TextEditorPanel extends PureComponent {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'rgba(248, 250, 252, 0.7)',
bgcolor: SURFACE_WASH,
zIndex: 2,
}}
>
@@ -292,7 +297,7 @@ export class TextEditorPanel extends PureComponent {
fontSize: '0.72rem',
lineHeight: 1.45,
tabSize: 4,
bgcolor: '#ffffff',
bgcolor: WHITE,
p: 1.5,
'& textarea': {
height: '100% !important',

View File

@@ -9,6 +9,12 @@ import customergroupAutomat from '../../automats/customergroupAutomat';
import deliveryAutomat from '../../automats/deliveryAutomat';
import quoteAutomat from '../../automats/quoteAutomat';
import orderAutomat from '../../automats/orderAutomat';
import {
GREEN_PANEL,
ORANGE_PANEL,
PURPLE_PANEL,
YELLOW_PANEL,
} from '../../colors';
/** Groups that are not sales customers (filter client-side). */
export const EXCLUDED_CUSTOMER_GROUP_NAMES = new Set(['Lieferanten', 'POS']);
@@ -559,9 +565,9 @@ export function manifestConfig(kind) {
getPathLabel: 'GET /api/delivery/:id',
postPathLabel: 'POST /api/delivery',
patchPathLabel: 'PATCH /api/delivery/:id',
listColor: '#fed7aa',
headerColor: '#fed7aa',
linesColor: '#bbf7d0',
listColor: ORANGE_PANEL,
headerColor: ORANGE_PANEL,
linesColor: GREEN_PANEL,
requirePartyBeforeLines: false,
listAutomat: deliveryAutomat,
partiesByIdFromList: (parties) =>
@@ -595,9 +601,9 @@ export function manifestConfig(kind) {
getPathLabel: 'GET /api/quote/:id',
postPathLabel: 'POST /api/quote',
patchPathLabel: 'PATCH /api/quote/:id',
listColor: '#fde68a',
headerColor: '#fde68a',
linesColor: '#bbf7d0',
listColor: YELLOW_PANEL,
headerColor: YELLOW_PANEL,
linesColor: GREEN_PANEL,
requirePartyBeforeLines: false,
listAutomat: quoteAutomat,
partiesByIdFromList: (parties) =>
@@ -628,9 +634,9 @@ export function manifestConfig(kind) {
getPathLabel: 'GET /api/order/:id',
postPathLabel: 'POST /api/order',
patchPathLabel: 'PATCH /api/order/:id',
listColor: '#c4b5fd',
headerColor: '#c4b5fd',
linesColor: '#bbf7d0',
listColor: PURPLE_PANEL,
headerColor: PURPLE_PANEL,
linesColor: GREEN_PANEL,
requirePartyBeforeLines: false,
listAutomat: orderAutomat,
partiesByIdFromList: (parties) =>

View File

@@ -14,6 +14,16 @@ import {
} from '../article/ThumbHashImage';
import { BrandLabel } from '../brand';
import { thumb } from '../../api';
import {
GREEN,
ICE,
INK_STRONG,
SCRIM_LABEL,
SCRIM_TILE,
TEXT_GLOW_DARK,
TEXT_GLOW_LIGHT,
WHITE,
} from '../../colors';
const MIN_EDGE_STRETCH = 10;
@@ -85,7 +95,7 @@ function drawContainedWithStretchedEdges(
const grow = maxBlur * 0.5;
lctx.globalCompositeOperation = 'destination-in';
lctx.filter = `blur(${maxBlur}px)`;
lctx.fillStyle = '#000';
lctx.fillStyle = INK_STRONG;
lctx.fillRect(
offsetX - grow,
offsetY - grow,
@@ -325,7 +335,7 @@ export class ArticleTile extends Tile {
px: 0.75,
py: 0.25,
borderRadius: 999,
bgcolor: 'rgba(0, 0, 0, 0.65)',
bgcolor: SCRIM_LABEL,
color: 'common.white',
fontSize: 11,
fontWeight: 600,
@@ -389,10 +399,10 @@ export class ArticleTile extends Tile {
bottom: 0,
px: 1,
py: 0.75,
background: 'linear-gradient(transparent, rgba(0,0,0,0.65))',
background: SCRIM_TILE,
textAlign: 'left',
color: '#fff',
textShadow: '0 1px 3px rgba(0, 0, 0, 0.95), 0 0 2px rgba(0, 0, 0, 0.85)',
color: WHITE,
textShadow: TEXT_GLOW_DARK,
}}
>
{article?.brand?.name ? (
@@ -405,11 +415,11 @@ export class ArticleTile extends Tile {
variant: 'caption',
noWrap: true,
sx: {
color: '#b3e5fc',
color: ICE,
opacity: 1,
fontWeight: 500,
lineHeight: 1.1,
textShadow: '0 1px 3px rgba(0, 0, 0, 0.95), 0 0 2px rgba(0, 0, 0, 0.85)',
textShadow: TEXT_GLOW_DARK,
},
}}
sx={{ mb: 0.25 }}
@@ -421,9 +431,9 @@ export class ArticleTile extends Tile {
fontWeight={600}
noWrap
sx={{
color: '#111',
color: INK_STRONG,
lineHeight: 1.2,
textShadow: '0 0 3px rgba(255, 255, 255, 1), 0 0 6px rgba(255, 255, 255, 0.9), 0 1px 2px rgba(255, 255, 255, 0.9)',
textShadow: TEXT_GLOW_LIGHT,
}}
>
{label}
@@ -434,8 +444,8 @@ export class ArticleTile extends Tile {
variant="caption"
noWrap
sx={{
color: '#ffffff',
textShadow: '0 1px 3px rgba(0, 0, 0, 0.95), 0 0 2px rgba(0, 0, 0, 0.85)',
color: WHITE,
textShadow: TEXT_GLOW_DARK,
}}
>
{sku}
@@ -447,8 +457,8 @@ export class ArticleTile extends Tile {
fontWeight={600}
component="div"
sx={{
color: '#b9f6ca',
textShadow: '0 1px 3px rgba(0, 0, 0, 0.95), 0 0 2px rgba(0, 0, 0, 0.85)',
color: GREEN,
textShadow: TEXT_GLOW_DARK,
}}
>
{formatPrice(article.net)}

View File

@@ -12,6 +12,12 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import { getThumbHashDataUrl, isImageLoaded, markImageLoaded } from '../article/ThumbHashImage';
import { thumb } from '../../api';
import { tileBaseSx } from 'dashgui/tiles';
import {
RED_TINT,
SCRIM_BADGE,
SCRIM_MEDIUM,
VEIL_HOVER,
} from '../../colors';
/**
* Top row (1/3): sort handle | settings toggle | move handle.
@@ -28,7 +34,7 @@ function TopBand({ onSettingsClick, settingsTab, terminalCount, onPlacementClick
top: '50%',
transform: 'translateY(-50%)',
color: 'common.white',
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.15)' },
'&:hover': { bgcolor: VEIL_HOVER },
};
return (
@@ -39,7 +45,7 @@ function TopBand({ onSettingsClick, settingsTab, terminalCount, onPlacementClick
left: 0,
right: 0,
height: '33.33%',
bgcolor: 'rgba(0, 0, 0, 0.45)',
bgcolor: SCRIM_MEDIUM,
}}
>
<IconButton
@@ -115,7 +121,7 @@ function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds,
...(isActionTab
? {
'&:hover': {
bgcolor: settingsTab === -3 ? 'rgba(211, 47, 47, 0.12)' : 'grey.400',
bgcolor: settingsTab === -3 ? RED_TINT : 'grey.400',
},
}
: {}),
@@ -332,7 +338,7 @@ export class CategoryTile extends Component {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'rgba(0, 0, 0, 0.5)',
bgcolor: SCRIM_BADGE,
px: 1,
}}
>