recipe author and stars and a bit of cleanup

This commit is contained in:
fred 2025-07-24 12:11:32 -07:00
parent c47dac9986
commit 6f43d17ddd
21 changed files with 361 additions and 207 deletions

View file

@ -1,44 +1,22 @@
import React, { useState, useEffect } from 'react';
import { type Ingredient } from "../types/Recipe";
interface AddBulkIngredientsProps {
ingredients: Ingredient[];
onChange?: (ingredients: Ingredient[]) => void;
ingredients: string[];
onChange?: (ingredients: string[]) => void;
}
const AddBulkIngredients: React.FC<AddBulkIngredientsProps> = ({ ingredients, onChange }) => {
const [textValue, setTextValue] = useState<string>('');
useEffect(() => {
const textRepresentation = ingredients.map(ingredient =>
`${ingredient.quantity} ${ingredient.unit} ${ingredient.name}`
).join('\n');
const textRepresentation = ingredients.join('\n');
setTextValue(textRepresentation);
}, [ingredients]);
const parseAndUpdate = (value: string) => {
const lines = value.split('\n').filter(line => line.trim() !== '');
const pattern = /^([0-9/.]+)?\s*(\S+)\s*((\w+\s*)*)$/;
const parsedIngredients = lines.map(line => {
const parts = line.match(pattern);
let quantity = 0;
if (parts?.[1]) {
const [num, denom] = parts[1].split('/');
if (denom) {
quantity = parseFloat(num) / parseFloat(denom);
} else {
quantity = parseFloat(parts[1]);
}
}
return {
quantity: +quantity.toFixed(2),
unit: parts?.[2]?.trim() || '',
name: parts?.[3]?.trim() || ''
};
});
if (onChange) onChange(parsedIngredients);
if (onChange) onChange(lines);
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {

View file

@ -1,5 +1,9 @@
import React, { useState, useEffect } from 'react';
import { type Step } from "../types/Recipe";
interface Step {
step_number: number;
instruction: string;
}
interface AddBulkStepsProps {
steps: Step[];
@ -11,7 +15,7 @@ const AddBulkSteps: React.FC<AddBulkStepsProps> = ({ steps, onChange }) => {
useEffect(() => {
const textRepresentation = steps.map(step =>
`${step.instructions}`
`${step.instruction}`
).join('\n');
setTextValue(textRepresentation);
}, [steps]);
@ -24,8 +28,8 @@ const AddBulkSteps: React.FC<AddBulkStepsProps> = ({ steps, onChange }) => {
const parseAndUpdate = (value: string) => {
const lines = value.split('\n').filter(line => line.trim() !== '');
const parsedSteps = lines.map((line, idx) => {
return { idx: idx + 1, instructions: line }
const parsedSteps: Step[] = lines.map((line, idx) => {
return { step_number: idx + 1, instruction: line }
})
if (onChange) onChange(parsedSteps);

View file

@ -1,17 +1,17 @@
import React, { useState } from 'react';
import { type Recipe } from "../types/Recipe"
import { type RecipeSmall } from "../types/Recipe"
import Modal from '../components/Modal.tsx'
interface CookbookRecipeTileProps {
recipe: Recipe;
recipe: RecipeSmall;
handleDelete: (id: number | undefined) => void;
}
function CookbookRecipeTile({ recipe, handleDelete }: CookbookRecipeTileProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
const openModal = () => { setIsModalOpen(true) };
const closeModal = () => { setIsModalOpen(false) };
const confirmDelete = () => {
handleDelete(recipe.id);
closeModal();

View file

@ -1,5 +1,3 @@
import "../css/Modal.css"
interface ModalProps {
isOpen: boolean;
onClose: () => void;
@ -12,14 +10,14 @@ const Modal = ({ isOpen, onClose, message, confirmAction, cancelAction }: ModalP
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-overlay fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center" onClick={onClose}>
<div className="modal-content bg-amber-200 p-12 rounded-md shadow-md" onClick={(e) => e.stopPropagation()}>
<div className="modal-msg">
<span aria-labelledby="message">{message}</span>
</div>
<div className="modal-buttons">
<button onClick={confirmAction}>Yes, Delete</button>
<button onClick={cancelAction}>Cancel</button>
<button className="bg-amber-600 rounded-md m-4 pt-1 pb-1 pr-2 pl-2" onClick={confirmAction}>Yes, Delete</button>
<button className="bg-amber-600 rounded-md m-4 pt-1 pb-1 pr-2 pl-2" onClick={cancelAction}>Cancel</button>
</div>
</div>
</div>

View file

@ -1,4 +1,3 @@
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
const RecipeBookTabs = () => {

View file

@ -0,0 +1,26 @@
interface StarRatingProps {
rating: number;
onRatingChange: (newRating: number) => void;
}
const StarRating = ({ rating, onRatingChange }: StarRatingProps) => {
return (
<div>
{[...Array(5)].map((star, index) => {
index += 1;
return (
<span
key={index}
onClick={() => onRatingChange(index)}
style={{ color: index <= rating ? 'gold' : 'gray', fontSize: '2rem', cursor: 'pointer' }}
>
</span>
);
})}
</div>
);
};
export default StarRating;