Planejador de Refeições
Receitas
Adicionar Receita
Ingredientes
Planejamento Semanal
Lista de Compras
// Dados
const DAYS = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'];
const MEALS = [
{ key: 'breakfast', label: 'Café da manhã' },
{ key: 'lunch', label: 'Almoço' },
{ key: 'snack', label: 'Lanche' },
{ key: 'dinner', label: 'Jantar' }
];
// Armazenar dados
let recipes = [];
let plan = [];
// Estado atual de edição de slot
let currentSlot = null; // { dayIndex, mealKey }
// DOM refs
const recipesListEl = document.getElementById('recipesList');
const weekPlannerEl = document.getElementById('weekPlanner');
const groceriesEl = document.getElementById('groceries');
const modalEl = document.getElementById('modal');
const modalRecipesEl = document.getElementById('recipesInModal');
const recipeSearchEl = document.getElementById('recipeSearch');
const btnModalClose = document.getElementById('btnModalClose');
const btnNewRecipe = document.getElementById('btnNewRecipe');
const newRecipeSection = document.getElementById('newRecipeSection');
const btnPrint = document.getElementById('btnPrint');
const btnExportJSON = document.getElementById('btnExportJSON');
const btnExportCSV = document.getElementById('btnExportCSV');
const btnExportHTML = document.getElementById('btnExportHTML');
const btnRefreshGroceries = document.getElementById('btnRefreshGroceries');
const btnExportGroceriesCSV = document.getElementById('btnExportGroceriesCSV');
const btnAddIngredient = document.getElementById('btnAddIngredient');
const rpName = document.getElementById('rpName');
const rpServings = document.getElementById('rpServings');
const rpCalories = document.getElementById('rpCalories');
const rpCarbs = document.getElementById('rpCarbs');
const rpProtein = document.getElementById('rpProtein');
const rpFat = document.getElementById('rpFat');
const rpIngredientsEl = document.getElementById('rpIngredients');
const btnSaveRecipe = document.getElementById('btnSaveRecipe');
const btnCancelRecipe = document.getElementById('btnCancelRecipe');
const btnAddRecipeToList = document.getElementById('btnSaveRecipe');
const btnNewRecipeFinal = document.getElementById('btnNewRecipe');
// Init data
function initData(){
// Seeds
recipes = [
{
id: 'r1',
name: 'Omelete de Espinafre',
servings: 2,
calories: 320,
macros: { carbs: 8, protein: 22, fat: 18 },
ingredients: [
{ name: 'Ovos', quantity: 2, unit: 'un' },
{ name: 'Espinafre', quantity: 1, unit: 'xícara' },
{ name: 'Queijo', quantity: 30, unit: 'g' }
]
},
{
id: 'r2',
name: 'Salada de Grão-de-Bico',
servings: 2,
calories: 420,
macros: { carbs: 60, protein: 18, fat: 12 },
ingredients: [
{ name: 'Grão-de-bico cozido', quantity: 1, unit: 'xícara' },
{ name: 'Tomate', quantity: 1, unit: 'un' },
{ name: 'Cebola roxa', quantity: 0.25, unit: 'un' },
{ name: 'Azeite', quantity: 1, unit: 'col sopa' }
]
},
{
id: 'r3',
name: 'Frango com Quinoa',
servings: 2,
calories: 520,
macros: { carbs: 48, protein: 38, fat: 18 },
ingredients: [
{ name: 'Frango (peito)', quantity: 200, unit: 'g' },
{ name: 'Quinoa', quantity: 0.75, unit: 'xícara' },
{ name: 'Legumes variados', quantity: 1, unit: 'xícara' }
]
}
];
// Planejamento inicial (vazio)
plan = [];
for(let i=0; i<7; i++){ const day = {}; MEALS.forEach(m => day[m.key] = null);
plan.push({ dayIndex: i, meals: day });
}
renderRecipes();
renderPlanner();
updateGroceries();
}
// Helpers
function saveAll(){
const data = { recipes, plan };
localStorage.setItem('mealplanner.data', JSON.stringify(data));
}
function loadAll(){
const raw = localStorage.getItem('mealplanner.data');
if(raw){
try{
const d = JSON.parse(raw);
if(d.recipes) recipes = d.recipes;
if(d.plan){
plan = d.plan;
}
}catch(e){
console.warn('Erro ao ler dados locais', e);
}
} else {
// se não houver dados locais, usa seed
initData();
return;
}
renderRecipes();
renderPlanner();
updateGroceries();
}
// Rendering
function renderRecipes(){
recipesListEl.innerHTML = '';
if(recipes.length === 0){
recipesListEl.innerHTML = '
';
return;
}
const grid = document.createElement('div');
grid.className = 'recipes-grid';
grid.style.display = 'grid';
grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(260px, 1fr))';
grid.style.gap = '8px';
recipes.forEach(r => {
const c = document.createElement('div');
c.className = 'recipe-card';
c.innerHTML = `
`;
grid.appendChild(c);
});
recipesListEl.appendChild(grid);
// Bind select buttons
grid.querySelectorAll('button[data-id]').forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.getAttribute('data-id');
// Se houver slot atual ativo, atualize
if(currentSlot){
setSlotRecipe(currentSlot.dayIndex, currentSlot.mealKey, id);
} else {
// nada específico
}
});
});
}
function renderPlanner(){
weekPlannerEl.innerHTML = '';
// Cada dia
plan.forEach((d, idx) => {
const dayDiv = document.createElement('div');
dayDiv.className = 'day';
const dayName = DAYS[idx];
dayDiv.innerHTML = `
`;
// slots
MEALS.forEach(m => {
const slot = document.createElement('div');
slot.className = 'slot';
slot.setAttribute('data-dayidx', idx);
slot.setAttribute('data-meal', m.key);
const recipe = d.meals[m.key];
const recipeName = recipe ? (getRecipeById(recipe).name) : 'Selecionar';
slot.innerHTML = `
`;
slot.style.display = 'block';
slot.style.padding = '8px';
slot.style.borderRadius = '8px';
slot.style.border = '1px solid var(--border)';
slot.style.cursor = 'pointer';
slot.addEventListener('click', () => {
currentSlot = { dayIndex: idx, mealKey: m.key };
openModal();
});
dayDiv.appendChild(slot);
});
weekPlannerEl.appendChild(dayDiv);
});
}
function updateGroceries(){
// Constrói lista de compras agregando ingredientes de todas as receitas planejadas
const toto = {};
plan.forEach((d) => {
MEALS.forEach(m => {
const rid = d.meals[m.key];
if(rid){
const r = getRecipeById(rid);
if(r && r.ingredients){
r.ingredients.forEach(ing => {
const key = ing.name + '|' + (ing.unit || '');
const q = Number(ing.quantity) || 0;
if(!toto[key]){
toto[key] = { name: ing.name, unit: ing.unit || '', quantity: q };
} else {
toto[key].quantity += q;
}
});
}
}
});
});
// Renderizar
const arr = Object.values(toto);
groceriesEl.innerHTML = '';
if(arr.length === 0){
groceriesEl.innerHTML = '
';
return;
}
arr.forEach(item => {
const li = document.createElement('div');
li.className = 'row';
li.style.display = 'flex';
li.innerHTML = `${item.name} ${item.unit ? '('+item.unit+')' : ''}${item.quantity}`;
groceriesEl.appendChild(li);
});
}
// Helpers de utilidade
function getRecipeById(id){
return recipes.find(r => r.id === id);
}
function escapeHtml(text){
if(!text) return '';
return text
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// Modal
function openModal(){
modalEl.classList.remove('hidden');
modalEl.style.display = 'flex';
renderModalRecipes();
recipeSearchEl.value = '';
recipeSearchEl.focus();
}
function closeModal(){
modalEl.classList.add('hidden');
modalEl.style.display = 'none';
currentSlot = null;
}
function renderModalRecipes(){
const q = (recipeSearchEl.value || '').toLowerCase();
modalRecipesEl.innerHTML = '';
const items = recipes.filter(r => r.name.toLowerCase().includes(q));
if(items.length === 0){
modalRecipesEl.innerHTML = '
';
return;
}
items.forEach(r => {
const card = document.createElement('div');
card.className = 'recipe-card';
card.style.display = 'flex';
card.style.alignItems = 'center';
card.style.justifyContent = 'space-between';
card.innerHTML = `
`;
modalRecipesEl.appendChild(card);
});
modalRecipesEl.querySelectorAll('button[data-id]').forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.getAttribute('data-id');
if(currentSlot){
setSlotRecipe(currentSlot.dayIndex, currentSlot.mealKey, id);
closeModal();
}
});
});
}
function setSlotRecipe(dayIndex, mealKey, recipeId){
plan[dayIndex].meals[mealKey] = recipeId;
renderPlanner();
updateGroceries();
saveAll();
}
// Ingredient dinamics for new recipe
function addIngredientRow(name='', quantity='', unit=''){
const row = document.createElement('div');
row.className = 'ingredient';
row.innerHTML = `
`;
row.querySelector('button').addEventListener('click', () => row.remove());
rpIngredientsEl.appendChild(row);
}
// Events
document.addEventListener('DOMContentLoaded', () => {
// Load existing or seed
loadAll();
// Modal close
btnModalClose.addEventListener('click', closeModal);
window.addEventListener('keydown', (e) => {
if(e.key === 'Escape' && !modalEl.classList.contains('hidden')) closeModal();
});
recipeSearchEl.addEventListener('input', renderModalRecipes);
// Nova receita
btnNewRecipe.addEventListener('click', () => {
newRecipeSection.style.display = newRecipeSection.style.display === 'none' ? 'block' : 'none';
});
// Adicionar ingrediente
btnAddIngredient.addEventListener('click', () => {
addIngredientRow();
});
// Salvar nova receita
btnSaveRecipe.addEventListener('click', () => {
const nameVal = rpName.value.trim();
if(!nameVal){
alert('Informe o nome da receita.');
return;
}
const servings = Number(rpServings.value) || 1;
const calories = Number(rpCalories.value) || 0;
const carbs = Number(rpCarbs.value) || 0;
const protein = Number(rpProtein.value) || 0;
const fat = Number(rpFat.value) || 0;
// Ingredientes coletados
const ingDivs = rpIngredientsEl.querySelectorAll('.ingredient');
const ingredients = [];
ingDivs.forEach(div => {
const inputs = div.querySelectorAll('input');
const ingName = inputs[0].value.trim();
const qty = Number(inputs[1].value) || 0;
const unit = inputs[2].value.trim();
if(ingName){
ingredients.push({ name: ingName, quantity: qty, unit: unit });
}
});
const newRec = {
id: 'r' + Date.now(),
name: nameVal,
servings,
calories,
macros: { carbs, protein, fat },
ingredients
};
recipes.push(newRec);
// Limpar form
rpName.value = ''; rpServings.value = ''; rpCalories.value = '';
rpCarbs.value = ''; rpProtein.value = ''; rpFat.value = '';
rpIngredientsEl.innerHTML = '';
newRecipeSection.style.display = 'none';
// Atualizar UI
renderRecipes();
renderPlanner();
saveAll();
// Fechar modal se aberto
renderModalRecipes();
});
btnCancelRecipe.addEventListener('click', () => {
newRecipeSection.style.display = 'none';
});
// Exportações
btnExportJSON.addEventListener('click', () => {
const data = { recipes, plan };
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
downloadBlob(blob, 'mealplanner.json');
});
btnExportCSV.addEventListener('click', () => {
const csv = generateGroceriesCSV();
const blob = new Blob([csv], { type: 'text/csv' });
downloadBlob(blob, 'groceries.csv');
});
btnExportHTML.addEventListener('click', () => {
const html = generatePrintableHTML();
const blob = new Blob([html], { type: 'text/html' });
downloadBlob(blob, 'mealplanner.html');
});
btnPrint.addEventListener('click', () => window.print());
btnRefreshGroceries.addEventListener('click', () => {
updateGroceries();
});
btnExportGroceriesCSV.addEventListener('click', () => {
const csv = generateGroceriesCSV();
const blob = new Blob([csv], { type: 'text/csv' });
downloadBlob(blob, 'groceries.csv');
});
});
// Utils de exportação
function downloadBlob(blob, filename){
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function generateGroceriesCSV(){
// Reutilizar a lógica de atualização de compras
const items = [];
const totals = {};
plan.forEach(d => {
MEALS.forEach(m => {
const rid = d.meals[m.key];
if(rid){
const r = getRecipeById(rid);
if(r && r.ingredients){
r.ingredients.forEach(ing => {
const key = (ing.name || '') + '|' + (ing.unit || '');
const q = Number(ing.quantity) || 0;
if(!totals[key]){
totals[key] = { name: ing.name, unit: ing.unit, quantity: q };
} else {
totals[key].quantity += q;
}
});
}
}
});
});
const headers = ['Ingrediente', 'Quantidade', 'Unidade'];
items.push(headers.join(','));
Object.values(totals).forEach(it => {
items.push([it.name, String(it.quantity), it.unit].join(','));
});
return items.join('\\n');
}
function generatePrintableHTML(){
// Gera uma versão simples e imprimível do plano
let html = `
Plano de Refeições
Este é um resumo gerado pelo Planejador de Refeições.
Planos por dia
| Dia | ${m.label} |
|---|---|
| ${DAYS[idx]} | ${name} |
`;
return html;
}
`;

































