import { Component, PureComponent } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Switch from '@mui/material/Switch';
import DragHandleIcon from '@mui/icons-material/DragHandle';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import SettingsIcon from '@mui/icons-material/Settings';
import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
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.
* Icons are pinned to exact thirds (left / center / right) so they stay
* evenly spread regardless of icon size or button padding.
*/
function TopBand({ onSettingsClick, settingsTab, terminalCount, onPlacementClick }) {
const isTerminalMode = settingsTab >= 0 && settingsTab < terminalCount;
const isPhotoMode = settingsTab === -2;
const isDeleteMode = settingsTab === -3;
const handleSx = {
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
color: 'common.white',
'&:hover': { bgcolor: VEIL_HOVER },
};
return (
{isTerminalMode ? (
) : isPhotoMode ? (
) : isDeleteMode ? (
) : (
)}
{
e.stopPropagation();
onPlacementClick?.();
}}
sx={{
...handleSx,
right: 0,
cursor: 'pointer',
}}
>
);
}
/**
* Middle row (1/3): gray band — TextField or terminal toggle.
*/
function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds, onTerminalToggle, fullHash }) {
const isActionTab = settingsTab === -2 || settingsTab === -3;
const fillSx = {
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'grey.300',
px: 1,
...(isActionTab
? {
'&:hover': {
bgcolor: settingsTab === -3 ? RED_TINT : 'grey.400',
},
}
: {}),
};
// Show terminal-specific toggle when on a terminal tab
if (settingsTab >= 0 && settingsTab < terminals.length) {
const terminal = terminals[settingsTab];
const isEnabled = categoryPosterminalIds.includes(terminal.id);
return (
{terminal.name}
onTerminalToggle?.(terminal.id, e.target.checked)}
size="small"
/>
);
}
// Show photo upload UI when on photo tab
if (settingsTab === -2) {
return (
{fullHash ? 'Bild löschen' : 'Hochladen'}
);
}
// Show category deletion UI when on delete tab
if (settingsTab === -3) {
return (
Kategorie löschen
);
}
return (
{nameInput}
);
}
/**
* Renders a ThumbHash preview and the full thumbnail as backdrop for a category tile.
* ThumbHash is shown immediately as a placeholder; the thumb fades in once loaded.
*/
class CategoryBackdrop extends PureComponent {
constructor(props) {
super(props);
this.thumbUrl = getThumbHashDataUrl(props.thumbHash);
this.isCached = isImageLoaded(props.fullHash);
}
componentDidUpdate(prevProps) {
if (prevProps.thumbHash !== this.props.thumbHash) {
this.thumbUrl = getThumbHashDataUrl(this.props.thumbHash);
}
if (prevProps.fullHash !== this.props.fullHash) {
this.isCached = isImageLoaded(this.props.fullHash);
this.forceUpdate();
}
}
handleLoad = (e) => {
if (this.props.fullHash && e.target) {
markImageLoaded(this.props.fullHash, e.target);
}
};
render() {
const { fullHash } = this.props;
if (!this.thumbUrl && !fullHash) return null;
const isCached = this.isCached || isImageLoaded(fullHash);
return (
{!isCached && this.thumbUrl ? (
) : null}
{fullHash ? (
) : null}
);
}
}
/**
* Category tile.
*
* Normal mode (edit=false): clickable tile with label overlay; click navigates.
* Edit mode (edit=true): three rows of 1/3 each —
* Top row: sort-handle | settings toggle | drop-handle
* Middle row: gray band — TextField, POS toggle, or photo upload
* Bottom row: upper half = open-category click zone
* lower half = gray band with chevrons + delete
*/
export class CategoryTile extends Component {
state = { settingsTab: -1 }; // -1 = rename, -2 = photo, -3 = delete, 0..n-1 = terminal index
handleClick = () => {
this.props.onClick?.();
};
handleKeyDown = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.handleClick();
}
};
handleSettingsToggle = () => {
const { terminals } = this.props;
const terminalCount = terminals?.length || 0;
this.setState((prev) => {
let nextTab;
if (prev.settingsTab === -1) {
nextTab = terminalCount > 0 ? 0 : -2;
} else if (prev.settingsTab >= 0 && prev.settingsTab < terminalCount - 1) {
nextTab = prev.settingsTab + 1;
} else if (prev.settingsTab === terminalCount - 1 || prev.settingsTab >= 0) {
nextTab = -2;
} else if (prev.settingsTab === -2) {
nextTab = -3;
} else {
nextTab = -1;
}
return { settingsTab: nextTab };
});
};
handleMiddleBandClick = () => {
const { settingsTab } = this.state;
const { onPhotoUpload, onPhotoDelete, fullHash, onDelete } = this.props;
if (settingsTab === -2) {
if (fullHash) {
onPhotoDelete?.();
} else {
onPhotoUpload?.();
}
} else if (settingsTab === -3) {
onDelete?.();
}
};
renderLabel() {
const { label, nameInput } = this.props;
if (label == null) return null;
return (
{nameInput ?? (
{label}
)}
);
}
render() {
const {
sx,
edit,
label,
thumbHash,
fullHash,
onClick,
nameInput,
terminals,
categoryPosterminalIds,
onTerminalToggle,
onPhotoUpload,
onPhotoDelete,
onPlacementClick,
onOpen,
onDelete,
...rest
} = this.props;
const { settingsTab } = this.state;
// Shared by both modes: the thumbnail backdrop.
const backdrop = (
);
// Only the overlay differs between normal and edit mode.
const overlay = edit ? (
<>
{/* Lower third: clickable in edit mode to open/navigate into category */}
{
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
(onOpen || onClick)?.();
}
}}
sx={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '33.33%',
cursor: 'pointer',
'&:hover': {
bgcolor: 'action.hover',
},
}}
/>
>
) : (
this.renderLabel()
);
return (
{backdrop}
{overlay}
);
}
}