Chapitre 3 : Autres fonctionnalités

Géolocalisation

Obtenir la position

import { useState } from 'react';
import { Button, Block } from '@cap-rel/smartcommon';

function LocationButton({ onLocation }) {
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    const getLocation = () => {
        if (!navigator.geolocation) {
            setError('Géolocalisation non supportée');
            return;
        }

        setLoading(true);
        setError(null);

        navigator.geolocation.getCurrentPosition(
            (position) => {
                setLoading(false);
                onLocation({
                    lat: position.coords.latitude,
                    lng: position.coords.longitude,
                    accuracy: position.coords.accuracy
                });
            },
            (err) => {
                setLoading(false);
                setError(err.message);
            },
            {
                enableHighAccuracy: true,
                timeout: 10000,
                maximumAge: 0
            }
        );
    };

    return (
        <Block>
            <Button onClick={getLocation} loading={loading}>
                Obtenir ma position
            </Button>
            {error && <p className="text-red-500 mt-2">{error}</p>}
        </Block>
    );
}

Composant Gps de SmartCommon

import { Gps } from '@cap-rel/smartcommon';

<Gps
    name="location"
    label="Position de l'intervention"
    onChange={(coords) => console.log(coords)}
/>

Upload de fichiers

PhotosUploader

import { PhotosUploader } from '@cap-rel/smartcommon';

<PhotosUploader
    name="photos"
    label="Photos"
    maxFiles={5}
    maxSize={5 * 1024 * 1024}  // 5 Mo
    onChange={(files) => console.log(files)}
/>

FilesUploader

import { FilesUploader } from '@cap-rel/smartcommon';

<FilesUploader
    name="documents"
    label="Documents"
    accept=".pdf,.doc,.docx"
    multiple
    maxFiles={10}
/>

Redimensionner une image

import { useFile } from '@cap-rel/smartcommon';

function ImageUploader() {
    const { resizeImage } = useFile();

    const handleFile = async (e) => {
        const file = e.target.files[0];

        const base64 = await resizeImage(file, {
            maxWidth: 1200,
            maxHeight: 1200,
            quality: 80
        });

        // Envoyer au serveur
        await api.private.post('files', {
            json: {
                filename: file.name,
                content: base64
            }
        });
    };

    return <input type="file" accept="image/*" onChange={handleFile} />;
}

Thème sombre

Configuration globale

// appConfig.js
export const config = {
    globalState: {
        reducers: {
            settings: {
                theme: 'light'  // 'light' ou 'dark'
            }
        }
    }
};

Appliquer le thème

import { useEffect } from 'react';
import { useGlobalStates } from '@cap-rel/smartcommon';

function ThemeProvider({ children }) {
    const gst = useGlobalStates();

    useEffect(() => {
        document.documentElement.classList.toggle(
            'dark',
            gst.get('settings')?.theme === 'dark'
        );
    }, [gst.get('settings')?.theme]);

    return children;
}

Toggle de thème

import { useGlobalStates } from '@cap-rel/smartcommon';
import { Boolean } from '@cap-rel/smartcommon';

function ThemeToggle() {
    const gst = useGlobalStates();
    const settings = gst.get('settings');

    const toggleTheme = () => {
        gst.local.set('settings', {
            ...settings,
            theme: settings.theme === 'light' ? 'dark' : 'light'
        });
    };

    return (
        <Boolean
            value={settings?.theme === 'dark'}
            onChange={toggleTheme}
            label="Mode sombre"
        />
    );
}

Signature manuscrite

import { SignaturePad } from '@cap-rel/smartcommon';

<SignaturePad
    name="signature"
    label="Signature du client"
    width={400}
    height={200}
/>

Scan de code-barres

Utiliser une bibliothèque comme @zxing/browser :

npm install @zxing/browser @zxing/library
import { useState, useRef, useEffect } from 'react';
import { BrowserMultiFormatReader } from '@zxing/browser';

function BarcodeScanner({ onScan }) {
    const videoRef = useRef(null);
    const [scanning, setScanning] = useState(false);

    useEffect(() => {
        let reader;

        if (scanning) {
            reader = new BrowserMultiFormatReader();
            reader.decodeFromVideoDevice(
                undefined,
                videoRef.current,
                (result, error) => {
                    if (result) {
                        onScan(result.getText());
                        setScanning(false);
                    }
                }
            );
        }

        return () => {
            if (reader) {
                reader.reset();
            }
        };
    }, [scanning]);

    return (
        <div>
            {scanning ? (
                <video ref={videoRef} className="w-full" />
            ) : (
                <button onClick={() => setScanning(true)}>
                    Scanner un code
                </button>
            )}
        </div>
    );
}

Notifications locales

function requestNotificationPermission() {
    if ('Notification' in window) {
        Notification.requestPermission().then(permission => {
            console.log('Permission:', permission);
        });
    }
}

function showNotification(title, body) {
    if (Notification.permission === 'granted') {
        new Notification(title, { body });
    }
}

Points clés à retenir

  1. Géolocalisation avec navigator.geolocation ou composant Gps
  2. Upload avec PhotosUploader, FilesUploader, useFile
  3. Thème sombre via useGlobalStates et classes CSS
  4. Signature avec SignaturePad
  5. Intégrer des bibliothèques tierces selon les besoins

← Chapitre précédent | Retour au module | Module suivant : Bonnes pratiques ->