From 7e95cf64c6c3eed6a5a6bede036410a461575264 Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Sat, 21 Mar 2026 16:43:31 -0400 Subject: [PATCH] Add inventory store with WebSocket and REST API integration --- web/client/src/store/inventoryStore.js | 210 +++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 web/client/src/store/inventoryStore.js diff --git a/web/client/src/store/inventoryStore.js b/web/client/src/store/inventoryStore.js new file mode 100644 index 0000000..0343e73 --- /dev/null +++ b/web/client/src/store/inventoryStore.js @@ -0,0 +1,210 @@ +import { create } from 'zustand'; + +const WS_URL = import.meta.env.VITE_WS_URL || + `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`; +const API_URL = import.meta.env.VITE_API_URL || + `${window.location.protocol}//${window.location.host}/api`; + +console.log('🔌 WebSocket URL:', WS_URL); +console.log('📡 API URL:', API_URL); + +export const useInventoryStore = create((set, get) => ({ + // State + inventory: { + itemList: [], + grandTotal: 0, + chestCount: 0, + totalSlots: 0, + usedSlots: 0, + freeSlots: 0, + usedRatio: 0, + dropperOk: false, + barrelOk: false, + furnaceCount: 0, + furnaceStatus: {}, + }, + activity: {}, + alerts: [], + smeltingPaused: false, + disabledRecipes: {}, + smeltable: {}, + craftable: [], + craftTurtleOk: false, + connected: false, + ws: null, + lastUpdate: 0, + searchQuery: '', + commandResult: null, + + // WebSocket connection + connect: () => { + const ws = new WebSocket(WS_URL); + + ws.onopen = () => { + console.log('✅ Connected to server'); + set({ connected: true, ws }); + }; + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + + if (data.type === 'initial_state') { + set({ + inventory: data.inventory || get().inventory, + activity: data.activity || {}, + alerts: data.alerts || [], + smeltingPaused: data.smeltingPaused || false, + disabledRecipes: data.disabledRecipes || {}, + smeltable: data.smeltable || {}, + craftable: data.craftable || [], + craftTurtleOk: data.craftTurtleOk || false, + lastUpdate: data.lastUpdate || Date.now(), + }); + } else if (data.type === 'state_update') { + set({ + inventory: data.inventory || get().inventory, + activity: data.activity || get().activity, + alerts: data.alerts || get().alerts, + smeltingPaused: data.smeltingPaused !== undefined ? data.smeltingPaused : get().smeltingPaused, + disabledRecipes: data.disabledRecipes || get().disabledRecipes, + craftTurtleOk: data.craftTurtleOk !== undefined ? data.craftTurtleOk : get().craftTurtleOk, + lastUpdate: data.lastUpdate || Date.now(), + }); + } else if (data.type === 'command_result') { + set({ commandResult: data }); + // Clear after 5s + setTimeout(() => { + set((state) => { + if (state.commandResult === data) { + return { commandResult: null }; + } + return {}; + }); + }, 5000); + } + } catch (error) { + console.error('Error processing message:', error); + } + }; + + ws.onclose = () => { + console.log('❌ Disconnected from server'); + set({ connected: false, ws: null }); + setTimeout(() => { + get().connect(); + }, 3000); + }; + + ws.onerror = (error) => { + console.error('WebSocket error:', error); + }; + }, + + // Search + setSearchQuery: (query) => set({ searchQuery: query }), + + getFilteredItems: () => { + const { inventory, searchQuery } = get(); + const items = inventory.itemList || []; + if (!searchQuery) return items; + const q = searchQuery.toLowerCase(); + return items.filter((item) => { + const name = (item.displayName || item.name || '').toLowerCase(); + return name.includes(q); + }); + }, + + // Actions via REST API + orderItem: async (itemName, amount, dropperName) => { + try { + const response = await fetch(`${API_URL}/order`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ itemName, amount, dropperName }), + }); + return await response.json(); + } catch (error) { + console.error('❌ Error ordering item:', error); + return { success: false, error: error.message }; + } + }, + + requestScan: async () => { + try { + const response = await fetch(`${API_URL}/scan`, { method: 'POST' }); + return await response.json(); + } catch (error) { + console.error('❌ Error requesting scan:', error); + return { success: false, error: error.message }; + } + }, + + toggleSmelting: async () => { + try { + const response = await fetch(`${API_URL}/smelting/toggle`, { method: 'POST' }); + return await response.json(); + } catch (error) { + console.error('❌ Error toggling smelting:', error); + return { success: false, error: error.message }; + } + }, + + toggleRecipe: async (recipe) => { + try { + const response = await fetch(`${API_URL}/recipes/toggle`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ recipe }), + }); + return await response.json(); + } catch (error) { + console.error('❌ Error toggling recipe:', error); + return { success: false, error: error.message }; + } + }, + + enableAllRecipes: async () => { + try { + await fetch(`${API_URL}/recipes/enable-all`, { method: 'POST' }); + } catch (error) { + console.error('❌ Error enabling all recipes:', error); + } + }, + + disableAllRecipes: async () => { + try { + await fetch(`${API_URL}/recipes/disable-all`, { method: 'POST' }); + } catch (error) { + console.error('❌ Error disabling all recipes:', error); + } + }, + + craftItem: async (recipeIdx) => { + try { + const response = await fetch(`${API_URL}/craft`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ recipeIdx }), + }); + return await response.json(); + } catch (error) { + console.error('❌ Error crafting item:', error); + return { success: false, error: error.message }; + } + }, + + sortBarrel: async (barrelName) => { + try { + const response = await fetch(`${API_URL}/sort-barrel`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ barrelName }), + }); + return await response.json(); + } catch (error) { + console.error('❌ Error sorting barrel:', error); + return { success: false, error: error.message }; + } + }, +}));