Removable ingredients in planner
This commit is contained in:
@@ -1,59 +0,0 @@
|
|||||||
name: Build and Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Gitea
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: git.ulfrx.dev
|
|
||||||
username: ${{ secrets.GIT_USERNAME }}
|
|
||||||
password: ${{ secrets.GIT_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
git.ulfrx.dev/ulfr/recipe-mockup:latest
|
|
||||||
git.ulfrx.dev/ulfr/recipe-mockup:${{ github.sha }}
|
|
||||||
|
|
||||||
- name: Update image tag in infra repo
|
|
||||||
run: |
|
|
||||||
REPO="ulfr/homelab"
|
|
||||||
FILE_PATH="stacks/recipe-mockup/docker-compose.yaml"
|
|
||||||
NEW_TAG="${{ github.sha }}"
|
|
||||||
API_URL="https://git.ulfrx.dev/api/v1/repos/${REPO}/contents/${FILE_PATH}"
|
|
||||||
|
|
||||||
RESPONSE=$(curl -fsS \
|
|
||||||
-H "Authorization: token ${{ secrets.GIT_TOKEN }}" \
|
|
||||||
"${API_URL}")
|
|
||||||
|
|
||||||
FILE_SHA=$(echo "$RESPONSE" | jq -r '.sha')
|
|
||||||
CONTENT=$(echo "$RESPONSE" | jq -r '.content' | base64 -d)
|
|
||||||
|
|
||||||
UPDATED=$(echo "$CONTENT" | sed "s|git.ulfrx.dev/ulfr/recipe-mockup:[a-zA-Z0-9._-]*|git.ulfrx.dev/ulfr/recipe-mockup:${NEW_TAG}|g")
|
|
||||||
ENCODED=$(echo "$UPDATED" | base64 -w 0)
|
|
||||||
|
|
||||||
curl -fsS -X PUT \
|
|
||||||
-H "Authorization: token ${{ secrets.GIT_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"message\":\"deploy: recipe-mockup ${NEW_TAG::7}\",\"content\":\"${ENCODED}\",\"sha\":\"${FILE_SHA}\"}" \
|
|
||||||
"${API_URL}"
|
|
||||||
|
|
||||||
- name: Trigger Dockhand redeploy
|
|
||||||
run: |
|
|
||||||
curl -fsS "${{ secrets.DOCKHAND_WEBHOOK_URL }}?secret=${{ secrets.DOCKHAND_WEBHOOK_SECRET }}"
|
|
||||||
@@ -3,7 +3,7 @@ import { getFilterHTML, setupFilter } from './views/Filter.js?v=2';
|
|||||||
import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js?v=2';
|
import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js?v=2';
|
||||||
import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js?v=2';
|
import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js?v=2';
|
||||||
import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js?v=2';
|
import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js?v=2';
|
||||||
import { getMealPlanEditorHTML, setupMealPlanEditor } from './ui/mealPlanEditor.js?v=2';
|
import { getMealPlanEditorHTML, setupMealPlanEditor } from './ui/mealPlanEditor.js?v=3';
|
||||||
|
|
||||||
function getAppToastHTML() {
|
function getAppToastHTML() {
|
||||||
return `
|
return `
|
||||||
|
|||||||
@@ -61,13 +61,13 @@ export function getMealPlanEditorHTML() {
|
|||||||
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mt-3 mb-2">Pora posiłku</p>
|
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mt-3 mb-2">Pora posiłku</p>
|
||||||
<div id="mpe-slot-chips" class="flex flex-wrap gap-1.5"></div>
|
<div id="mpe-slot-chips" class="flex flex-wrap gap-1.5"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="mpe-nutrition-section" class="mb-4"></div>
|
||||||
<div id="mpe-servings-row" class="flex items-center justify-between mb-4"></div>
|
<div id="mpe-servings-row" class="flex items-center justify-between mb-4"></div>
|
||||||
<div id="mpe-ing-section" class="mb-4">
|
<div id="mpe-ing-section" class="mb-4">
|
||||||
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Składniki</p>
|
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Składniki</p>
|
||||||
<div id="mpe-ing-list" class="space-y-1.5"></div>
|
<div id="mpe-ing-list" class="space-y-1.5"></div>
|
||||||
<div id="mpe-add-area" class="mt-2"></div>
|
<div id="mpe-add-area" class="mt-2"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mpe-nutrition-section"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="shrink-0 px-5 pb-8 pt-3 border-t border-gray-100">
|
<div class="shrink-0 px-5 pb-8 pt-3 border-t border-gray-100">
|
||||||
<button id="mpe-confirm-btn" type="button" class="w-full bg-gray-900 hover:bg-black text-white py-3 rounded-xl font-semibold text-[13px] transition-colors flex items-center justify-center gap-2">
|
<button id="mpe-confirm-btn" type="button" class="w-full bg-gray-900 hover:bg-black text-white py-3 rounded-xl font-semibold text-[13px] transition-colors flex items-center justify-center gap-2">
|
||||||
@@ -230,9 +230,13 @@ export function setupMealPlanEditor() {
|
|||||||
if (!r) return;
|
if (!r) return;
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
|
const removeBtn = (cls, attrs) =>
|
||||||
|
`<button type="button" class="${cls} shrink-0 w-6 h-6 rounded-full border border-gray-200 text-gray-300 hover:text-red-500 hover:border-red-200 hover:bg-red-50 flex items-center justify-center transition-colors" ${attrs}><i class="fas fa-minus text-[8px]"></i></button>`;
|
||||||
|
|
||||||
for (const ing of r.ingredients) {
|
for (const ing of r.ingredients) {
|
||||||
const id = ing.ingredientId;
|
const id = ing.ingredientId;
|
||||||
const excl = S.excluded.has(id);
|
if (S.excluded.has(id)) continue;
|
||||||
|
|
||||||
const eid = S.subs[id] || id;
|
const eid = S.subs[id] || id;
|
||||||
const eDef = INGREDIENTS[eid];
|
const eDef = INGREDIENTS[eid];
|
||||||
const eName = eDef?.name || eid;
|
const eName = eDef?.name || eid;
|
||||||
@@ -243,34 +247,26 @@ export function setupMealPlanEditor() {
|
|||||||
const disp = base * S.servings;
|
const disp = base * S.servings;
|
||||||
const modified = id in S.overrides;
|
const modified = id in S.overrides;
|
||||||
|
|
||||||
const checkCls = excl
|
const rowBorder = swapped ? 'border-amber-200' : 'border-gray-200';
|
||||||
? 'w-5 h-5 rounded-md border-2 border-gray-300 bg-white'
|
const rowBg = swapped ? 'bg-amber-50/30' : 'bg-white';
|
||||||
: 'w-5 h-5 rounded-md border-2 border-gray-900 bg-gray-900';
|
|
||||||
const checkIco = excl ? '' : '<i class="fas fa-check text-white text-[8px]"></i>';
|
|
||||||
const rowBorder = excl ? 'border-gray-100' : swapped ? 'border-amber-200' : 'border-gray-200';
|
|
||||||
const rowBg = excl ? 'bg-gray-50/50' : swapped ? 'bg-amber-50/30' : 'bg-white';
|
|
||||||
const rowOp = excl ? 'opacity-50' : '';
|
|
||||||
const nameCls = excl ? 'text-[12px] font-semibold text-gray-400 line-through' : 'text-[12px] font-semibold text-gray-900';
|
|
||||||
const amtCls = excl ? 'text-gray-300' : 'text-gray-900';
|
|
||||||
const unitCls = excl ? 'text-gray-300' : 'text-gray-500';
|
|
||||||
|
|
||||||
const shuffleBtn = hasAlts && !excl
|
const shuffleBtn = hasAlts
|
||||||
? `<button type="button" class="mpe-shuffle shrink-0 w-5 h-5 rounded-full ${swapped ? 'bg-amber-50 text-amber-500' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'} flex items-center justify-center transition-colors" data-orig-id="${esc(id)}"><i class="fas fa-shuffle text-[8px]"></i></button>`
|
? `<button type="button" class="mpe-shuffle shrink-0 w-5 h-5 rounded-full ${swapped ? 'bg-amber-50 text-amber-500' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'} flex items-center justify-center transition-colors" data-orig-id="${esc(id)}"><i class="fas fa-shuffle text-[8px]"></i></button>`
|
||||||
: '';
|
: '';
|
||||||
const modDot = modified && !excl ? '<span class="w-1.5 h-1.5 rounded-full bg-amber-400 shrink-0"></span>' : '';
|
const modDot = modified ? '<span class="w-1.5 h-1.5 rounded-full bg-amber-400 shrink-0"></span>' : '';
|
||||||
|
|
||||||
html += `<div class="mpe-ing-row rounded-xl border ${rowBorder} ${rowBg} ${rowOp} p-2.5" data-orig-id="${esc(id)}" data-type="recipe">`;
|
html += `<div class="mpe-ing-row rounded-xl border ${rowBorder} ${rowBg} p-2.5" data-orig-id="${esc(id)}" data-type="recipe">`;
|
||||||
html += `<div class="flex items-center gap-2.5">`;
|
html += `<div class="flex items-center gap-2">`;
|
||||||
html += `<button type="button" class="mpe-toggle ${checkCls} flex items-center justify-center shrink-0" data-orig-id="${esc(id)}">${checkIco}</button>`;
|
html += `<div class="flex-1 min-w-0 flex items-center gap-1.5"><span class="text-[12px] font-semibold text-gray-900 truncate">${esc(eName)}</span>${shuffleBtn}</div>`;
|
||||||
html += `<div class="flex-1 min-w-0 flex items-center gap-1.5"><span class="${nameCls} truncate">${esc(eName)}</span>${shuffleBtn}</div>`;
|
html += `<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-orig-id="${esc(id)}" data-type="recipe">`;
|
||||||
html += `<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg ${excl ? '' : 'hover:bg-gray-100'} transition-colors" data-orig-id="${esc(id)}" data-type="recipe" ${excl ? 'disabled' : ''}>`;
|
html += `${modDot}<span class="text-[12px] font-semibold text-gray-900 tabular-nums">${fmtAmt(disp)}</span>`;
|
||||||
html += `${modDot}<span class="text-[12px] font-semibold ${amtCls} tabular-nums">${fmtAmt(disp)}</span>`;
|
html += `<span class="text-[11px] text-gray-500">${esc(ing.unit)}</span></button>`;
|
||||||
html += `<span class="text-[11px] ${unitCls}">${esc(ing.unit)}</span></button>`;
|
html += removeBtn('mpe-remove-ing', `data-orig-id="${esc(id)}" data-type="recipe"`);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|
||||||
if (hasAlts && altOpen && !excl) {
|
if (hasAlts && altOpen) {
|
||||||
const opts = [id, ...ing.alternatives];
|
const opts = [id, ...ing.alternatives];
|
||||||
html += '<div class="mt-2 ml-7 space-y-1">';
|
html += '<div class="mt-2 ml-1 space-y-1">';
|
||||||
for (const altId of opts) {
|
for (const altId of opts) {
|
||||||
const def = INGREDIENTS[altId];
|
const def = INGREDIENTS[altId];
|
||||||
const name = def?.name || altId;
|
const name = def?.name || altId;
|
||||||
@@ -293,17 +289,23 @@ export function setupMealPlanEditor() {
|
|||||||
const def = INGREDIENTS[a.ingredientId];
|
const def = INGREDIENTS[a.ingredientId];
|
||||||
const name = def?.name || a.ingredientId;
|
const name = def?.name || a.ingredientId;
|
||||||
const disp = a.amount * S.servings;
|
const disp = a.amount * S.servings;
|
||||||
html += `<div class="mpe-ing-row rounded-xl border border-emerald-200 bg-emerald-50/30 p-2.5" data-ing-id="${esc(a.ingredientId)}" data-type="added">
|
html += `<div class="mpe-ing-row rounded-xl border border-dashed border-gray-300 bg-white p-2.5" data-ing-id="${esc(a.ingredientId)}" data-type="added">`;
|
||||||
<div class="flex items-center gap-2.5">
|
html += `<div class="flex items-center gap-2">`;
|
||||||
<span class="shrink-0 w-5 h-5 rounded-md bg-emerald-100 flex items-center justify-center text-emerald-600"><i class="fas fa-plus text-[8px]"></i></span>
|
html += `<div class="flex-1 min-w-0 flex items-center gap-1.5"><span class="text-[12px] font-semibold text-gray-900 truncate">${esc(name)}</span><span class="text-[9px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-400 font-medium shrink-0">Dodany</span></div>`;
|
||||||
<span class="flex-1 min-w-0 text-[12px] font-semibold text-gray-900 truncate">${esc(name)}</span>
|
html += `<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-ing-id="${esc(a.ingredientId)}" data-type="added">`;
|
||||||
<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-ing-id="${esc(a.ingredientId)}" data-type="added">
|
html += `<span class="text-[12px] font-semibold text-gray-900 tabular-nums">${fmtAmt(disp)}</span>`;
|
||||||
<span class="text-[12px] font-semibold text-gray-900 tabular-nums">${fmtAmt(disp)}</span>
|
html += `<span class="text-[11px] text-gray-500">${esc(a.unit)}</span></button>`;
|
||||||
<span class="text-[11px] text-gray-500">${esc(a.unit)}</span>
|
html += removeBtn('mpe-remove-ing', `data-ing-id="${esc(a.ingredientId)}" data-type="added"`);
|
||||||
</button>
|
html += `</div></div>`;
|
||||||
<button type="button" class="mpe-remove-added shrink-0 w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-red-600 hover:border-red-200 hover:bg-red-50 flex items-center justify-center transition-colors" data-ing-id="${esc(a.ingredientId)}"><i class="fas fa-times text-[8px]"></i></button>
|
}
|
||||||
</div>
|
|
||||||
</div>`;
|
if (S.excluded.size > 0) {
|
||||||
|
const cnt = S.excluded.size;
|
||||||
|
const label = cnt === 1 ? '1 składnik usunięty' : cnt < 5 ? `${cnt} składniki usunięte` : `${cnt} składników usuniętych`;
|
||||||
|
html += `<div class="flex items-center justify-between py-2 px-1">`;
|
||||||
|
html += `<span class="text-[11px] text-gray-400">${label}</span>`;
|
||||||
|
html += `<button type="button" id="mpe-restore-all" class="text-[11px] font-semibold text-gray-500 hover:text-gray-900 transition-colors">Przywróć</button>`;
|
||||||
|
html += `</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.innerHTML = html;
|
list.innerHTML = html;
|
||||||
@@ -515,10 +517,19 @@ export function setupMealPlanEditor() {
|
|||||||
const ingSec = document.getElementById('mpe-ing-section');
|
const ingSec = document.getElementById('mpe-ing-section');
|
||||||
|
|
||||||
ingSec?.addEventListener('click', (e) => {
|
ingSec?.addEventListener('click', (e) => {
|
||||||
const toggle = e.target.closest('.mpe-toggle');
|
const remove = e.target.closest('.mpe-remove-ing');
|
||||||
if (toggle) {
|
if (remove) {
|
||||||
const id = toggle.dataset.origId;
|
if (remove.dataset.type === 'added') {
|
||||||
if (S.excluded.has(id)) S.excluded.delete(id); else S.excluded.add(id);
|
S.added = S.added.filter((a) => a.ingredientId !== remove.dataset.ingId);
|
||||||
|
} else {
|
||||||
|
S.excluded.add(remove.dataset.origId);
|
||||||
|
}
|
||||||
|
renderIngredients(); renderNutrition();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.closest('#mpe-restore-all')) {
|
||||||
|
S.excluded.clear();
|
||||||
renderIngredients(); renderNutrition();
|
renderIngredients(); renderNutrition();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -547,13 +558,6 @@ export function setupMealPlanEditor() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeAdded = e.target.closest('.mpe-remove-added');
|
|
||||||
if (removeAdded) {
|
|
||||||
S.added = S.added.filter((a) => a.ingredientId !== removeAdded.dataset.ingId);
|
|
||||||
renderIngredients(); renderNutrition();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.target.closest('#mpe-add-btn')) {
|
if (e.target.closest('#mpe-add-btn')) {
|
||||||
S.addOpen = true; S.addQuery = '';
|
S.addOpen = true; S.addQuery = '';
|
||||||
renderAddArea();
|
renderAddArea();
|
||||||
|
|||||||
Reference in New Issue
Block a user