This commit is contained in:
seb
2026-08-10 03:13:53 +02:00
parent 7a1c6e2d94
commit 8bbd6fade1
8 changed files with 682 additions and 172 deletions

View File

@@ -1,4 +1,4 @@
import { Component } from 'react';
import { Component, PureComponent } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
@@ -74,7 +74,7 @@ function TopBand({ onSettingsClick, settingsTab, terminalCount }) {
/**
* Middle row (1/3): gray band — TextField or terminal toggle.
*/
function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds, onTerminalToggle, onPhotoUpload, onPhotoDelete, categoryFullHash }) {
function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds, onTerminalToggle, onPhotoUpload, onPhotoDelete, fullHash }) {
const fillSx = {
width: '100%',
height: '100%',
@@ -121,7 +121,7 @@ function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds,
noWrap
sx={{ maxWidth: 80, color: 'text.secondary' }}
>
{categoryFullHash ? 'Löschen' : 'Hochladen'}
{fullHash ? 'Löschen' : 'Hochladen'}
</Typography>
</Box>
</Box>
@@ -217,7 +217,7 @@ function BottomRow({ onOpen, editBand }) {
* 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 Component {
class CategoryBackdrop extends PureComponent {
state = { thumbLoaded: false };
componentDidUpdate(prevProps) {
@@ -265,13 +265,52 @@ class CategoryBackdrop extends Component {
}
/**
* Category tile (normal mode): no icon, click navigates to the category.
* 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, 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) => {
const next = prev.settingsTab + 1;
// Cycle: photo (-2) → rename (-1) → terminal 0 → ... → terminal n-1 → photo (-2)
const nextTab = next >= terminalCount ? -2 : next;
return { settingsTab: nextTab };
});
};
handleMiddleBandClick = () => {
const { settingsTab } = this.state;
const { onPhotoUpload, onPhotoDelete, fullHash } = this.props;
if (settingsTab === -2) {
if (fullHash) {
onPhotoDelete?.();
} else {
onPhotoUpload?.();
}
}
};
renderLabel() {
const { label, nameInput } = this.props;
if (label == null) return null;
@@ -304,123 +343,85 @@ export class CategoryTile extends Component {
);
}
render() {
const { sx, onClick: _onClick, label, thumbHash, fullHash, ...rest } = this.props;
return (
<Box
component="button"
type="button"
onClick={this.handleClick}
{...rest}
sx={{
...tileBaseSx,
position: 'relative',
overflow: 'hidden',
...sx,
}}
>
<CategoryBackdrop thumbHash={thumbHash} fullHash={fullHash} />
<Box sx={{ position: 'absolute', inset: 0, zIndex: 1 }}>
{this.renderLabel()}
</Box>
</Box>
);
}
}
/**
* Category tile for edit mode: 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 CategoryEditTile extends Component {
state = { settingsTab: -1 }; // -1 = rename, -2 = photo, 0..n-1 = terminal index
handleSettingsToggle = () => {
const { terminals } = this.props;
const terminalCount = terminals?.length || 0;
this.setState((prev) => {
const next = prev.settingsTab + 1;
// Cycle: photo (-2) → rename (-1) → terminal 0 → ... → terminal n-1 → photo (-2)
const nextTab = next >= terminalCount ? -2 : next;
return { settingsTab: nextTab };
});
};
handleMiddleBandClick = () => {
const { settingsTab } = this.state;
const { onPhotoUpload, onPhotoDelete, categoryFullHash } = this.props;
if (settingsTab === -2) {
if (categoryFullHash) {
onPhotoDelete?.();
} else {
onPhotoUpload?.();
}
}
};
render() {
const {
sx,
edit,
label,
thumbHash,
fullHash,
onClick,
nameInput,
editBand,
onOpen,
terminals,
categoryPosterminalIds,
onTerminalToggle,
hasTerminals: _hasTerminals,
categoryFullHash,
thumbHash,
onPhotoUpload: _onPhotoUpload,
onPhotoDelete: _onPhotoDelete,
sx,
onPhotoUpload,
onPhotoDelete,
...rest
} = this.props;
const { settingsTab } = this.state;
// Shared by both modes: the thumbnail backdrop.
const backdrop = (
<CategoryBackdrop thumbHash={thumbHash} fullHash={fullHash} />
);
// Only the overlay differs between normal and edit mode.
const overlay = edit ? (
<>
<TopBand
onSettingsClick={this.handleSettingsToggle}
settingsTab={settingsTab}
terminalCount={terminals?.length || 0}
/>
<Box
onClick={this.handleMiddleBandClick}
sx={{
position: 'absolute',
top: '33.33%',
bottom: '33.33%',
left: 0,
right: 0,
cursor: settingsTab === -2 ? 'pointer' : 'default',
}}
>
<MiddleBand
settingsTab={settingsTab}
nameInput={nameInput}
terminals={terminals}
categoryPosterminalIds={categoryPosterminalIds}
onTerminalToggle={onTerminalToggle}
fullHash={fullHash}
/>
</Box>
<BottomRow onOpen={onOpen} editBand={editBand} />
</>
) : (
this.renderLabel()
);
return (
<Box
{...rest}
role={edit ? undefined : 'button'}
tabIndex={edit ? undefined : 0}
onClick={edit ? undefined : this.handleClick}
onKeyDown={edit ? undefined : this.handleKeyDown}
sx={{
...tileBaseSx,
cursor: 'default',
position: 'relative',
overflow: 'hidden',
'&:hover': { bgcolor: 'background.paper', boxShadow: 0 },
...(edit
? { cursor: 'default', '&:hover': { bgcolor: 'background.paper', boxShadow: 0 } }
: { cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }),
...sx,
}}
>
<CategoryBackdrop thumbHash={thumbHash} fullHash={categoryFullHash} />
{backdrop}
<Box sx={{ position: 'absolute', inset: 0, zIndex: 1 }}>
<TopBand
onSettingsClick={this.handleSettingsToggle}
settingsTab={settingsTab}
terminalCount={terminals?.length || 0}
/>
<Box
onClick={this.handleMiddleBandClick}
sx={{
position: 'absolute',
top: '33.33%',
bottom: '33.33%',
left: 0,
right: 0,
cursor: settingsTab === -2 ? 'pointer' : 'default',
}}
>
<MiddleBand
settingsTab={settingsTab}
nameInput={nameInput}
terminals={terminals}
categoryPosterminalIds={categoryPosterminalIds}
onTerminalToggle={onTerminalToggle}
categoryFullHash={categoryFullHash}
/>
</Box>
<BottomRow onOpen={onOpen} editBand={editBand} />
{overlay}
</Box>
</Box>
);