File size: 11,197 Bytes
5ae7c8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// State Management
const state = {
    inventory: [],
    stats: {
        totalItems: 0,
        expiringSoon: 0,
        wasteAvoided: 0,
        moneySaved: 0
    },
    useMock: true
};

// Mock Data (Fallback)
const MOCK_INVENTORY = [
    { id: 1, name: 'Milk (Mock)', quantity: '1L', expiryDate: '2023-10-30', category: 'Dairy', price: 2.50 },
    { id: 2, name: 'Spinach (Mock)', quantity: '1 bag', expiryDate: '2023-10-26', category: 'Vegetables', price: 3.00 },
];

const MOCK_RECIPES = [
    { title: 'Mock Chicken Stir-fry', image: 'https://via.placeholder.com/300', ingredients: 'Chicken, Veggies' }
];

// Initialization
// Initialization
document.addEventListener('DOMContentLoaded', () => {
    // Check if backend is reachable or just load UI
    state.useMock = false; // We assume Python backend is primary now
    fetchInventory();
    setupFormListeners();

    // Hide N8N settings if present (optional UX polish)
    const settingsSection = document.getElementById('settings-section');
    if (settingsSection) {
        // We can inject a message or hide legacy N8N inputs here if we wanted
        // For now, we just leave it as is
    }
});

// Navigation
function showSection(sectionId) {
    const sections = ['dashboard', 'inventory', 'recipes', 'settings'];
    sections.forEach(id => document.getElementById(`${id}-section`).classList.add('hidden'));
    document.getElementById(`${sectionId}-section`).classList.remove('hidden');
    document.querySelectorAll('nav button').forEach(btn => btn.classList.remove('active'));

    if (sectionId === 'dashboard' || sectionId === 'inventory') {
        if (!state.useMock) fetchInventory();
        else updateUI();
    }
}

// Data Handling - Python API
async function apiRequest(endpoint, method = 'GET', data = null) {
    // If endpoint doesn't start with /, add it
    const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
    const url = `/api${path}`;

    const options = {
        method: method,
        headers: { 'Content-Type': 'application/json' }
    };

    if (data) {
        options.body = JSON.stringify(data);
    }

    const response = await fetch(url, options);
    if (!response.ok) {
        throw new Error(`Server Error: ${response.status}`);
    }
    return await response.json();
}

async function fetchInventory() {
    if (state.useMock) { updateUI(); return; } // Keep mock fallback for initial load if no URL was saved

    try {
        const data = await apiRequest('/get-items');
        state.inventory = data.items || [];
        updateUI();
    } catch (error) {
        console.error('API Error:', error);
        showToast('Backend not running? ' + error.message, 'error');
        // Fallback to mock data if API fails
        if (state.inventory.length === 0) {
            state.inventory = MOCK_INVENTORY;
            state.useMock = true; // Switch to mock mode
            updateUI();
        }
    }
}

async function addItem(itemData) {
    if (state.useMock) {
        state.inventory.push({ ...itemData, id: Date.now(), expiryDate: itemData.expiryDate || '2023-12-01' }); // Add ID for mock
        updateUI();
        showToast('Item added (Mock)!', 'success');
        return;
    }

    try {
        const result = await apiRequest('/add-item', 'POST', itemData);
        if (result.success) {
            showToast(`Item added! Expiry: ${result.expiryDate}`, 'success');
            fetchInventory();
        } else {
            showToast('Failed to add item: ' + (result.message || 'Unknown error'), 'error');
        }
    } catch (error) {
        console.error('Add Item Error:', error);
        showToast('Failed to add item.', 'error');
    }
}

async function getRecipeSuggestions() {
    const container = document.getElementById('recipes-container');
    container.innerHTML = '<div class="loader"></div>';

    if (state.useMock) {
        await new Promise(r => setTimeout(r, 1000));
        renderRecipes(MOCK_RECIPES);
        return;
    }

    try {
        const recipes = await apiRequest('/suggest-recipes', 'POST', {});
        renderRecipes(recipes);
    } catch (e) {
        console.error(e);
        container.innerHTML = '<p class="text-muted">Error fetching recipes. Showing mock recipes.</p>';
        setTimeout(() => renderRecipes(MOCK_RECIPES), 1000); // Fallback to mock recipes
    }
}

// Settings & Testing
function saveSettings() {
    const url = document.getElementById('config-webhook-url').value;
    updateConfig(url);
    showToast('Settings Saved! Reloading...', 'success');
    setTimeout(() => window.location.reload(), 1000);
}

async function testConnection() {
    const log = document.getElementById('connection-log');
    log.style.display = 'block';
    log.innerHTML = 'Testing connection...';

    // Explicitly testing 'get_items' action which matches the Switch node logic
    const payload = { action: 'get_items' };

    const url = document.getElementById('config-webhook-url').value;

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (response.ok) {
            const data = await response.json();
            log.innerHTML = `<span style="color: #10b981;">SUCCESS: Connected!</span><br>Response: ${JSON.stringify(data).slice(0, 50)}...`;
        } else {
            log.innerHTML = `<span style="color: #ef4444;">ERROR: Server returned ${response.status}</span>`;
        }
    } catch (e) {
        log.innerHTML = `<span style="color: #ef4444;">FAIL: ${e.name} - ${e.message}</span><br>Possible causes: CORS, Network, or Wrong URL.`;
    }
}

// UI Helpers
function showToast(msg, type = 'info') {
    // Simple alert replacement
    const div = document.createElement('div');
    div.innerText = msg;
    div.style.position = 'fixed';
    div.style.bottom = '20px';
    div.style.right = '20px';
    div.style.background = type === 'error' ? '#ef4444' : '#10b981';
    div.style.color = 'white';
    div.style.padding = '12px 24px';
    div.style.borderRadius = '8px';
    div.style.zIndex = '1000';
    div.style.animation = 'fadeInUp 0.3s ease';
    document.body.appendChild(div);
    setTimeout(() => div.remove(), 3000);
}

function renderRecipes(recipes) {
    const container = document.getElementById('recipes-container');
    container.innerHTML = '';
    if (!recipes || recipes.length === 0) {
        container.innerHTML = '<p class="text-muted">No recipes found.</p>';
        return;
    }
    recipes.forEach(recipe => {
        const div = document.createElement('div');
        div.className = 'recipe-card';
        div.innerHTML = `

            <img src="${recipe.image || 'https://via.placeholder.com/300'}" alt="${recipe.title}">

            <div class="recipe-content">

                <div class="recipe-title">${recipe.title}</div>

                <div class="recipe-ingredients">Ingredients: ${recipe.ingredients || 'Various'}</div>

            </div>

        `;
        container.appendChild(div);
    });
}

function calculateStats() {
    const today = new Date();
    const upcoming = new Date();
    upcoming.setDate(today.getDate() + 3);

    let expiringSoonCount = 0;

    state.inventory.forEach(item => {
        const expiry = new Date(item.expiryDate);
        if (expiry <= upcoming && expiry >= today) {
            expiringSoonCount++;
        }
    });

    state.stats.totalItems = state.inventory.length;
    state.stats.expiringSoon = expiringSoonCount;
    state.stats.wasteAvoided = (state.inventory.length * 0.1).toFixed(1) + 'kg';
    state.stats.moneySaved = '$' + (state.inventory.length * 2.5).toFixed(2);
}

function updateUI() {
    calculateStats();
    document.getElementById('total-items').innerText = state.stats.totalItems;
    document.getElementById('expiring-soon').innerText = state.stats.expiringSoon;
    document.getElementById('waste-avoided').innerText = state.stats.wasteAvoided;
    document.getElementById('money-saved').innerText = state.stats.moneySaved;

    const list = document.getElementById('inventory-list-ul');
    list.innerHTML = '';

    state.inventory.forEach(item => {
        const li = document.createElement('li');
        li.className = `inventory-item ${getExpiryClass(item.expiryDate)}`;
        li.innerHTML = `

            <div class="item-info">

                <h4>${item.name}</h4>

                <div class="item-meta"><span>${item.quantity || '-'}</span> • <span>${item.category || 'Other'}</span></div>

            </div>

            <div class="item-status">

                <span class="expiry-badge ${getExpiryClass(item.expiryDate)}">${formatDate(item.expiryDate)}</span>

            </div>

        `;
        list.appendChild(li);
    });

    generateDashboardAlerts();
}

function getExpiryClass(dateStr) {
    if (!dateStr) return 'safe';
    const today = new Date();
    const expiry = new Date(dateStr);
    const diffDays = Math.ceil((expiry - today) / (1000 * 60 * 60 * 24));
    if (diffDays < 0) return 'expired';
    if (diffDays <= 3) return 'soon';
    return 'safe';
}

function formatDate(dateStr) {
    if (!dateStr) return 'Unknown';
    return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}

function generateDashboardAlerts() {
    const container = document.getElementById('alerts-container');
    container.innerHTML = '';
    const expiringItems = state.inventory.filter(i => getExpiryClass(i.expiryDate) === 'soon');

    if (expiringItems.length === 0) {
        container.innerHTML = '<p class="text-muted">No urgent alerts. Good job!</p>';
        return;
    }
    expiringItems.forEach(item => {
        const div = document.createElement('div');
        div.style.marginBottom = '10px';
        div.style.padding = '10px';
        div.style.background = 'rgba(245, 158, 11, 0.1)';
        div.style.borderRadius = '8px';
        div.style.borderLeft = '3px solid var(--accent)';
        div.innerHTML = `<strong>${item.name}</strong> is expiring soon!`;
        container.appendChild(div);
    });
}

// Event Listeners
function setupFormListeners() {
    document.getElementById('quick-add-form').addEventListener('submit', (e) => {
        e.preventDefault();
        const name = document.getElementById('quick-name').value;
        const date = document.getElementById('quick-date').value;
        addItem({ name, quantity: '1 unit', expiryDate: date, category: 'Uncategorized' });
        document.getElementById('quick-add-form').reset();
    });

    document.getElementById('add-item-form').addEventListener('submit', (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        addItem(Object.fromEntries(formData.entries()));
        e.target.reset();
        showSection('inventory');
    });
}