Fix some code

This commit is contained in:
Anthony Janin 2025-06-05 10:04:10 +02:00
parent 2c329aa8a2
commit 1e6065ec7c
10 changed files with 323 additions and 334 deletions

View File

@ -1,54 +1,50 @@
# React + TypeScript + Vite # Application [4NK] - Interface Web
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. Cette application React fournit une interface pour interagir avec la plateforme [4NK]. Elle permet l'authentification des utilisateurs, la création de profils et de dossiers, ainsi que la visualisation des processus.
Currently, two official plugins are available: ## Fonctionnalités principales
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh - Authentification utilisateur via OAuth
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - Création et gestion de profils utilisateur
- Création et gestion de dossiers
- Visualisation des processus
- Console de messages pour le suivi des événements
## Expanding the ESLint configuration ## Prérequis
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - Node.js (version 18 ou supérieure)
- npm ou yarn
```js ## Installation
export default tseslint.config({
extends: [ Pour installer les dépendances du projet, exécutez :
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked, ```bash
// Alternatively, use this for stricter rules npm install
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
``` ```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ## Commandes disponibles
```js ### Démarrage de l'application
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default tseslint.config({ **Commande principale** pour lancer l'application en mode développement :
plugins: {
// Add the react-x and react-dom plugins ```bash
'react-x': reactX, npm run dev
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
``` ```
L'application sera accessible à l'adresse : http://localhost:5173
## Communication avec la plateforme [4NK]
L'application communique avec la plateforme [4NK] via une iframe et un bus de messages. La communication est gérée par les classes `MessageBus` et `EventBus` dans le dossier `/src/sdk`.
## Configuration
L'URL de l'iframe est définie dans `App.tsx` :
```typescript
const iframeUrl = 'https://dev3.4nkweb.com'
```
Pour modifier l'environnement cible, vous devez changer cette URL.

View File

@ -147,6 +147,7 @@ function App() {
onLogout={handleLogout} onLogout={handleLogout}
onCreateProfile={handleOpenProfileModal} onCreateProfile={handleOpenProfileModal}
onCreateFolder={handleOpenFolderModal} onCreateFolder={handleOpenFolderModal}
iframeUrl={iframeUrl}
/> />
{/* Structure flexible avec console à gauche et contenu à droite */} {/* Structure flexible avec console à gauche et contenu à droite */}

View File

@ -9,20 +9,14 @@ interface ControlPanelProps {
iframeUrl?: string; iframeUrl?: string;
} }
/**
* Composant de panel de contrôle pour gérer les actions principales
*
* Fournit des boutons pour la connexion, la création de profil et le contrôle de visibilité
*/
function ControlPanel({ function ControlPanel({
onLogin = () => console.log('Connexion demandée'), onLogin = () => console.log('Connexion demandée'),
onLogout = () => console.log('Déconnexion demandée'), onLogout = () => console.log('Déconnexion demandée'),
onCreateProfile = () => console.log('Création de profil demandée'), onCreateProfile = () => console.log('Création de profil demandée'),
onCreateFolder = () => console.log('Création de dossier demandée'), onCreateFolder = () => console.log('Création de dossier demandée'),
isConnected = false, isConnected = false,
iframeUrl = 'https://dev1.4nkweb.com' iframeUrl = ''
}: ControlPanelProps) { }: ControlPanelProps) {
// État pour gérer l'affichage du tooltip
const [showTooltip, setShowTooltip] = useState(false); const [showTooltip, setShowTooltip] = useState(false);
return ( return (

View File

@ -264,6 +264,7 @@
0% { 0% {
box-shadow: 0 0 0 2px rgba(103, 58, 183, 0.8); box-shadow: 0 0 0 2px rgba(103, 58, 183, 0.8);
} }
100% { 100% {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
@ -284,11 +285,8 @@
flex-direction: column; flex-direction: column;
} }
.btn-cancel, .btn-submit { .btn-cancel,
.btn-submit {
width: 100%; width: 100%;
} }
} }
/* Styles spécifiques pour le composant FolderModal */
/* Les styles génériques de modal sont fournis par Modal.css */

View File

@ -96,239 +96,239 @@ function FolderModal({
return ( return (
<Modal isOpen={isOpen} onClose={onClose} title="Créer un nouveau dossier"> <Modal isOpen={isOpen} onClose={onClose} title="Créer un nouveau dossier">
<div className="folder-container"> <div className="folder-container">
<form className="folder-form" onSubmit={handleSubmit}> <form className="folder-form" onSubmit={handleSubmit}>
<div className="form-section"> <div className="form-section">
<h3 className="section-title">Informations principales</h3> <h3 className="section-title">Informations principales</h3>
<div className="form-row"> <div className="form-row">
<div className="form-field"> <div className="form-field">
<label> <label>
Numéro de dossier <span className="required">*</span> Numéro de dossier <span className="required">*</span>
</label> </label>
<input <input
type="text" type="text"
name="folderNumber" name="folderNumber"
value={folderData.folderNumber} value={folderData.folderNumber}
onChange={handleInputChange} onChange={handleInputChange}
required required
disabled={readOnly} disabled={readOnly}
placeholder="ex: DOC-2025-001" placeholder="ex: DOC-2025-001"
/> />
</div>
<div className="form-field">
<label>
Nom <span className="required">*</span>
</label>
<input
type="text"
name="name"
value={folderData.name}
onChange={handleInputChange}
required
disabled={readOnly}
placeholder="Nom du dossier"
/>
</div>
</div>
<div className="form-row">
<div className="form-field">
<label>
Type d'acte <span className="required">*</span>
</label>
<select
name="deedType"
value={folderData.deedType}
onChange={handleInputChange}
required
disabled={readOnly}
>
<option value="">Sélectionnez le type d'acte</option>
<option value="vente">Vente</option>
<option value="achat">Achat</option>
<option value="succession">Succession</option>
<option value="donation">Donation</option>
<option value="autre">Autre</option>
</select>
</div>
<div className="form-field">
<label>
Statut <span className="required">*</span>
</label>
<select
name="status"
value={folderData.status}
onChange={handleInputChange}
required
disabled={readOnly}
>
<option value="active">Actif</option>
<option value="pending">En attente</option>
<option value="completed">Complété</option>
<option value="archived">Archivé</option>
</select>
</div>
</div>
<div className="form-field">
<label>Description</label>
<textarea
name="description"
value={folderData.description}
onChange={handleInputChange}
disabled={readOnly}
placeholder="Description du dossier"
rows={3}
/>
</div>
{folderData.status === 'archived' && (
<div className="form-field">
<label>Description d'archivage</label>
<textarea
name="archived_description"
value={folderData.archived_description}
onChange={handleInputChange}
disabled={readOnly}
placeholder="Raison d'archivage"
rows={2}
/>
</div>
)}
</div> </div>
<div className="form-field">
<div className="form-section"> <label>
<h3 className="section-title">Clients</h3> Nom <span className="required">*</span>
<div className="tag-list"> </label>
{folderData.customers.map((customer, index) => ( <input
<div key={index} className="tag-item"> type="text"
<span>{customer}</span> name="name"
{!readOnly && ( value={folderData.name}
<button onChange={handleInputChange}
type="button" required
className="tag-remove" disabled={readOnly}
onClick={() => removeCustomer(customer)} placeholder="Nom du dossier"
aria-label="Supprimer ce client" />
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentCustomer}
onChange={(e) => setCurrentCustomer(e.target.value)}
placeholder="Ajouter un client"
/>
<button
type="button"
className="btn-add-tag"
onClick={addCustomer}
>
Ajouter
</button>
</div>
)}
</div> </div>
</div>
<div className="form-section"> <div className="form-row">
<h3 className="section-title">Parties prenantes</h3> <div className="form-field">
<div className="tag-list"> <label>
{folderData.stakeholders.map((stakeholder, index) => ( Type d'acte <span className="required">*</span>
<div key={index} className="tag-item"> </label>
<span>{stakeholder}</span> <select
{!readOnly && ( name="deedType"
<button value={folderData.deedType}
type="button" onChange={handleInputChange}
className="tag-remove" required
onClick={() => removeStakeholder(stakeholder)} disabled={readOnly}
aria-label="Supprimer cette partie prenante"
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentStakeholder}
onChange={(e) => setCurrentStakeholder(e.target.value)}
placeholder="Ajouter une partie prenante"
/>
<button
type="button"
className="btn-add-tag"
onClick={addStakeholder}
>
Ajouter
</button>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Informations système</h3>
<div className="form-row">
<div className="form-field">
<label>Créé le</label>
<input
type="text"
value={new Date(folderData.created_at).toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
disabled
readOnly
/>
</div>
<div className="form-field">
<label>Dernière mise à jour</label>
<input
type="text"
value={new Date(folderData.updated_at).toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
disabled
readOnly
/>
</div>
</div>
</div>
<div className="form-actions">
{onCancel && (
<button
type="button"
className="btn-cancel"
onClick={onCancel}
>
Annuler
</button>
)}
<button
type="submit"
className="btn-submit"
> >
Enregistrer <option value="">Sélectionnez le type d'acte</option>
<option value="vente">Vente</option>
<option value="achat">Achat</option>
<option value="succession">Succession</option>
<option value="donation">Donation</option>
<option value="autre">Autre</option>
</select>
</div>
<div className="form-field">
<label>
Statut <span className="required">*</span>
</label>
<select
name="status"
value={folderData.status}
onChange={handleInputChange}
required
disabled={readOnly}
>
<option value="active">Actif</option>
<option value="pending">En attente</option>
<option value="completed">Complété</option>
<option value="archived">Archivé</option>
</select>
</div>
</div>
<div className="form-field">
<label>Description</label>
<textarea
name="description"
value={folderData.description}
onChange={handleInputChange}
disabled={readOnly}
placeholder="Description du dossier"
rows={3}
/>
</div>
{folderData.status === 'archived' && (
<div className="form-field">
<label>Description d'archivage</label>
<textarea
name="archived_description"
value={folderData.archived_description}
onChange={handleInputChange}
disabled={readOnly}
placeholder="Raison d'archivage"
rows={2}
/>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Clients</h3>
<div className="tag-list">
{folderData.customers.map((customer, index) => (
<div key={index} className="tag-item">
<span>{customer}</span>
{!readOnly && (
<button
type="button"
className="tag-remove"
onClick={() => removeCustomer(customer)}
aria-label="Supprimer ce client"
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentCustomer}
onChange={(e) => setCurrentCustomer(e.target.value)}
placeholder="Ajouter un client"
/>
<button
type="button"
className="btn-add-tag"
onClick={addCustomer}
>
Ajouter
</button> </button>
</div> </div>
</form> )}
</div> </div>
<div className="form-section">
<h3 className="section-title">Parties prenantes</h3>
<div className="tag-list">
{folderData.stakeholders.map((stakeholder, index) => (
<div key={index} className="tag-item">
<span>{stakeholder}</span>
{!readOnly && (
<button
type="button"
className="tag-remove"
onClick={() => removeStakeholder(stakeholder)}
aria-label="Supprimer cette partie prenante"
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentStakeholder}
onChange={(e) => setCurrentStakeholder(e.target.value)}
placeholder="Ajouter une partie prenante"
/>
<button
type="button"
className="btn-add-tag"
onClick={addStakeholder}
>
Ajouter
</button>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Informations système</h3>
<div className="form-row">
<div className="form-field">
<label>Créé le</label>
<input
type="text"
value={new Date(folderData.created_at).toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
disabled
readOnly
/>
</div>
<div className="form-field">
<label>Dernière mise à jour</label>
<input
type="text"
value={new Date(folderData.updated_at).toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
disabled
readOnly
/>
</div>
</div>
</div>
<div className="form-actions">
{onCancel && (
<button
type="button"
className="btn-cancel"
onClick={onCancel}
>
Annuler
</button>
)}
<button
type="submit"
className="btn-submit"
>
Enregistrer
</button>
</div>
</form>
</div>
</Modal> </Modal>
); );
}; };

View File

@ -16,8 +16,8 @@ const formatTimestamp = (isoString: string): string => {
// Vérifier si c'est aujourd'hui // Vérifier si c'est aujourd'hui
const isToday = date.getDate() === now.getDate() && const isToday = date.getDate() === now.getDate() &&
date.getMonth() === now.getMonth() && date.getMonth() === now.getMonth() &&
date.getFullYear() === now.getFullYear(); date.getFullYear() === now.getFullYear();
if (isToday) { if (isToday) {
// Format pour aujourd'hui: HH:MM:SS.ms // Format pour aujourd'hui: HH:MM:SS.ms
@ -31,7 +31,7 @@ const formatTimestamp = (isoString: string): string => {
}; };
interface MessageConsoleProps { interface MessageConsoleProps {
messages: {timestamp: string; data: any}[]; messages: { timestamp: string; data: any }[];
onClearMessages?: () => void; onClearMessages?: () => void;
} }
@ -46,7 +46,7 @@ function MessageConsole({
onClearMessages = () => console.log('Vidage des messages demandé') onClearMessages = () => console.log('Vidage des messages demandé')
}: MessageConsoleProps) { }: MessageConsoleProps) {
// État local pour stocker les messages reçus via l'EventBus // État local pour stocker les messages reçus via l'EventBus
const [localMessages, setLocalMessages] = useState<{timestamp: string; data: any}[]>([]); const [localMessages, setLocalMessages] = useState<{ timestamp: string; data: any }[]>([]);
// Tous les messages à afficher (externes + locaux), triés par timestamp // Tous les messages à afficher (externes + locaux), triés par timestamp
const allMessages = [...externalMessages, ...localMessages].sort((a, b) => { const allMessages = [...externalMessages, ...localMessages].sort((a, b) => {

View File

@ -2,7 +2,7 @@ export default class EventBus {
private static instance: EventBus; private static instance: EventBus;
private listeners: Record<string, Array<(...args: any[]) => void>> = {}; private listeners: Record<string, Array<(...args: any[]) => void>> = {};
private constructor() {} private constructor() { }
public static getInstance(): EventBus { public static getInstance(): EventBus {
if (!EventBus.instance) { if (!EventBus.instance) {