commit 43b0364a5a7e0f872d8547ca5eba7df9d9a6e340 Author: tristan Date: Tue Feb 3 17:59:39 2026 +0100 feat : first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..260ef0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ + +###> symfony/framework-bundle ### +/.env.local +/.env.prod +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +/LOG/ +/config/jwt/*.pem +###< symfony/framework-bundle ### + +###> friendsofphp/php-cs-fixer ### +/.php-cs-fixer.php +/.php-cs-fixer.cache +###< friendsofphp/php-cs-fixer ### + +###> phpunit/phpunit ### +/phpunit.xml +/.phpunit.cache/ +###< phpunit/phpunit ### + +###> docker ### +docker/.env.docker.local +###< docker ### + +###> lexik/jwt-authentication-bundle ### +/config/jwt/*.pem +###< lexik/jwt-authentication-bundle ### diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..248216a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.12.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..69c16cb --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# SIRH +Application de gestion des absences employée diff --git a/frontend/composable/useApi.ts b/frontend/composable/useApi.ts new file mode 100644 index 0000000..0d5b79a --- /dev/null +++ b/frontend/composable/useApi.ts @@ -0,0 +1,183 @@ +import type { FetchOptions } from 'ofetch' +import { $fetch, FetchError } from 'ofetch' +import { useAuthStore } from '~/stores/auth' + +export type AnyObject = Record + +export type ApiClient = { + get(url: string, query?: AnyObject, options?: ApiFetchOptions<'json'>): Promise + getBlob(url: string, query?: AnyObject, options?: ApiFetchOptions<'blob'>): Promise + post(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise + put(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise + patch(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise + delete(url: string, query?: AnyObject, options?: ApiFetchOptions<'json'>): Promise +} + +export type ApiFetchOptions = + FetchOptions & { + toast?: boolean + toastTitle?: string + toastErrorMessage?: string + toastSuccessMessage?: string + toastErrorKey?: string + toastSuccessKey?: string + } + +export const useApi = (): ApiClient => { + const config = useRuntimeConfig() + const baseURL = config.public.apiBase ?? '/api' + const toast = useToast() + const auth = useAuthStore() + const nuxtApp = useNuxtApp() + let isHandlingUnauthorized = false + const i18n = nuxtApp.$i18n as + | { + t: (key: string) => string + te?: (key: string) => boolean + } + | undefined + const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key) + const te = (key: string) => (i18n?.te ? i18n.te(key) : false) + + const extractErrorMessage = (error: unknown, responseData?: unknown): string => { + const data = responseData ?? (error as FetchError)?.data + + if (typeof data === 'string') { + return data + } + + if (data && typeof data === 'object') { + const record = data as Record + return ( + (record['hydra:description'] as string) || + (record.detail as string) || + (record.message as string) || + (record.error as string) || + (record.title as string) || + (record['hydra:title'] as string) || + '' + ) + } + + return (error as FetchError)?.message ?? 'Erreur inconnue.' + } + + const methodErrorKeys: Record = { + GET: 'errors.http.get', + POST: 'errors.http.post', + PUT: 'errors.http.put', + PATCH: 'errors.http.patch', + DELETE: 'errors.http.delete' + } + + const client = $fetch.create({ + baseURL, + retry: 0, + credentials: 'include', + onResponse({ options, response }) { + const apiOptions = options as ApiFetchOptions<'json'> + if (apiOptions?.toast === false) { + return + } + + if (response?.status && response.status >= 400) { + return + } + + const successKey = apiOptions?.toastSuccessKey + const successMessage = + apiOptions?.toastSuccessMessage || + (successKey ? (te(successKey) ? t(successKey) : successKey) : '') + + if (successMessage) { + toast.success({ + title: 'Succès', + message: successMessage + }) + } + }, + async onResponseError({ response, error, options }) { + if (response?.status === 401) { + const requestUrl = typeof options?.url === 'string' ? options.url : '' + if (!requestUrl.includes('login_check') && !requestUrl.includes('logout')) { + if (!isHandlingUnauthorized) { + isHandlingUnauthorized = true + auth.clearSession() + const route = useRoute() + if (route.path !== '/login') { + await navigateTo('/login') + } + isHandlingUnauthorized = false + } + } + return + } + + const apiOptions = options as ApiFetchOptions<'json'> + if (apiOptions?.toast === false) { + return + } + + const method = + typeof options?.method === 'string' ? options.method.toUpperCase() : 'GET' + const defaultKey = methodErrorKeys[method] + const defaultMessage = + defaultKey && te(defaultKey) ? t(defaultKey) : '' + const errorKey = apiOptions?.toastErrorKey + const errorMessage = + errorKey ? (te(errorKey) ? t(errorKey) : errorKey) : '' + const extractedMessage = extractErrorMessage(error, response?._data) + const message = + apiOptions?.toastErrorMessage || + errorMessage || + defaultMessage || + extractedMessage || + 'Une erreur est survenue.' + + toast.error({ + title: apiOptions?.toastTitle ?? 'Erreur', + message + }) + } + }) + + const request = ( + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', + url: string, + options: ApiFetchOptions<'json'> = {} + ) => { + const needsJsonBody = method === 'POST' || method === 'PUT' + const needsMergePatch = method === 'PATCH' + + const headers = new Headers(options.headers as HeadersInit | undefined) + + if (needsMergePatch && !headers.has('Content-Type')) { + headers.set('Content-Type', 'application/merge-patch+json') + } else if (needsJsonBody && !headers.has('Content-Type')) { + headers.set('Content-Type', 'application/json') + } + + return client(url, { ...options, method, headers }) + } + + return { + get(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { + return request('GET', url, { ...options, query }) + }, + getBlob(url: string, query: AnyObject = {}, options: ApiFetchOptions<'blob'> = {}) { + return client(url, { ...options, method: 'GET', query, responseType: 'blob' }) + }, + post(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { + return request('POST', url, { ...options, body }) + }, + put(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { + return request('PUT', url, { ...options, body }) + }, + patch(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { + return request('PATCH', url, { ...options, body }) + }, + delete(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { + return request('DELETE', url, { ...options, query }) + } + } +} diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json new file mode 100644 index 0000000..6f9026f --- /dev/null +++ b/frontend/i18n/locales/fr.json @@ -0,0 +1,64 @@ +{ + "errors": { + "http": { + "get": "Impossible de récupérer les données.", + "post": "Impossible de créer la ressource.", + "put": "Impossible de mettre à jour la ressource.", + "patch": "Impossible de mettre à jour la ressource.", + "delete": "Impossible de supprimer la ressource." + }, + "reception": { + "list": "Impossible de récupérer la liste des réceptions.", + "fetch": "Impossible de récupérer la réception.", + "create": "Impossible de créer la réception.", + "update": "Impossible de mettre à jour la réception.", + "weigh": "Impossible de récupérer la pesée." + }, + "receptionType": { + "list": "Impossible de récupérer la liste des types de réception." + }, + "merchandiseType": { + "list": "Impossible de récupérer la liste des types de marchandises." + }, + "building": { + "list": "Impossible de récupérer la liste des bâtiments." + }, + "pelletType": { + "list": "Impossible de récupérer la liste des types de granulés." + }, + "receptionPelletBuilding": { + "list": "Impossible de récupérer la liste des dépôts de granulés.", + "create": "Impossible d'enregistrer le dépôt de granulés.", + "delete": "Impossible de supprimer le dépôt de granulés." + }, + "supplier": { + "list": "Impossible de récupérer la liste des fournisseurs." + }, + "truck": { + "list": "Impossible de récupérer la liste des camions." + }, + "carrier": { + "list": "Impossible de récupérer la liste des transporteurs." + }, + "driver": { + "list": "Impossible de récupérer la liste des chauffeurs." + }, + "vehicle": { + "list": "Impossible de récupérer la liste des immatriculations." + }, + "auth": { + "login": "Identifiants invalides.", + "users": "Impossible de récupérer les utilisateurs.", + "logout": "Impossible de se déconnecter." + } + }, + "success": { + "reception": { + "update": "Réception mise à jour avec succès." + }, + "auth": { + "login": "Connexion réussie.", + "logout": "Déconnexion réussie." + } + } +} diff --git a/frontend/layouts/auth.vue b/frontend/layouts/auth.vue new file mode 100644 index 0000000..d1acf22 --- /dev/null +++ b/frontend/layouts/auth.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue new file mode 100644 index 0000000..9c011bc --- /dev/null +++ b/frontend/layouts/default.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue new file mode 100644 index 0000000..9c011bc --- /dev/null +++ b/frontend/pages/index.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/frontend/public/malio.png b/frontend/public/malio.png new file mode 100644 index 0000000..dc520e5 Binary files /dev/null and b/frontend/public/malio.png differ diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts new file mode 100644 index 0000000..4229a9e --- /dev/null +++ b/frontend/tailwind.config.ts @@ -0,0 +1,25 @@ +import type { Config } from 'tailwindcss' + +export default >{ + theme: { + extend: { + fontFamily: { + sans: ['"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif'] + }, + colors: { + primary: { + 50: '#f6f9ea', + 100: '#eaf2cf', + 200: '#d6e3a4', + 300: '#c1d47a', + 400: '#afc85a', + 500: '#9ebb43', + 600: '#7e9735', + 700: '#607228', + 800: '#414d1a', + 900: '#24290d' + } + } + } + } +}