v1.0 - Meal planner with favorites, history, and notes
This commit is contained in:
310
src/App.jsx
Normal file
310
src/App.jsx
Normal file
@@ -0,0 +1,310 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||
const MEALS = ['Breakfast', 'Lunch', 'Dinner', 'Snacks']
|
||||
const API_URL = '/api'
|
||||
|
||||
function App() {
|
||||
const [week, setWeek] = useState(Array(7).fill('').map(() => Array(4).fill('')))
|
||||
const [favorites, setFavorites] = useState([])
|
||||
const [history, setHistory] = useState([])
|
||||
const [darkMode, setDarkMode] = useState(true)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showFavorites, setShowFavorites] = useState(false)
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
const [selectedFavorite, setSelectedFavorite] = useState(null)
|
||||
const [notesModal, setNotesModal] = useState(null) // { dayIndex, mealIndex, notes }
|
||||
const [notes, setNotes] = useState({}) // { "dayIndex-mealIndex": "notes" }
|
||||
|
||||
useEffect(() => {
|
||||
const savedTheme = localStorage.getItem('darkMode')
|
||||
if (savedTheme !== null) setDarkMode(JSON.parse(savedTheme))
|
||||
|
||||
fetch(`${API_URL}/meals`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.meals && data.meals[0]) {
|
||||
const weekData = data.meals[0].map(dayMeals =>
|
||||
Array(4).fill('').map((_, i) => dayMeals[i] || '')
|
||||
)
|
||||
setWeek(weekData)
|
||||
}
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
|
||||
fetch(`${API_URL}/favorites`)
|
||||
.then(res => res.json())
|
||||
.then(data => setFavorites(data.favorites || []))
|
||||
.catch(err => console.error(err))
|
||||
|
||||
fetch(`${API_URL}/history`)
|
||||
.then(res => res.json())
|
||||
.then(data => setHistory(data.history || []))
|
||||
.catch(err => console.error(err))
|
||||
|
||||
// Load notes
|
||||
fetch(`${API_URL}/notes`)
|
||||
.then(res => res.json())
|
||||
.then(data => setNotes(data.notes || {}))
|
||||
.catch(err => console.error(err))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('darkMode', JSON.stringify(darkMode))
|
||||
}, [darkMode])
|
||||
|
||||
const updateMeal = (dayIndex, mealIndex, value) => {
|
||||
const newWeek = week.map((row, i) =>
|
||||
i === dayIndex
|
||||
? row.map((cell, j) => j === mealIndex ? value : cell)
|
||||
: row
|
||||
)
|
||||
setWeek(newWeek)
|
||||
|
||||
fetch(`${API_URL}/meals`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ meals: [newWeek.map(row => [...row]), Array(7).fill('').map(() => Array(4).fill(''))] })
|
||||
}).catch(err => console.error(err))
|
||||
}
|
||||
|
||||
const toggleFavorite = (meal, dayIndex, mealIndex) => {
|
||||
fetch(`${API_URL}/favorites`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ meal, day: DAYS[dayIndex], mealType: MEALS[mealIndex] })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => setFavorites(data.favorites))
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
const isFavorite = (meal) => {
|
||||
return favorites.some(f => f.meal === meal && meal !== '')
|
||||
}
|
||||
|
||||
const addFromFavorite = (meal) => {
|
||||
// Just set the selected favorite, let user click where to place it
|
||||
setSelectedFavorite(meal)
|
||||
setShowFavorites(false)
|
||||
}
|
||||
|
||||
const handleCellClick = (dayIndex, mealIndex) => {
|
||||
if (selectedFavorite) {
|
||||
const newWeek = week.map((row, i) =>
|
||||
i === dayIndex
|
||||
? row.map((cell, j) => j === mealIndex ? selectedFavorite : cell)
|
||||
: row
|
||||
)
|
||||
setWeek(newWeek)
|
||||
fetch(`${API_URL}/meals`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ meals: [newWeek, Array(7).fill('').map(() => Array(4).fill(''))] })
|
||||
}).catch(err => console.error(err))
|
||||
setSelectedFavorite(null)
|
||||
}
|
||||
}
|
||||
|
||||
const addFromHistory = (historyWeek) => {
|
||||
setWeek(historyWeek.map(row => [...row]))
|
||||
fetch(`${API_URL}/meals`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ meals: [historyWeek, Array(7).fill('').map(() => Array(4).fill(''))] })
|
||||
}).catch(err => console.error(err))
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
if (confirm('Clear all meals? Current week will be saved to history.')) {
|
||||
const empty = Array(7).fill('').map(() => Array(4).fill(''))
|
||||
setWeek(empty)
|
||||
fetch(`${API_URL}/meals`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ meals: [empty, Array(7).fill('').map(() => Array(4).fill(''))], addToHistory: true })
|
||||
}).catch(err => console.error(err))
|
||||
|
||||
// Refresh history
|
||||
fetch(`${API_URL}/history`)
|
||||
.then(res => res.json())
|
||||
.then(data => setHistory(data.history || []))
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
}
|
||||
|
||||
const print = () => {
|
||||
window.print()
|
||||
}
|
||||
|
||||
const openNotes = (dayIndex, mealIndex) => {
|
||||
const key = `${dayIndex}-${mealIndex}`
|
||||
setNotesModal({ dayIndex, mealIndex, notes: notes[key] || '' })
|
||||
}
|
||||
|
||||
const saveNotes = () => {
|
||||
if (notesModal) {
|
||||
const key = `${notesModal.dayIndex}-${notesModal.mealIndex}`
|
||||
const newNotes = { ...notes }
|
||||
if (notesModal.notes) {
|
||||
newNotes[key] = notesModal.notes
|
||||
} else {
|
||||
delete newNotes[key]
|
||||
}
|
||||
setNotes(newNotes)
|
||||
|
||||
fetch(`${API_URL}/notes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ notes: newNotes })
|
||||
}).catch(err => console.error(err))
|
||||
|
||||
setNotesModal(null)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div className="loading">Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`app ${darkMode ? 'dark' : 'light'}`}>
|
||||
<header>
|
||||
<h1>What the F**K to Eat?! 🍽️</h1>
|
||||
<div className="controls">
|
||||
{selectedFavorite && (
|
||||
<span className="selected-fav">Click a cell to add: <strong>{selectedFavorite}</strong> <button onClick={() => setSelectedFavorite(null)}>Cancel</button></span>
|
||||
)}
|
||||
<button onClick={() => setDarkMode(!darkMode)}>
|
||||
{darkMode ? '☀️ Light' : '🌙 Dark'}
|
||||
</button>
|
||||
<button onClick={() => setShowFavorites(!showFavorites)}>
|
||||
❤️ Favorites ({favorites.length})
|
||||
</button>
|
||||
<button onClick={() => setShowHistory(!showHistory)}>
|
||||
📜 History ({history.length})
|
||||
</button>
|
||||
<button onClick={reset}>🔄 Reset</button>
|
||||
<button onClick={print}>🖨️ Print</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Favorites Panel */}
|
||||
{showFavorites && (
|
||||
<div className="panel">
|
||||
<h2>❤️ Favorites</h2>
|
||||
{favorites.length === 0 ? (
|
||||
<p className="empty-msg">No favorites yet. Click the heart on a meal to add it.</p>
|
||||
) : (
|
||||
<div className="favorites-grid">
|
||||
{favorites.map((f, i) => (
|
||||
<div key={i} className="fav-card" onClick={() => addFromFavorite(f.meal)}>
|
||||
<span>{f.meal}</span>
|
||||
<small>{f.day} - {f.mealType}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* History Panel */}
|
||||
{showHistory && (
|
||||
<div className="panel">
|
||||
<h2>📜 History</h2>
|
||||
{history.length === 0 ? (
|
||||
<p className="empty-msg">No history yet. Reset the week to save it.</p>
|
||||
) : (
|
||||
<div className="history-list">
|
||||
{history.map((h, i) => (
|
||||
<div key={i} className="history-card" onClick={() => addFromHistory(h.week)}>
|
||||
<span>Week {i + 1}</span>
|
||||
<small>{new Date(h.date).toLocaleDateString()}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<main>
|
||||
<table className="meal-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Day</th>
|
||||
{MEALS.map(meal => <th key={meal}>{meal}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{DAYS.map((day, dayIndex) => (
|
||||
<tr key={day}>
|
||||
<td className="day-label">{day}</td>
|
||||
{MEALS.map((meal, mealIndex) => (
|
||||
<td
|
||||
key={meal}
|
||||
onClick={() => handleCellClick(dayIndex, mealIndex)}
|
||||
className={notes[`${dayIndex}-${mealIndex}`] ? 'has-notes' : ''}
|
||||
>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={week[dayIndex][mealIndex] || ''}
|
||||
onChange={(e) => updateMeal(dayIndex, mealIndex, e.target.value)}
|
||||
placeholder="..."
|
||||
/>
|
||||
<div className="cell-buttons">
|
||||
{week[dayIndex][mealIndex] && (
|
||||
<button
|
||||
className="fav-btn"
|
||||
onClick={(e) => { e.stopPropagation(); toggleFavorite(week[dayIndex][mealIndex], dayIndex, mealIndex); }}
|
||||
>
|
||||
{isFavorite(week[dayIndex][mealIndex]) ? '❤️' : '🤍'}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="notes-btn"
|
||||
onClick={(e) => { e.stopPropagation(); openNotes(dayIndex, mealIndex); }}
|
||||
>
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div className="legend">
|
||||
<span>❤️ Click heart to favorite</span>
|
||||
<span>📋 Click notepad to add notes</span>
|
||||
<span><span className="legend-box"></span> Blue border = has notes</span>
|
||||
</div>
|
||||
<p>Data synced across all devices</p>
|
||||
</footer>
|
||||
|
||||
{/* Notes Modal */}
|
||||
{notesModal && (
|
||||
<div className="modal-overlay" onClick={() => setNotesModal(null)}>
|
||||
<div className="modal" onClick={e => e.stopPropagation()}>
|
||||
<h3>Notes for {DAYS[notesModal.dayIndex]} - {MEALS[notesModal.mealIndex]}</h3>
|
||||
<textarea
|
||||
value={notesModal.notes}
|
||||
onChange={(e) => setNotesModal({ ...notesModal, notes: e.target.value })}
|
||||
placeholder="Add notes here... (prep time, ingredients, etc.)"
|
||||
rows={5}
|
||||
/>
|
||||
<div className="modal-buttons">
|
||||
<button onClick={saveNotes}>Save</button>
|
||||
<button onClick={() => setNotesModal(null)} className="cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
484
src/index.css
Normal file
484
src/index.css
Normal file
@@ -0,0 +1,484 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
transition: background 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.app.dark {
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.app.light {
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #e2e8f0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app.light h1 {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.85;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.app.dark button {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.app.light button {
|
||||
background: #64748b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.meal-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
border: 2px solid #334155;
|
||||
}
|
||||
|
||||
.meal-table th,
|
||||
.meal-table td {
|
||||
padding: 14px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.meal-table th {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.app.dark .meal-table th {
|
||||
background: #334155;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.app.light .meal-table th {
|
||||
background: #cbd5e1;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
/* Row striping */
|
||||
.meal-table tbody tr:nth-child(odd) {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.app.light .meal-table tbody tr:nth-child(odd) {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* Hover effect */
|
||||
.meal-table tbody tr {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.meal-table tbody tr:hover {
|
||||
background: rgba(59, 130, 246, 0.15) !important;
|
||||
}
|
||||
|
||||
.app.light .meal-table tbody tr:hover {
|
||||
background: rgba(59, 130, 246, 0.08) !important;
|
||||
}
|
||||
|
||||
.meal-table td {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.meal-table td.has-notes {
|
||||
box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
.app.light .meal-table td {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.meal-table td.day-label {
|
||||
font-weight: 600;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.meal-table textarea {
|
||||
width: 100%;
|
||||
min-height: 4.5em;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: inherit;
|
||||
resize: none;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.app.light .meal-table textarea {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.meal-table textarea:focus {
|
||||
outline: none;
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.meal-table textarea::placeholder {
|
||||
opacity: 0.4;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
opacity: 0.6;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.legend span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.legend-box {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(59, 130, 246, 0.6);
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Favorites & History Panels */
|
||||
.panel {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.app.light .panel {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.panel h2 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.empty-msg {
|
||||
opacity: 0.6;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.favorites-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.fav-card {
|
||||
background: rgba(59, 130, 246, 0.2);
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.fav-card:hover {
|
||||
transform: scale(1.05);
|
||||
background: rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.fav-card span {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fav-card small {
|
||||
opacity: 0.7;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.history-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.history-card:hover {
|
||||
background: rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.history-card span {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.history-card small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Favorite button in table */
|
||||
.meal-table td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cell-buttons {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.meal-table td:hover .cell-buttons {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.fav-btn, .notes-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Selected favorite indicator */
|
||||
.selected-fav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(59, 130, 246, 0.3);
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.selected-fav button {
|
||||
background: #ef4444;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.meal-table tbody tr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #1e293b;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.app.light .modal {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.modal h3 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.modal textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #334155;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
font-family: inherit;
|
||||
resize: none;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.app.light .modal textarea {
|
||||
background: #fff;
|
||||
border-color: #cbd5e1;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.modal textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-buttons button {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.modal-buttons .cancel {
|
||||
background: #475569;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.meal-table {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.meal-table th {
|
||||
background: #333 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.meal-table td {
|
||||
border-color: #000 !important;
|
||||
}
|
||||
|
||||
.meal-table textarea {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 12pt;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meal-table {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.meal-table th,
|
||||
.meal-table td {
|
||||
padding: 10px 6px;
|
||||
}
|
||||
|
||||
.meal-table textarea {
|
||||
font-size: 0.9rem;
|
||||
min-height: 4em;
|
||||
}
|
||||
}
|
||||
10
src/main.jsx
Normal file
10
src/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
Reference in New Issue
Block a user