Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Fix crash when switching ingredient units caused by reading the event target inside a deferred React state updater
- Fix UI hang on ingredient views by deferring accordion body rendering until expanded via `mountOnEnter`

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,110 +56,112 @@ export default function IngredientInternalUpdateRow(props: IngredientInternalUpd
</small>
</div>
</Accordion.Header>
<Accordion.Body>
<Form>
<Form.Group className="mb-3">
<Form.Label>Ingredient ID</Form.Label>
<Form.Control type="text" value={ingredientId} disabled />
</Form.Group>
<Accordion.Collapse eventKey={eventKey} mountOnEnter>
<div className="accordion-body">
<Form>
<Form.Group className="mb-3">
<Form.Label>Ingredient ID</Form.Label>
<Form.Control type="text" value={ingredientId} disabled />
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Ingredient Names (semi-colon separated)</Form.Label>
<Form.Control
type="text"
placeholder="Cooktime ingredient name (semi-colon separated)"
onChange={e => setUpdate({ ...update, ingredientNames: e.target.value })}
value={update.ingredientNames}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Ingredient Names (semi-colon separated)</Form.Label>
<Form.Control
type="text"
placeholder="Cooktime ingredient name (semi-colon separated)"
onChange={e => setUpdate({ ...update, ingredientNames: e.target.value })}
value={update.ingredientNames}
/>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Nutrition Description</Form.Label>
<Form.Control type="text" value={nutritionDescription || ''} disabled />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Nutrition Description</Form.Label>
<Form.Control type="text" value={nutritionDescription || ''} disabled />
</Form.Group>

<Row className="mb-3">
<Col md={6}>
<Form.Group>
<Form.Label>NDB Number</Form.Label>
<NutritionAutosuggest
placeholder="Type ingredient name or NDB Number"
value={update.ndbNumber}
onSelect={(ndbNumber, gtinUpc) => {
if (ndbNumber !== null) {
setUpdate({ ...update, ndbNumber });
}
}}
fieldType="ndb"
/>
</Form.Group>
</Col>
<Col md={6}>
<Form.Group>
<Form.Label>GTIN/UPC Number</Form.Label>
<NutritionAutosuggest
placeholder="Type ingredient name or GTIN/UPC"
value={update.gtinUpc}
onSelect={(ndbNumber, gtinUpc) => {
if (gtinUpc !== null) {
setUpdate({ ...update, gtinUpc });
}
}}
fieldType="gtin"
/>
</Form.Group>
</Col>
</Row>
<Row className="mb-3">
<Col md={6}>
<Form.Group>
<Form.Label>NDB Number</Form.Label>
<NutritionAutosuggest
placeholder="Type ingredient name or NDB Number"
value={update.ndbNumber}
onSelect={(ndbNumber, gtinUpc) => {
if (ndbNumber !== null) {
setUpdate({ ...update, ndbNumber });
}
}}
fieldType="ndb"
/>
</Form.Group>
</Col>
<Col md={6}>
<Form.Group>
<Form.Label>GTIN/UPC Number</Form.Label>
<NutritionAutosuggest
placeholder="Type ingredient name or GTIN/UPC"
value={update.gtinUpc}
onSelect={(ndbNumber, gtinUpc) => {
if (gtinUpc !== null) {
setUpdate({ ...update, gtinUpc });
}
}}
fieldType="gtin"
/>
</Form.Group>
</Col>
</Row>

<Row className="mb-3">
<Col md={6}>
<Form.Group>
<Form.Label>Count RegEx</Form.Label>
<Form.Control
type="text"
placeholder="Count RegEx"
onChange={e => setUpdate({ ...update, countRegex: e.target.value })}
value={update.countRegex}
/>
</Form.Group>
</Col>
<Col md={6}>
<Form.Group>
<Form.Label>Unit Mass (kg)</Form.Label>
<Form.Control
type="text"
placeholder="Expected unit mass"
onChange={e => {
let unitMass = Number.parseFloat(e.target.value);
if (e.target.value === "0.") {
setUpdate({ ...update, expectedUnitMass: "0." });
} else {
if (isNaN(unitMass)) {
unitMass = 0.1;
<Row className="mb-3">
<Col md={6}>
<Form.Group>
<Form.Label>Count RegEx</Form.Label>
<Form.Control
type="text"
placeholder="Count RegEx"
onChange={e => setUpdate({ ...update, countRegex: e.target.value })}
value={update.countRegex}
/>
</Form.Group>
</Col>
<Col md={6}>
<Form.Group>
<Form.Label>Unit Mass (kg)</Form.Label>
<Form.Control
type="text"
placeholder="Expected unit mass"
onChange={e => {
let unitMass = Number.parseFloat(e.target.value);
if (e.target.value === "0.") {
setUpdate({ ...update, expectedUnitMass: "0." });
} else {
if (isNaN(unitMass)) {
unitMass = 0.1;
}
setUpdate({ ...update, expectedUnitMass: unitMass.toString() })
}
setUpdate({ ...update, expectedUnitMass: unitMass.toString() })
}
}}
value={update.expectedUnitMass}
/>
</Form.Group>
</Col>
</Row>
}}
value={update.expectedUnitMass}
/>
</Form.Group>
</Col>
</Row>

<div className="d-flex gap-2">
{isSubmitting ? (
<Spinner animation="border" size="sm" />
) : (
<Button
variant={failed ? "danger" : "outline-success"}
onClick={handleSave}
>
{failed ? "Failed - Retry" : "Save"}
</Button>
)}
</div>
</Form>
</Accordion.Body>
<div className="d-flex gap-2">
{isSubmitting ? (
<Spinner animation="border" size="sm" />
) : (
<Button
variant={failed ? "danger" : "outline-success"}
onClick={handleSave}
>
{failed ? "Failed - Retry" : "Save"}
</Button>
)}
</div>
</Form>
</div>
</Accordion.Collapse>
</Accordion.Item>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function IngredientNormalizerRow(props: IngredientNormalizerRowPr
onError(`Failed to replace ingredient: ${errorText}`);
return;
}

location.reload();
} catch (err) {
onError(`Network error: ${err instanceof Error ? err.message : 'Unknown error'}`);
Expand All @@ -55,7 +55,7 @@ export default function IngredientNormalizerRow(props: IngredientNormalizerRowPr
onError(`Failed to delete ingredient: ${errorText}`);
return;
}

location.reload();
} catch (err) {
onError(`Network error: ${err instanceof Error ? err.message : 'Unknown error'}`);
Expand All @@ -79,63 +79,65 @@ export default function IngredientNormalizerRow(props: IngredientNormalizerRowPr
</div>
</div>
</Accordion.Header>
<Accordion.Body>
<Form>
<Form.Group className="mb-3">
<Form.Label>Ingredient ID</Form.Label>
<Form.Control type="text" value={replacedId} disabled />
<Form.Text className="text-muted">
Copy this ID to merge this ingredient into another
</Form.Text>
</Form.Group>
<Accordion.Collapse eventKey={eventKey} mountOnEnter>
<div className="accordion-body">
<Form>
<Form.Group className="mb-3">
<Form.Label>Ingredient ID</Form.Label>
<Form.Control type="text" value={replacedId} disabled />
<Form.Text className="text-muted">
Copy this ID to merge this ingredient into another
</Form.Text>
</Form.Group>

<Row className="mb-3">
<Col md={6}>
<Form.Group>
<Form.Label>Recipes Using</Form.Label>
<Form.Control type="text" value={usage} disabled />
</Form.Group>
</Col>
<Col md={6}>
<Form.Group>
<Form.Label>Has Nutrition Data</Form.Label>
<Form.Control type="text" value={hasNutrition ? 'Yes' : 'No'} disabled />
</Form.Group>
</Col>
</Row>
<Row className="mb-3">
<Col md={6}>
<Form.Group>
<Form.Label>Recipes Using</Form.Label>
<Form.Control type="text" value={usage} disabled />
</Form.Group>
</Col>
<Col md={6}>
<Form.Group>
<Form.Label>Has Nutrition Data</Form.Label>
<Form.Control type="text" value={hasNutrition ? 'Yes' : 'No'} disabled />
</Form.Group>
</Col>
</Row>

<Form.Group className="mb-3">
<Form.Label>Replace With (search by ingredient name)</Form.Label>
<IngredientAutosuggest
placeholder="Type ingredient name to search"
value=""
onSelect={(ingredientId, ingredientName) => setReplacement({ ...replacement, keptId: ingredientId })}
excludeId={replacedId}
/>
<Form.Text className="text-muted">
This will merge all recipe references to the target ingredient
</Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Replace With (search by ingredient name)</Form.Label>
<IngredientAutosuggest
placeholder="Type ingredient name to search"
value=""
onSelect={(ingredientId, ingredientName) => setReplacement({ ...replacement, keptId: ingredientId })}
excludeId={replacedId}
/>
<Form.Text className="text-muted">
This will merge all recipe references to the target ingredient
</Form.Text>
</Form.Group>

<div className="d-flex gap-2">
<Button
variant="outline-success"
onClick={handleReplace}
disabled={!replacement.keptId || replacement.keptId === EMPTY_GUID}
>
Replace & Merge
</Button>
{usage === 0 && (
<div className="d-flex gap-2">
<Button
variant="outline-danger"
onClick={handleDelete}
variant="outline-success"
onClick={handleReplace}
disabled={!replacement.keptId || replacement.keptId === EMPTY_GUID}
>
Delete
Replace & Merge
</Button>
)}
</div>
</Form>
</Accordion.Body>
{usage === 0 && (
<Button
variant="outline-danger"
onClick={handleDelete}
>
Delete
</Button>
)}
</div>
</Form>
</div>
</Accordion.Collapse>
</Accordion.Item>
);
}
Loading
Loading