- rename `supplier` field to `suppliers` in OrderItem interface and PocketBase expand queries - complete visual rebrand to warm cream (#f2f1ed) and dark brown (#26251e) color palette with custom CSS variables - add accessibility improvements: skip-to-main content link, focus-visible styles, reduced motion support, and proper ARIA labels - update all component styling to match new theme, including offline indicator, install prompt, login page, modals, map, bottom nav and global CSS - standardize status colors: use green for success, red for errors, retain orange (#f54e00) as primary accent CTAs
154 lines
No EOL
4.9 KiB
TypeScript
154 lines
No EOL
4.9 KiB
TypeScript
import PocketBase from 'pocketbase';
|
|
import { AppSettings, DEFAULT_SETTINGS, OrderItem, DriverLocation } from './types';
|
|
|
|
const POCKETBASE_URL = process.env.NEXT_PUBLIC_POCKETBASE_URL || 'https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io';
|
|
|
|
export const pb = new PocketBase(POCKETBASE_URL);
|
|
|
|
// Disable auto-cancellation so React re-renders don't kill in-flight requests
|
|
pb.autoCancellation(false);
|
|
|
|
// Auth helpers
|
|
export const isAuthenticated = () => pb.authStore.isValid;
|
|
|
|
export const getCurrentUser = () => pb.authStore.model;
|
|
|
|
export const logout = () => {
|
|
pb.authStore.clear();
|
|
if (typeof window !== 'undefined') {
|
|
window.location.href = '/login/';
|
|
}
|
|
};
|
|
|
|
export const login = async (email: string, password: string) => {
|
|
return await pb.collection('users').authWithPassword(email, password);
|
|
};
|
|
|
|
// Get assigned collections (order_items assigned to current driver).
|
|
// The `order_items` schema has `supplier` (singular) and `driver`
|
|
// (singular) as relation field names — those are the keys to use
|
|
// in expand / sort / filter.
|
|
export const getAssignedCollections = async () => {
|
|
const user = getCurrentUser();
|
|
if (!user) return [];
|
|
|
|
// 1) Happy path — rich expand + sort by supplier name.
|
|
try {
|
|
const records = await pb.collection('order_items').getFullList<OrderItem>({
|
|
filter: `driver = "${user.id}" && (item_status = "assigned" || item_status = "driver_pool")`,
|
|
expand: 'supplier,order,purchase_order',
|
|
sort: 'supplier.suppliername',
|
|
});
|
|
return records as unknown as OrderItem[];
|
|
} catch (err: any) {
|
|
if (typeof console !== 'undefined') {
|
|
console.warn('[pocketbase] expand=supplier/sort failed, retrying without it:', err?.message || err);
|
|
}
|
|
}
|
|
|
|
// 2) Resilient fallback — items only, then batched suppliers.
|
|
const items = await pb.collection('order_items').getFullList<OrderItem>({
|
|
filter: `driver = "${user.id}" && (item_status = "assigned" || item_status = "driver_pool")`,
|
|
});
|
|
|
|
const supplierIds = Array.from(new Set(
|
|
items
|
|
.map((it) => (it as any).supplier)
|
|
.filter((id): id is string => typeof id === 'string' && id.length > 0)
|
|
));
|
|
|
|
let supplierMap = new Map<string, any>();
|
|
if (supplierIds.length > 0) {
|
|
try {
|
|
const filter = supplierIds.map((id) => `id = "${id}"`).join(' || ');
|
|
const suppliers = await pb.collection('suppliers').getFullList({ filter });
|
|
supplierMap = new Map(suppliers.map((s: any) => [s.id, s]));
|
|
} catch {
|
|
// Suppliers collection not reachable.
|
|
}
|
|
}
|
|
|
|
return items.map((it) => {
|
|
const supplierId = (it as any).supplier;
|
|
const supplier = supplierId ? supplierMap.get(supplierId) : null;
|
|
if (!supplier) return it;
|
|
return {
|
|
...it,
|
|
expand: { ...(it.expand || {}), supplier },
|
|
} as unknown as OrderItem;
|
|
});
|
|
};
|
|
|
|
// Update order item status when collected
|
|
export const updateCollectionStatus = async (
|
|
id: string,
|
|
status: string,
|
|
data: { qty_collected?: number; notes?: string; collected_date?: string; problem_type?: string; problem_flag?: boolean }
|
|
) => {
|
|
return await pb.collection('order_items').update(id, {
|
|
item_status: status,
|
|
...data,
|
|
});
|
|
};
|
|
|
|
// Report a problem on an order item
|
|
export const reportProblem = async (
|
|
orderItemId: string,
|
|
problemType: string,
|
|
description: string
|
|
) => {
|
|
return await pb.collection('order_items').update(orderItemId, {
|
|
item_status: 'problem',
|
|
problem_type: problemType,
|
|
problem_flag: true,
|
|
problem_notes: description,
|
|
});
|
|
};
|
|
|
|
// Location tracking - tries to save, fails silently if table doesn't exist
|
|
export const saveDriverLocation = async (location: Omit<DriverLocation, 'id'>) => {
|
|
try {
|
|
return await pb.collection('driver_locations').create(location);
|
|
} catch {
|
|
// Silently fail - table may not exist yet
|
|
}
|
|
};
|
|
|
|
// Get driver's location trail for today (for map polyline)
|
|
export const getDriverTrail = async (driverId: string) => {
|
|
try {
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
const records = await pb.collection('driver_locations').getFullList({
|
|
filter: `driver = "${driverId}" && timestamp >= "${today.toISOString()}"`,
|
|
sort: 'timestamp',
|
|
});
|
|
return records as unknown as DriverLocation[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// Settings helpers
|
|
const SETTINGS_KEY = '1driver_settings';
|
|
|
|
export const getSettings = (): AppSettings => {
|
|
if (typeof window === 'undefined') return DEFAULT_SETTINGS;
|
|
const saved = localStorage.getItem(SETTINGS_KEY);
|
|
if (saved) {
|
|
try {
|
|
return { ...DEFAULT_SETTINGS, ...JSON.parse(saved) };
|
|
} catch {
|
|
return DEFAULT_SETTINGS;
|
|
}
|
|
}
|
|
return DEFAULT_SETTINGS;
|
|
};
|
|
|
|
export const saveSettings = (settings: Partial<AppSettings>) => {
|
|
if (typeof window === 'undefined') return;
|
|
const current = getSettings();
|
|
const updated = { ...current, ...settings };
|
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(updated));
|
|
return updated;
|
|
}; |