mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 06:39:34 +01:00
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.
98 lines
3.2 KiB
TypeScript
98 lines
3.2 KiB
TypeScript
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;
|
|
}
|