Infrastructure: Implement master/dev/vX branching, save git path, and fix username casing
This commit is contained in:
110
frontend/lib/api.ts
Normal file
110
frontend/lib/api.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const getBackendUrl = () => {
|
||||
if (typeof window === 'undefined') return 'http://localhost:8000';
|
||||
|
||||
const host = window.location.hostname;
|
||||
|
||||
// If we are on HTTPS (Proxy/Mobile mode), we use port 3002 for the backend
|
||||
if (window.location.protocol === 'https:') {
|
||||
if (host.includes('.loca.lt')) {
|
||||
return 'https://inventory-ai-api.loca.lt';
|
||||
}
|
||||
return `https://${host}:3002`;
|
||||
}
|
||||
|
||||
return `http://${host}:8000`;
|
||||
};
|
||||
|
||||
export const inventoryApi = {
|
||||
getItems: async () => {
|
||||
const res = await axios.get(`${getBackendUrl()}/items/`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
syncBulkOperations: async (userId: number, operations: any[]) => {
|
||||
const url = `${getBackendUrl()}/operations/bulk-sync`;
|
||||
try {
|
||||
const res = await axios.post(url, {
|
||||
user_id: userId,
|
||||
operations: operations
|
||||
});
|
||||
return res.data;
|
||||
} catch (err: any) {
|
||||
console.error("Sync API Error at:", url, err);
|
||||
if (err.response?.status === 404) {
|
||||
throw new Error(`404: Endpoint not found at ${url}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
analyzeLabel: async (formData: FormData) => {
|
||||
const res = await axios.post(`${getBackendUrl()}/items/extract-label`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
|
||||
createItem: async (userId: number, itemData: any) => {
|
||||
const res = await axios.post(`${getBackendUrl()}/items/`, itemData, {
|
||||
params: { user_id: userId }
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
|
||||
updateItem: async (itemId: number, itemData: any) => {
|
||||
const res = await axios.put(`${getBackendUrl()}/items/${itemId}`, itemData);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
adjustStock: async (endpoint: string, data: any) => {
|
||||
const res = await axios.post(`${getBackendUrl()}/operations/${endpoint}`, data);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
deleteItem: async (itemId: number) => {
|
||||
const res = await axios.delete(`${getBackendUrl()}/items/${itemId}`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
getAuditLogs: async (limit: number = 50) => {
|
||||
const res = await axios.get(`${getBackendUrl()}/operations/logs`, { params: { limit } });
|
||||
return res.data;
|
||||
},
|
||||
|
||||
// Users
|
||||
getUsers: async () => {
|
||||
const res = await axios.get(`${getBackendUrl()}/users/`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
createUser: async (userData: any) => {
|
||||
const res = await axios.post(`${getBackendUrl()}/users/`, userData);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
login: async (credentials: any) => {
|
||||
const res = await axios.post(`${getBackendUrl()}/users/login`, credentials);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
deleteUser: async (userId: number) => {
|
||||
const res = await axios.delete(`${getBackendUrl()}/users/${userId}`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
// Categories
|
||||
getCategories: async () => {
|
||||
const res = await axios.get(`${getBackendUrl()}/categories/`);
|
||||
return res.data;
|
||||
},
|
||||
createCategory: async (data: any) => {
|
||||
const res = await axios.post(`${getBackendUrl()}/categories/`, data);
|
||||
return res.data;
|
||||
},
|
||||
deleteCategory: async (id: number) => {
|
||||
const res = await axios.delete(`${getBackendUrl()}/categories/${id}`);
|
||||
return res.data;
|
||||
}
|
||||
};
|
||||
41
frontend/lib/db.ts
Normal file
41
frontend/lib/db.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import Dexie, { Table } from 'dexie';
|
||||
|
||||
export interface Item {
|
||||
id?: number;
|
||||
barcode: string;
|
||||
name: string;
|
||||
category: string;
|
||||
part_number?: string;
|
||||
color?: string;
|
||||
specs?: string;
|
||||
quantity: number;
|
||||
min_quantity: number;
|
||||
image_url?: string;
|
||||
labels_data?: string;
|
||||
}
|
||||
|
||||
export interface PendingOperation {
|
||||
id?: number;
|
||||
type: 'CHECK_IN' | 'CHECK_OUT' | 'TRASH';
|
||||
barcode: string;
|
||||
quantity: number;
|
||||
timestamp: number;
|
||||
synced: 0 | 1; // 0 for false, 1 for true
|
||||
uuid: string;
|
||||
details?: string;
|
||||
}
|
||||
|
||||
export class InventoryDatabase extends Dexie {
|
||||
items!: Table<Item>;
|
||||
pendingOperations!: Table<PendingOperation>;
|
||||
|
||||
constructor() {
|
||||
super('InventoryDatabase');
|
||||
this.version(3).stores({
|
||||
items: '++id, barcode, name, category, part_number, color',
|
||||
pendingOperations: '++id, barcode, timestamp, synced, uuid'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new InventoryDatabase();
|
||||
62
frontend/lib/sync.ts
Normal file
62
frontend/lib/sync.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { db, PendingOperation } from './db';
|
||||
import { inventoryApi } from './api';
|
||||
|
||||
export const syncOfflineOperations = async (userId: number) => {
|
||||
const pending = await db.pendingOperations
|
||||
.filter(op => op.synced === 0)
|
||||
.toArray();
|
||||
|
||||
if (pending.length === 0) return { success: 0, errors: 0 };
|
||||
|
||||
try {
|
||||
// Format operations for the backend (convert timestamp to ISO string if needed)
|
||||
const formattedOps = pending.map(op => ({
|
||||
type: op.type,
|
||||
barcode: op.barcode,
|
||||
quantity: op.quantity,
|
||||
timestamp: new Date(op.timestamp).toISOString(),
|
||||
uuid: op.uuid,
|
||||
details: op.details
|
||||
}));
|
||||
|
||||
const results = await inventoryApi.syncBulkOperations(userId, formattedOps);
|
||||
|
||||
// Mark successfully synced operations in IndexedDB
|
||||
// For simplicity, if the whole bulk call succeeds, we mark all as synced or delete them
|
||||
// Real implementation should match success/error per item
|
||||
|
||||
const successBarcodes = new Set(results.success.map((s: any) => s.barcode));
|
||||
|
||||
for (const op of pending) {
|
||||
if (successBarcodes.has(op.barcode)) {
|
||||
await db.pendingOperations.update(op.id!, { synced: 1 });
|
||||
// Or remove it: await db.pendingOperations.delete(op.id!);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up synced operations
|
||||
await db.pendingOperations.where('synced').equals(1).delete();
|
||||
|
||||
return {
|
||||
success: results.success.length,
|
||||
errors: results.errors.length,
|
||||
details: results
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Sync failed:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchAndCacheItems = async () => {
|
||||
try {
|
||||
const items = await inventoryApi.getItems();
|
||||
// Update local cache
|
||||
await db.items.clear();
|
||||
await db.items.bulkAdd(items);
|
||||
return items;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch items, using cache:', error);
|
||||
return await db.items.toArray();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user