mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-05 21:51:23 +02:00
feat(spoolman-inventory): squashed feature work for rebase onto dev Squashed all commits from feature/spoolman-inventory-ui onto a single commit to enable a clean rebase onto dev. Original per-commit history preserved at backup tag backup/spoolman-inventory-ui-prerebase-20260507-105721.
202 lines
6.0 KiB
TypeScript
202 lines
6.0 KiB
TypeScript
import { useEffect, useReducer, useCallback } from 'react';
|
|
|
|
export interface MatchedSpool {
|
|
id: number;
|
|
tag_uid: string;
|
|
material: string;
|
|
subtype: string | null;
|
|
color_name: string | null;
|
|
rgba: string | null;
|
|
brand: string | null;
|
|
label_weight: number;
|
|
core_weight: number;
|
|
weight_used: number;
|
|
}
|
|
|
|
export interface SpoolBuddyState {
|
|
weight: number | null;
|
|
weightStable: boolean;
|
|
rawAdc: number | null;
|
|
matchedSpool: MatchedSpool | null;
|
|
unknownTagUid: string | null;
|
|
unknownTrayUuid: string | null;
|
|
deviceOnline: boolean;
|
|
deviceId: string | null;
|
|
}
|
|
|
|
type Action =
|
|
| { type: 'WEIGHT_UPDATE'; weight: number; stable: boolean; rawAdc: number; deviceId: string }
|
|
| { type: 'TAG_MATCHED'; spool: MatchedSpool; deviceId: string }
|
|
| { type: 'UNKNOWN_TAG'; tagUid: string; trayUuid: string | null; deviceId: string }
|
|
| { type: 'TAG_REMOVED'; deviceId: string }
|
|
| { type: 'DEVICE_ONLINE'; deviceId: string }
|
|
| { type: 'DEVICE_OFFLINE'; deviceId: string };
|
|
|
|
const initialState: SpoolBuddyState = {
|
|
weight: null,
|
|
weightStable: false,
|
|
rawAdc: null,
|
|
matchedSpool: null,
|
|
unknownTagUid: null,
|
|
unknownTrayUuid: null,
|
|
deviceOnline: false,
|
|
deviceId: null,
|
|
};
|
|
|
|
function reducer(state: SpoolBuddyState, action: Action): SpoolBuddyState {
|
|
switch (action.type) {
|
|
case 'WEIGHT_UPDATE':
|
|
return {
|
|
...state,
|
|
weight: action.weight,
|
|
weightStable: action.stable,
|
|
rawAdc: action.rawAdc,
|
|
deviceId: action.deviceId,
|
|
deviceOnline: true,
|
|
};
|
|
case 'TAG_MATCHED':
|
|
return {
|
|
...state,
|
|
matchedSpool: action.spool,
|
|
unknownTagUid: null,
|
|
unknownTrayUuid: null,
|
|
deviceId: action.deviceId,
|
|
};
|
|
case 'UNKNOWN_TAG':
|
|
return {
|
|
...state,
|
|
matchedSpool: null,
|
|
unknownTagUid: action.tagUid,
|
|
unknownTrayUuid: action.trayUuid ?? null,
|
|
deviceId: action.deviceId,
|
|
};
|
|
case 'TAG_REMOVED':
|
|
return {
|
|
...state,
|
|
matchedSpool: null,
|
|
unknownTagUid: null,
|
|
unknownTrayUuid: null,
|
|
};
|
|
case 'DEVICE_ONLINE':
|
|
return {
|
|
...state,
|
|
deviceOnline: true,
|
|
deviceId: action.deviceId,
|
|
};
|
|
case 'DEVICE_OFFLINE':
|
|
return {
|
|
...state,
|
|
deviceOnline: false,
|
|
weight: null,
|
|
weightStable: false,
|
|
rawAdc: null,
|
|
};
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
export function useSpoolBuddyState() {
|
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
|
|
const handleWeight = useCallback((e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
dispatch({
|
|
type: 'WEIGHT_UPDATE',
|
|
weight: detail.weight_grams ?? detail.data?.weight_grams,
|
|
stable: detail.stable ?? detail.data?.stable ?? false,
|
|
rawAdc: detail.raw_adc ?? detail.data?.raw_adc ?? null,
|
|
deviceId: detail.device_id ?? detail.data?.device_id ?? '',
|
|
});
|
|
}, []);
|
|
|
|
const handleTagMatched = useCallback((e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
const spool = detail.spool ?? detail.data?.spool;
|
|
if (spool) {
|
|
dispatch({
|
|
type: 'TAG_MATCHED',
|
|
spool: {
|
|
id: spool.id,
|
|
tag_uid: detail.tag_uid ?? detail.data?.tag_uid ?? '',
|
|
material: spool.material ?? '',
|
|
subtype: spool.subtype ?? null,
|
|
color_name: spool.color_name ?? null,
|
|
rgba: spool.rgba ?? null,
|
|
brand: spool.brand ?? null,
|
|
label_weight: spool.label_weight ?? 0,
|
|
core_weight: spool.core_weight ?? 0,
|
|
weight_used: spool.weight_used ?? 0,
|
|
},
|
|
deviceId: detail.device_id ?? detail.data?.device_id ?? '',
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
const handleUnknownTag = useCallback((e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
dispatch({
|
|
type: 'UNKNOWN_TAG',
|
|
tagUid: detail.tag_uid ?? detail.data?.tag_uid ?? '',
|
|
trayUuid: detail.tray_uuid ?? detail.data?.tray_uuid ?? null,
|
|
deviceId: detail.device_id ?? detail.data?.device_id ?? '',
|
|
});
|
|
}, []);
|
|
|
|
const handleTagRemoved = useCallback((e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
dispatch({
|
|
type: 'TAG_REMOVED',
|
|
deviceId: detail.device_id ?? detail.data?.device_id ?? '',
|
|
});
|
|
}, []);
|
|
|
|
const handleOnline = useCallback((e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
dispatch({
|
|
type: 'DEVICE_ONLINE',
|
|
deviceId: detail.device_id ?? detail.data?.device_id ?? '',
|
|
});
|
|
}, []);
|
|
|
|
const handleOffline = useCallback((e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
dispatch({
|
|
type: 'DEVICE_OFFLINE',
|
|
deviceId: detail.device_id ?? detail.data?.device_id ?? '',
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
window.addEventListener('spoolbuddy-weight', handleWeight);
|
|
window.addEventListener('spoolbuddy-tag-matched', handleTagMatched);
|
|
window.addEventListener('spoolbuddy-unknown-tag', handleUnknownTag);
|
|
window.addEventListener('spoolbuddy-tag-removed', handleTagRemoved);
|
|
window.addEventListener('spoolbuddy-online', handleOnline);
|
|
window.addEventListener('spoolbuddy-offline', handleOffline);
|
|
|
|
return () => {
|
|
window.removeEventListener('spoolbuddy-weight', handleWeight);
|
|
window.removeEventListener('spoolbuddy-tag-matched', handleTagMatched);
|
|
window.removeEventListener('spoolbuddy-unknown-tag', handleUnknownTag);
|
|
window.removeEventListener('spoolbuddy-tag-removed', handleTagRemoved);
|
|
window.removeEventListener('spoolbuddy-online', handleOnline);
|
|
window.removeEventListener('spoolbuddy-offline', handleOffline);
|
|
};
|
|
}, [handleWeight, handleTagMatched, handleUnknownTag, handleTagRemoved, handleOnline, handleOffline]);
|
|
|
|
const remainingWeight = state.matchedSpool
|
|
? Math.max(0, state.matchedSpool.label_weight - state.matchedSpool.weight_used)
|
|
: null;
|
|
|
|
const netWeight = state.weight !== null && state.matchedSpool
|
|
? Math.max(0, state.weight - state.matchedSpool.core_weight)
|
|
: null;
|
|
|
|
return {
|
|
...state,
|
|
remainingWeight,
|
|
netWeight,
|
|
};
|
|
}
|