regroup etc ...
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { useCallback } from 'react';
|
||||
import { createMMKV, useMMKVObject } from 'react-native-mmkv';
|
||||
import { Batch } from '../types';
|
||||
|
||||
const storage = createMMKV({ id: 'vaultdrop-batches' });
|
||||
|
||||
export function useBatchStore() {
|
||||
const [batchIds, setBatchIds] = useMMKVObject<string[]>('batch-ids', storage);
|
||||
|
||||
const saveBatch = useCallback((batch: Batch) => {
|
||||
storage.set(`batch_${batch.id}`, JSON.stringify(batch));
|
||||
const current = batchIds ?? [];
|
||||
if (!current.includes(batch.id)) {
|
||||
setBatchIds([batch.id, ...current]);
|
||||
}
|
||||
}, [batchIds, setBatchIds]);
|
||||
|
||||
const getBatch = useCallback((id: string): Batch | undefined => {
|
||||
const raw = storage.getString(`batch_${id}`);
|
||||
if (!raw) return undefined;
|
||||
return JSON.parse(raw) as Batch;
|
||||
}, []);
|
||||
|
||||
const getAllBatches = useCallback((): Batch[] => {
|
||||
return (batchIds ?? [])
|
||||
.map((id) => getBatch(id))
|
||||
.filter((b): b is Batch => b !== undefined);
|
||||
}, [batchIds, getBatch]);
|
||||
|
||||
const addTagToBatch = useCallback((batchId: string, tag: string) => {
|
||||
const batch = getBatch(batchId);
|
||||
if (!batch) return;
|
||||
if (batch.tags.includes(tag)) return;
|
||||
batch.tags = [...batch.tags, tag];
|
||||
storage.set(`batch_${batchId}`, JSON.stringify(batch));
|
||||
}, [getBatch]);
|
||||
|
||||
const removeTagFromBatch = useCallback((batchId: string, tag: string) => {
|
||||
const batch = getBatch(batchId);
|
||||
if (!batch) return;
|
||||
batch.tags = batch.tags.filter((t) => t !== tag);
|
||||
storage.set(`batch_${batchId}`, JSON.stringify(batch));
|
||||
}, [getBatch]);
|
||||
|
||||
const deleteBatch = useCallback((batchId: string) => {
|
||||
storage.set(`batch_${batchId}`, undefined as any);
|
||||
storage.remove(`batch_${batchId}` as any);
|
||||
setBatchIds((batchIds ?? []).filter((id) => id !== batchId));
|
||||
}, [batchIds, setBatchIds]);
|
||||
|
||||
return {
|
||||
saveBatch,
|
||||
getBatch,
|
||||
getAllBatches,
|
||||
addTagToBatch,
|
||||
removeTagFromBatch,
|
||||
deleteBatch,
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { useCameraDevice, useCameraPermission, usePhotoOutput, type TorchMode, type CameraRef } from 'react-native-vision-camera';
|
||||
import { useUpload } from './useUpload';
|
||||
import { CapturedPhoto } from '../types';
|
||||
|
||||
type CaptureStatus = 'idle' | 'capturing' | 'uploading' | 'processing' | 'success' | 'error';
|
||||
type CaptureStatus = 'idle' | 'capturing' | 'uploading' | 'success' | 'error';
|
||||
|
||||
export function useCameraCapture() {
|
||||
const { hasPermission, requestPermission } = useCameraPermission();
|
||||
@@ -13,6 +14,7 @@ export function useCameraCapture() {
|
||||
const [captureError, setCaptureError] = useState<string>();
|
||||
const [torchMode, setTorchMode] = useState<TorchMode>('off');
|
||||
const [cameraReady, setCameraReady] = useState(false);
|
||||
const [capturedPhotos, setCapturedPhotos] = useState<CapturedPhoto[]>([]);
|
||||
|
||||
const upload = useUpload();
|
||||
|
||||
@@ -53,35 +55,61 @@ export function useCameraCapture() {
|
||||
setCameraReady(false);
|
||||
}, []);
|
||||
|
||||
const addCapturedPhoto = useCallback((photo: CapturedPhoto) => {
|
||||
setCapturedPhotos((prev) => [...prev, photo]);
|
||||
}, []);
|
||||
|
||||
const removeCapturedPhoto = useCallback((id: string) => {
|
||||
setCapturedPhotos((prev) => prev.filter((p) => p.id !== id));
|
||||
}, []);
|
||||
|
||||
const clearCapturedPhotos = useCallback(() => {
|
||||
setCapturedPhotos([]);
|
||||
}, []);
|
||||
|
||||
const capturePhoto = useCallback(async () => {
|
||||
if (!photoOutput) return;
|
||||
|
||||
const photoId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
||||
|
||||
try {
|
||||
setCaptureStatus('capturing');
|
||||
setCaptureError(undefined);
|
||||
|
||||
const { filePath } = await photoOutput.capturePhotoToFile(
|
||||
{},
|
||||
{}
|
||||
);
|
||||
const { filePath } = await photoOutput.capturePhotoToFile({}, {});
|
||||
|
||||
setCaptureStatus('uploading');
|
||||
|
||||
const capturedPhoto: CapturedPhoto = {
|
||||
id: photoId,
|
||||
filePath,
|
||||
uri: 'file://' + filePath,
|
||||
};
|
||||
|
||||
addCapturedPhoto(capturedPhoto);
|
||||
|
||||
const result = await upload.mutateAsync([
|
||||
{ uri: 'file://' + filePath, type: 'image/jpeg', name: 'scan.jpg' },
|
||||
{ uri: capturedPhoto.uri, type: 'image/jpeg', name: `scan_${photoId}.jpg` },
|
||||
]);
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
setCaptureStatus('error');
|
||||
setCaptureError(result.errors[0].message);
|
||||
} else {
|
||||
capturedPhoto.uploadedId = result.uploaded[0]?.id;
|
||||
capturedPhoto.uploadedAt = new Date().toISOString();
|
||||
setCaptureStatus('success');
|
||||
}
|
||||
} catch (err) {
|
||||
setCaptureStatus('error');
|
||||
setCaptureError(err instanceof Error ? err.message : 'Erreur lors de la capture');
|
||||
}
|
||||
}, [photoOutput, upload]);
|
||||
|
||||
setTimeout(() => {
|
||||
setCaptureStatus('idle');
|
||||
setCaptureError(undefined);
|
||||
}, 1500);
|
||||
}, [photoOutput, upload, addCapturedPhoto]);
|
||||
|
||||
return {
|
||||
cameraRef,
|
||||
@@ -97,5 +125,9 @@ export function useCameraCapture() {
|
||||
toggleTorch,
|
||||
onStarted,
|
||||
onStopped,
|
||||
capturedPhotos,
|
||||
removeCapturedPhoto,
|
||||
clearCapturedPhotos,
|
||||
capturedCount: capturedPhotos.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import * as Print from 'expo-print';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import { CapturedPhoto } from '../types';
|
||||
|
||||
export function usePdfGeneration() {
|
||||
const [generating, setGenerating] = useState(false);
|
||||
|
||||
const generatePdf = useCallback(async (photos: CapturedPhoto[]): Promise<string | null> => {
|
||||
if (photos.length === 0) return null;
|
||||
|
||||
setGenerating(true);
|
||||
|
||||
try {
|
||||
const imagesHtml = photos.map((p) => {
|
||||
const ext = p.filePath.split('.').pop()?.toLowerCase() || 'jpeg';
|
||||
const mime = ext === 'png' ? 'image/png' : 'image/jpeg';
|
||||
return `<div style="page-break-after: always; display: flex; justify-content: center; align-items: center; height: 100vh;">
|
||||
<img src="${p.uri}" style="max-width: 100%; max-height: 100vh; object-fit: contain;" />
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #fff; }
|
||||
@media print {
|
||||
@page { margin: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>${imagesHtml}</body>
|
||||
</html>`;
|
||||
|
||||
const { uri } = await Print.printToFileAsync({ html });
|
||||
return uri;
|
||||
} catch (err) {
|
||||
console.error('PDF generation failed:', err);
|
||||
return null;
|
||||
} finally {
|
||||
setGenerating(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
generatePdf,
|
||||
generating,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user