Fix some code
This commit is contained in:
parent
2c329aa8a2
commit
1e6065ec7c
82
README.md
82
README.md
@ -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
|
||||
- [@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
|
||||
- Authentification utilisateur via OAuth
|
||||
- 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
|
||||
export default tseslint.config({
|
||||
extends: [
|
||||
// Remove ...tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...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,
|
||||
},
|
||||
},
|
||||
})
|
||||
## Installation
|
||||
|
||||
Pour installer les dépendances du projet, exécutez :
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
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
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
### Démarrage de l'application
|
||||
|
||||
export default tseslint.config({
|
||||
plugins: {
|
||||
// Add the react-x and react-dom plugins
|
||||
'react-x': reactX,
|
||||
'react-dom': reactDom,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended typescript rules
|
||||
...reactX.configs['recommended-typescript'].rules,
|
||||
...reactDom.configs.recommended.rules,
|
||||
},
|
||||
})
|
||||
**Commande principale** pour lancer l'application en mode développement :
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
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.
|
||||
|
@ -147,6 +147,7 @@ function App() {
|
||||
onLogout={handleLogout}
|
||||
onCreateProfile={handleOpenProfileModal}
|
||||
onCreateFolder={handleOpenFolderModal}
|
||||
iframeUrl={iframeUrl}
|
||||
/>
|
||||
|
||||
{/* Structure flexible avec console à gauche et contenu à droite */}
|
||||
|
@ -9,28 +9,22 @@ interface ControlPanelProps {
|
||||
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'),
|
||||
onLogout = () => console.log('Déconnexion demandée'),
|
||||
onCreateProfile = () => console.log('Création de profil demandée'),
|
||||
onCreateFolder = () => console.log('Création de dossier demandée'),
|
||||
isConnected = false,
|
||||
iframeUrl = 'https://dev1.4nkweb.com'
|
||||
iframeUrl = ''
|
||||
}: ControlPanelProps) {
|
||||
// État pour gérer l'affichage du tooltip
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
|
||||
return (
|
||||
<div className="control-panel-container">
|
||||
<div className="control-panel-row">
|
||||
{/* Bouton d'information avec tooltip */}
|
||||
<div className="url-tooltip-container button-container">
|
||||
<button
|
||||
<button
|
||||
className="info-button"
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
@ -44,19 +38,19 @@ function ControlPanel({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Boutons de contrôle */}
|
||||
<div className="button-container">
|
||||
<button
|
||||
className={`control-button ${isConnected ? 'logout-button' : ''}`}
|
||||
<button
|
||||
className={`control-button ${isConnected ? 'logout-button' : ''}`}
|
||||
onClick={isConnected ? onLogout : onLogin}
|
||||
>
|
||||
{isConnected ? 'Se déconnecter' : 'Se connecter'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="button-container">
|
||||
<button
|
||||
className={`control-button ${!(isConnected) ? 'disabled' : ''}`}
|
||||
<button
|
||||
className={`control-button ${!(isConnected) ? 'disabled' : ''}`}
|
||||
onClick={onCreateProfile}
|
||||
disabled={!(isConnected)}
|
||||
>
|
||||
@ -64,8 +58,8 @@ function ControlPanel({
|
||||
</button>
|
||||
</div>
|
||||
<div className="button-container">
|
||||
<button
|
||||
className={`control-button ${!(isConnected) ? 'disabled' : ''}`}
|
||||
<button
|
||||
className={`control-button ${!(isConnected) ? 'disabled' : ''}`}
|
||||
onClick={onCreateFolder}
|
||||
disabled={!(isConnected)}
|
||||
>
|
||||
|
@ -77,7 +77,7 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.form-field input,
|
||||
.form-field input,
|
||||
.form-field select,
|
||||
.form-field textarea {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
@ -264,6 +264,7 @@
|
||||
0% {
|
||||
box-shadow: 0 0 0 2px rgba(103, 58, 183, 0.8);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@ -279,16 +280,13 @@
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-cancel, .btn-submit {
|
||||
|
||||
.btn-cancel,
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles spécifiques pour le composant FolderModal */
|
||||
/* Les styles génériques de modal sont fournis par Modal.css */
|
||||
|
||||
}
|
@ -27,10 +27,10 @@ const defaultFolder: FolderData = {
|
||||
stakeholders: []
|
||||
};
|
||||
|
||||
function FolderModal({
|
||||
folder = defaultFolder,
|
||||
onSave,
|
||||
onCancel,
|
||||
function FolderModal({
|
||||
folder = defaultFolder,
|
||||
onSave,
|
||||
onCancel,
|
||||
readOnly = false,
|
||||
isOpen,
|
||||
onClose
|
||||
@ -96,239 +96,239 @@ function FolderModal({
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Créer un nouveau dossier">
|
||||
<div className="folder-container">
|
||||
<form className="folder-form" onSubmit={handleSubmit}>
|
||||
<div className="form-section">
|
||||
<h3 className="section-title">Informations principales</h3>
|
||||
<div className="form-row">
|
||||
<div className="form-field">
|
||||
<label>
|
||||
Numéro de dossier <span className="required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="folderNumber"
|
||||
value={folderData.folderNumber}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={readOnly}
|
||||
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>
|
||||
)}
|
||||
<form className="folder-form" onSubmit={handleSubmit}>
|
||||
<div className="form-section">
|
||||
<h3 className="section-title">Informations principales</h3>
|
||||
<div className="form-row">
|
||||
<div className="form-field">
|
||||
<label>
|
||||
Numéro de dossier <span className="required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="folderNumber"
|
||||
value={folderData.folderNumber}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={readOnly}
|
||||
placeholder="ex: DOC-2025-001"
|
||||
/>
|
||||
</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>
|
||||
</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-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"
|
||||
<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}
|
||||
>
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
@ -7,18 +7,18 @@ import EventBus from '../sdk/EventBus';
|
||||
const formatTimestamp = (isoString: string): string => {
|
||||
const date = new Date(isoString);
|
||||
const now = new Date();
|
||||
|
||||
|
||||
// Obtenir les composants de l'heure
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
||||
|
||||
|
||||
// Vérifier si c'est aujourd'hui
|
||||
const isToday = date.getDate() === now.getDate() &&
|
||||
date.getMonth() === now.getMonth() &&
|
||||
date.getFullYear() === now.getFullYear();
|
||||
|
||||
const isToday = date.getDate() === now.getDate() &&
|
||||
date.getMonth() === now.getMonth() &&
|
||||
date.getFullYear() === now.getFullYear();
|
||||
|
||||
if (isToday) {
|
||||
// Format pour aujourd'hui: HH:MM:SS.ms
|
||||
return `${hours}:${minutes}:${seconds}.${ms}`;
|
||||
@ -31,7 +31,7 @@ const formatTimestamp = (isoString: string): string => {
|
||||
};
|
||||
|
||||
interface MessageConsoleProps {
|
||||
messages: {timestamp: string; data: any}[];
|
||||
messages: { timestamp: string; data: any }[];
|
||||
onClearMessages?: () => void;
|
||||
}
|
||||
|
||||
@ -41,18 +41,18 @@ interface MessageConsoleProps {
|
||||
* Affiche une liste de messages horodatés dans un panneau de console
|
||||
* S'abonne automatiquement aux messages du EventBus
|
||||
*/
|
||||
function MessageConsole({
|
||||
function MessageConsole({
|
||||
messages: externalMessages = [],
|
||||
onClearMessages = () => console.log('Vidage des messages demandé')
|
||||
}: MessageConsoleProps) {
|
||||
// É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
|
||||
const allMessages = [...externalMessages, ...localMessages].sort((a, b) => {
|
||||
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
|
||||
});
|
||||
|
||||
|
||||
// Gestionnaire de message reçu via EventBus
|
||||
const handleMessageReceived = (message: any) => {
|
||||
const newMessage = {
|
||||
@ -65,19 +65,19 @@ function MessageConsole({
|
||||
// S'abonner aux événements
|
||||
useEffect(() => {
|
||||
const unsubscribeMessageReceived = EventBus.getInstance().on('MESSAGE_RECEIVED', handleMessageReceived);
|
||||
|
||||
|
||||
// S'abonner à l'événement CLEAR_CONSOLE pour vider les messages locaux lors de la déconnexion
|
||||
const unsubscribeClearConsole = EventBus.getInstance().on('CLEAR_CONSOLE', () => {
|
||||
setLocalMessages([]);
|
||||
});
|
||||
|
||||
|
||||
// Se désabonner lors du nettoyage
|
||||
return () => {
|
||||
unsubscribeMessageReceived();
|
||||
unsubscribeClearConsole();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
// Fonction pour vider les messages locaux et externes
|
||||
const handleClearAll = () => {
|
||||
setLocalMessages([]);
|
||||
@ -85,7 +85,7 @@ function MessageConsole({
|
||||
};
|
||||
// Référence pour le conteneur de la console de messages
|
||||
const consoleContentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
||||
// Effet pour faire défiler automatiquement vers le bas quand de nouveaux messages arrivent
|
||||
useEffect(() => {
|
||||
if (consoleContentRef.current && allMessages.length > 0) {
|
||||
@ -100,8 +100,8 @@ function MessageConsole({
|
||||
<h3>Console</h3>
|
||||
<div className="console-controls">
|
||||
<span className="message-count">{allMessages.length} message(s)</span>
|
||||
<button
|
||||
className="clear-console-button"
|
||||
<button
|
||||
className="clear-console-button"
|
||||
onClick={handleClearAll}
|
||||
disabled={allMessages.length === 0}
|
||||
title="Vider les messages"
|
||||
@ -119,8 +119,8 @@ function MessageConsole({
|
||||
<li key={index} className="message-item">
|
||||
<div className="message-timestamp">{formatTimestamp(message.timestamp)}</div>
|
||||
<pre className="message-data">
|
||||
{typeof message.data === 'object'
|
||||
? JSON.stringify(message.data, null, 2)
|
||||
{typeof message.data === 'object'
|
||||
? JSON.stringify(message.data, null, 2)
|
||||
: String(message.data)}
|
||||
</pre>
|
||||
</li>
|
||||
|
@ -171,4 +171,4 @@
|
||||
word-break: break-all;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
@ -37,16 +37,16 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
}
|
||||
|
||||
const toggleBlock = (blockId: string) => {
|
||||
setExpandedBlocks(prev =>
|
||||
prev.includes(blockId)
|
||||
? prev.filter(id => id !== blockId)
|
||||
setExpandedBlocks(prev =>
|
||||
prev.includes(blockId)
|
||||
? prev.filter(id => id !== blockId)
|
||||
: [...prev, blockId]
|
||||
);
|
||||
};
|
||||
|
||||
const formatAddress = (address: string | number[] | undefined): string => {
|
||||
if (!address) return "Adresse non disponible";
|
||||
|
||||
|
||||
if (Array.isArray(address)) {
|
||||
// Si c'est un tableau de nombres, on le convertit en chaîne de caractères
|
||||
try {
|
||||
@ -66,7 +66,7 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
|
||||
const formatName = (name: string | number[] | undefined): string => {
|
||||
if (!name) return "Nom non disponible";
|
||||
|
||||
|
||||
if (Array.isArray(name)) {
|
||||
if (name.length === 1 && name[0] === 96) {
|
||||
return "`"; // Caractère spécial
|
||||
@ -88,30 +88,30 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
<div className="processes-viewer">
|
||||
<h2>Processus</h2>
|
||||
<p className="block-count">{Object.keys(processes).length} processus disponible(s)</p>
|
||||
|
||||
|
||||
<div className="block-list">
|
||||
{Object.entries(processes).map(([blockId, block]) => {
|
||||
const isExpanded = expandedBlocks.includes(blockId);
|
||||
const stateCount = block.states.length;
|
||||
// Le premier état est le plus récent
|
||||
|
||||
|
||||
return (
|
||||
<div key={blockId} className="block-item">
|
||||
<div
|
||||
className={`block-header ${isExpanded ? 'expanded' : ''}`}
|
||||
<div
|
||||
className={`block-header ${isExpanded ? 'expanded' : ''}`}
|
||||
onClick={() => toggleBlock(blockId)}
|
||||
>
|
||||
<div className="block-id">{blockId.substring(0, 8)}...{blockId.substring(blockId.length - 4)}</div>
|
||||
<div className="block-state-count">{stateCount} état(s)</div>
|
||||
<div className="block-toggle">{isExpanded ? '▼' : '▶'}</div>
|
||||
</div>
|
||||
|
||||
|
||||
{isExpanded && (
|
||||
<div className="block-details">
|
||||
<div className="block-complete-id">
|
||||
<strong>ID complet:</strong> {blockId}
|
||||
</div>
|
||||
|
||||
|
||||
{block.states.map((state, index) => (
|
||||
<div key={`${blockId}-state-${index}`} className="state-item">
|
||||
<h4>État {index + 1}</h4>
|
||||
@ -121,7 +121,7 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
<div className="state-detail">
|
||||
<strong>Commited dans:</strong> {state.commited_in}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="state-public-data">
|
||||
<h5>Données publiques</h5>
|
||||
<div className="public-data-item">
|
||||
@ -131,7 +131,7 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
<div className="public-data-item">
|
||||
<strong>Adresses associées:</strong>
|
||||
<ul className="address-list">
|
||||
{Array.isArray(state.public_data.pairedAddresses) ?
|
||||
{Array.isArray(state.public_data.pairedAddresses) ?
|
||||
(typeof state.public_data.pairedAddresses[0] === 'string' ? (
|
||||
(state.public_data.pairedAddresses as string[]).map((addr, i) => (
|
||||
<li key={i}>{addr}</li>
|
||||
|
@ -171,4 +171,4 @@
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ export default class EventBus {
|
||||
private static instance: EventBus;
|
||||
private listeners: Record<string, Array<(...args: any[]) => void>> = {};
|
||||
|
||||
private constructor() {}
|
||||
private constructor() { }
|
||||
|
||||
public static getInstance(): EventBus {
|
||||
if (!EventBus.instance) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user