diff --git a/client/src/api.js b/client/src/api.js
index 11cb9db70..1d374309f 100644
--- a/client/src/api.js
+++ b/client/src/api.js
@@ -7,6 +7,7 @@ export const api = {
const params = new URLSearchParams()
if (filters.warehouse && filters.warehouse !== 'all') params.append('warehouse', filters.warehouse)
if (filters.category && filters.category !== 'all') params.append('category', filters.category)
+ if (filters.stock_status && filters.stock_status !== 'all') params.append('stock_status', filters.stock_status)
const response = await axios.get(`${API_BASE_URL}/inventory?${params.toString()}`)
return response.data
diff --git a/client/src/locales/en.js b/client/src/locales/en.js
index 03a58fe6e..346590003 100644
--- a/client/src/locales/en.js
+++ b/client/src/locales/en.js
@@ -80,6 +80,9 @@ export default {
skus: 'SKUs',
searchPlaceholder: 'Search by item name...',
clearSearch: 'Clear search',
+ stockStatusFilter: 'Stock Status',
+ stockStatusAll: 'All',
+ stockStatusOutOfStock: 'Out of Stock',
totalItems: 'Total Items',
totalValue: 'Total Value',
lowStockItems: 'Low Stock Items',
diff --git a/client/src/locales/ja.js b/client/src/locales/ja.js
index db33223ac..e24c0b0a1 100644
--- a/client/src/locales/ja.js
+++ b/client/src/locales/ja.js
@@ -80,6 +80,9 @@ export default {
skus: 'SKU',
searchPlaceholder: '品目名で検索...',
clearSearch: '検索をクリア',
+ stockStatusFilter: '在庫状況',
+ stockStatusAll: 'すべて',
+ stockStatusOutOfStock: '在庫切れ',
totalItems: '総品目数',
totalValue: '総価値',
lowStockItems: '在庫僅少品目',
diff --git a/client/src/views/Inventory.vue b/client/src/views/Inventory.vue
index 4819326eb..0a70e14f2 100644
--- a/client/src/views/Inventory.vue
+++ b/client/src/views/Inventory.vue
@@ -32,6 +32,17 @@
+
+
+
+
@@ -106,6 +117,10 @@ export default {
const error = ref(null)
const items = ref([])
const searchQuery = ref('')
+ // Local filter for stock status - scoped to this component only.
+ // The global useFilters `selectedStatus` is for Order Status; inventory
+ // intentionally does not use it (see comment below in loadInventory).
+ const stockStatusFilter = ref('all')
// Modal state
const showItemModal = ref(false)
@@ -156,7 +171,8 @@ export default {
// Inventory doesn't support month/status filters, only warehouse and category
items.value = await api.getInventory({
warehouse: filters.warehouse,
- category: filters.category
+ category: filters.category,
+ stock_status: stockStatusFilter.value
})
} catch (err) {
error.value = 'Failed to load inventory: ' + err.message
@@ -166,7 +182,7 @@ export default {
}
// Watch for filter changes and reload data
- watch([selectedLocation, selectedCategory], () => {
+ watch([selectedLocation, selectedCategory, stockStatusFilter], () => {
loadInventory()
})
@@ -209,6 +225,7 @@ export default {
error,
items,
searchQuery,
+ stockStatusFilter,
filteredItems,
getStockStatus,
getStockStatusClass,
@@ -317,6 +334,41 @@ export default {
height: 18px;
}
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+.stock-status-filter {
+ display: flex;
+ align-items: center;
+}
+
+.stock-status-select {
+ padding: 0.5rem 0.75rem;
+ border: 1px solid #cbd5e1;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ color: #0f172a;
+ background: #f8fafc;
+ transition: all 0.2s;
+ cursor: pointer;
+}
+
+.stock-status-select:focus {
+ outline: none;
+ border-color: #3b82f6;
+ background: white;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
.loading,
.error {
padding: 2rem;
diff --git a/server/data/inventory.json b/server/data/inventory.json
index 486fda819..9deeda74f 100644
--- a/server/data/inventory.json
+++ b/server/data/inventory.json
@@ -113,7 +113,7 @@
"name": "Standard Servo Motor",
"category": "Actuators",
"warehouse": "Tokyo",
- "quantity_on_hand": 28,
+ "quantity_on_hand": 0,
"reorder_point": 30,
"unit_cost": 725.0,
"location": "Warehouse C-03",
diff --git a/server/main.py b/server/main.py
index a0c2d8c5a..efad7d80a 100644
--- a/server/main.py
+++ b/server/main.py
@@ -46,6 +46,16 @@ def apply_filters(items: list, warehouse: Optional[str] = None, category: Option
return filtered
+def filter_by_stock_status(items: list, stock_status: Optional[str] = None) -> list:
+ """Filter inventory items by stock status derived from quantity_on_hand"""
+ if not stock_status or stock_status == 'all':
+ return items
+
+ if stock_status == 'out_of_stock':
+ return [item for item in items if item.get('quantity_on_hand', 0) == 0]
+
+ return items
+
# CORS middleware
app.add_middleware(
CORSMiddleware,
@@ -128,10 +138,12 @@ def root():
@app.get("/api/inventory", response_model=List[InventoryItem])
def get_inventory(
warehouse: Optional[str] = None,
- category: Optional[str] = None
+ category: Optional[str] = None,
+ stock_status: Optional[str] = None
):
"""Get all inventory items with optional filtering"""
- return apply_filters(inventory_items, warehouse, category)
+ filtered = apply_filters(inventory_items, warehouse, category)
+ return filter_by_stock_status(filtered, stock_status)
@app.get("/api/inventory/{item_id}", response_model=InventoryItem)
def get_inventory_item(item_id: str):