mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:19:35 +01:00
feat(scanner): Mobile Scanner App mit echten Produktionsaufträgen
Scanner-App Tauri v2 Android App mit Login, Barcode-Scanner, Consume/Move/Book-Flows und Aufgabenliste. TasksPage lädt echte RELEASED/IN_PROGRESS Produktionsaufträge via API, Consume-Flow nutzt den konkreten Auftrag für korrekte Rezept-Skalierung statt blind den ersten IN_PROGRESS-Auftrag zu nehmen. Backend: FindStockByBatchId Use Case + REST-Endpoint für Stock-Lookup per Chargennummer.
This commit is contained in:
parent
72979c9537
commit
bf09e3b747
93 changed files with 8977 additions and 60 deletions
98
frontend/apps/scanner/src/api.tsx
Normal file
98
frontend/apps/scanner/src/api.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { createContext, useContext, useMemo, useState, useCallback, type ReactNode } from 'react';
|
||||
import { createEffigenixClient, type EffigenixClient, type TokenProvider } from '@effigenix/api-client';
|
||||
import { fetch as tauriFetch } from '@tauri-apps/plugin-http';
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
||||
|
||||
/**
|
||||
* Axios adapter that delegates to Tauri's HTTP plugin.
|
||||
* Bypasses CORS restrictions on mobile webviews.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const tauriAdapter = async (config: any) => {
|
||||
const url = new URL(config.url || '', config.baseURL);
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
if (config.headers) {
|
||||
const raw = typeof config.headers.toJSON === 'function' ? config.headers.toJSON(false) : config.headers;
|
||||
for (const [key, value] of Object.entries(raw)) {
|
||||
if (value != null) headers[key] = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await tauriFetch(url.toString(), {
|
||||
method: (config.method || 'GET').toUpperCase(),
|
||||
headers,
|
||||
body: config.data != null
|
||||
? (typeof config.data === 'string' ? config.data : JSON.stringify(config.data))
|
||||
: undefined,
|
||||
});
|
||||
|
||||
let data: unknown;
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
data = await response.json();
|
||||
} else {
|
||||
data = await response.text();
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
config,
|
||||
};
|
||||
};
|
||||
|
||||
interface ApiState {
|
||||
client: EffigenixClient;
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
const ApiContext = createContext<ApiState | null>(null);
|
||||
|
||||
export function ApiProvider({ children }: { children: ReactNode }) {
|
||||
const [tokens, setTokens] = useState<{ access: string; refresh: string } | null>(null);
|
||||
|
||||
const tokenProvider = useMemo<TokenProvider>(() => ({
|
||||
getAccessToken: async () => tokens?.access ?? null,
|
||||
getRefreshToken: async () => tokens?.refresh ?? null,
|
||||
saveTokens: async (accessToken, refreshToken) => {
|
||||
setTokens({ access: accessToken, refresh: refreshToken });
|
||||
},
|
||||
clearTokens: async () => setTokens(null),
|
||||
}), [tokens]);
|
||||
|
||||
const client = useMemo(() => createEffigenixClient(tokenProvider, {
|
||||
baseUrl: API_BASE,
|
||||
adapter: tauriAdapter,
|
||||
}), [tokenProvider]);
|
||||
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
const res = await tauriFetch(`${API_BASE}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`${res.status}: ${text}`);
|
||||
}
|
||||
const data = await res.json();
|
||||
setTokens({ access: data.accessToken, refresh: data.refreshToken });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ApiContext.Provider value={{ client, login, isAuthenticated: tokens !== null }}>
|
||||
{children}
|
||||
</ApiContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useApi() {
|
||||
const ctx = useContext(ApiContext);
|
||||
if (!ctx) throw new Error('useApi must be used within ApiProvider');
|
||||
return ctx;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue