import { Component, PureComponent } from 'react'; import { EditablePanelTile } from 'dashgui/panels'; import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import { StorageManagerContext } from '../../../api/storageManagerContext'; import { getCategoryBreadcrumbs } from '../../../store/categorySlice'; import { Box, Chip, IconButton } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import CategoryPickerDialog from '../CategoryPickerDialog'; import { notifyError } from '../../../utils/notify'; const articleNameTextSx = { fontWeight: 700, lineHeight: 1.2, }; const articleNameFieldSx = { '& .MuiInputBase-input': { fontSize: '2.125rem', ...articleNameTextSx, }, }; const articleNameFieldHiddenSx = { ...articleNameFieldSx, userSelect: 'text', '& .MuiOutlinedInput-notchedOutline': { borderColor: 'transparent', }, '& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'transparent', }, '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'transparent', }, '& .MuiInputLabel-root': { visibility: 'hidden', }, }; const pillsContainerSx = { display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 'auto', }; const columnSx = { display: 'flex', flexDirection: 'column', flex: 1, alignSelf: 'stretch', }; function categoryIdsChanged(a, b) { const aa = a ?? []; const bb = b ?? []; return ( aa.length !== bb.length || [...aa].map(String).sort().join(',') !== [...bb].map(String).sort().join(',') ); } function draftDirty(draft, saved) { const nameDiff = draft.name.trim() !== String(saved.name ?? '').trim(); const catDiff = categoryIdsChanged(draft.categoryIds, saved.categoryIds); return nameDiff || catDiff; } function draftValid(draft) { return draft.name.trim().length > 0 && (draft.categoryIds?.length ?? 0) > 0; } const headerTileSx = { minHeight: 0, }; function buildCategoryLabel(categories, categoryId, rootCategoryId) { if (!categories || categoryId == null) return ''; const crumbs = getCategoryBreadcrumbs(categories, categoryId, rootCategoryId); if (crumbs.length === 0) return ''; return crumbs.map((c) => c.name).join(' | '); } function categoryLabels(categoryIds, categories, rootCategoryId) { return (categoryIds ?? []) .map((id) => ({ id, label: buildCategoryLabel(categories, id, rootCategoryId) })) .filter((c) => c.label); } class CategoryPills extends Component { static contextType = StorageManagerContext; constructor(props) { super(props); this.state = { pickerOpen: false }; } handleRemoveCategory = (removeId) => { const { categoryIds, onCategoryIdsChange } = this.props; const remaining = categoryIds.filter((id) => String(id) !== String(removeId)); onCategoryIdsChange?.(remaining); }; handleOpenPicker = () => { this.setState({ pickerOpen: true }); }; handleClosePicker = () => { this.setState({ pickerOpen: false }); }; render() { const { categoryIds, editMode, onCategoryIdsChange } = this.props; const { pickerOpen } = this.state; const { categories, rootCategoryId } = this.context.getCategoryState() ?? {}; const labels = categoryLabels(categoryIds, categories, rootCategoryId); return ( <> {labels.map(({ id, label }) => ( 1 ? () => this.handleRemoveCategory(id) : undefined } /> ))} {editMode ? ( ) : null} {editMode ? ( ) : null} ); } } class NameDisplay extends Component { render() { const { name, categoryIds } = this.props; return ( {}} sx={articleNameFieldHiddenSx} /> {(categoryIds?.length ?? 0) > 0 ? ( ) : null} ); } } class NameEdit extends Component { constructor(props) { super(props); this.state = { name: props.name, categoryIds: [...(props.categoryIds ?? [])], }; } componentDidMount() { const { onNameDraftChange, onCategoryIdsDraftChange } = this.props; onNameDraftChange?.(this.state.name); onCategoryIdsDraftChange?.(this.state.categoryIds); } handleNameChange = (name) => { this.setState({ name }); this.props.onNameDraftChange?.(name); }; handleCategoryIdsChange = (categoryIds) => { this.setState({ categoryIds }); this.props.onCategoryIdsDraftChange?.(categoryIds); }; render() { const { name, categoryIds } = this.state; return ( ); } } export default class ArticleViewName extends PureComponent { static contextType = StorageManagerContext; constructor(props) { super(props); this.state = { nameDraft: null, categoryIdsDraft: null, }; } handleNameDraftChange = (name) => { this.setState({ nameDraft: name }); }; handleCategoryIdsDraftChange = (categoryIds) => { this.setState({ categoryIdsDraft: categoryIds }); }; handleSave = async () => { const { article: articleData, onArticleUpdated } = this.props; const { nameDraft, categoryIdsDraft } = this.state; const store = this.context; const name = nameDraft.trim(); const categoryIds = categoryIdsDraft; const nameChanged = name !== String(articleData.name ?? '').trim(); const categoriesChanged = categoryIdsChanged(categoryIds, articleData.categoryIds); let article = null; if (nameChanged) { console.log('storing name update', name, articleData.name); const nameRes = await store.article.update(articleData.sku, { name }); article = nameRes.article; } if (categoriesChanged) { console.log('storing categories update', categoryIds, articleData.categoryIds); const catRes = await store.article.setCategories(articleData.sku, { categoryIds }); article = catRes.article; } if (article) onArticleUpdated?.(article); this.setState({ nameDraft: null, categoryIdsDraft: null }); }; render() { const { article: articleData } = this.props; const { nameDraft, categoryIdsDraft } = this.state; const savedCategoryIds = articleData.categoryIds ?? []; const value = { name: articleData.name ?? '', categoryIds: [...savedCategoryIds] }; const draft = nameDraft != null && categoryIdsDraft != null ? { name: nameDraft, categoryIds: categoryIdsDraft } : null; return ( draft != null && draftDirty(draft, value)} canSave={() => draft != null && draftValid(draft)} display={ } edit={ } /> ); } }