From e4e26fb64eb398157e576c45ad01a8c35b19bd8a Mon Sep 17 00:00:00 2001
From: Etienne0101
Date: Thu, 15 Feb 2024 10:48:48 +0100
Subject: [PATCH 1/6] test server deploy
---
pages/api/references.js | 39 +++++++++++++++++
pages/references/[reference].js | 58 ++++++++++++++++++++++++++
public/sitedata/references_catalog.csv | 36 ++++++++++++++++
3 files changed, 133 insertions(+)
create mode 100644 pages/api/references.js
create mode 100644 pages/references/[reference].js
create mode 100644 public/sitedata/references_catalog.csv
diff --git a/pages/api/references.js b/pages/api/references.js
new file mode 100644
index 00000000..607184bb
--- /dev/null
+++ b/pages/api/references.js
@@ -0,0 +1,39 @@
+// pages/api/references.js
+import fs from 'fs';
+import path from 'path';
+import { parse } from 'csv-parse';
+
+export default async function handler(req, res) {
+ const { query: { action, id } } = req;
+
+ const filePath = path.join(process.cwd(), 'public', 'sitedata', 'references_catalog.csv');
+ const fileContents = fs.readFileSync(filePath, 'utf8');
+
+ const records = await new Promise((resolve, reject) => {
+ parse(fileContents, {
+ columns: headers => headers.map(header => header.trim().replace(/^\uFEFF/, '').toLowerCase()), // Normalize headers
+ trim: true,
+ skip_empty_lines: true
+ }, (err, output) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(output);
+ }
+ });
+ });
+
+ if (action === 'list') {
+ res.status(200).json(records);
+ } else if (action === 'get' && id) {
+ // Now accessing the id field without the special character
+ const reference = records.find(record => record.id === id.trim().toLowerCase());
+ if (reference) {
+ res.status(200).json(reference);
+ } else {
+ res.status(404).json({ message: 'Reference not found' });
+ }
+ } else {
+ res.status(400).json({ message: 'Invalid action or missing id' });
+ }
+}
diff --git a/pages/references/[reference].js b/pages/references/[reference].js
new file mode 100644
index 00000000..d7dade28
--- /dev/null
+++ b/pages/references/[reference].js
@@ -0,0 +1,58 @@
+// pages/references/[reference].js
+import { useRouter } from 'next/router';
+import Layout from '../../components/layout';
+
+export async function getServerSideProps(context) {
+ const { params, req } = context;
+ const { reference } = params;
+
+ // Construct the URL based on the request's headers
+ // This assumes your Next.js app and API are hosted on the same domain
+ const protocol = req.headers['x-forwarded-proto'] || 'http';
+ const host = req.headers.host;
+ const apiUrl = `${protocol}://${host}/api/references?action=get&id=${reference}`;
+
+ const res = await fetch(apiUrl);
+ const data = await res.json();
+
+ if (!data || res.status === 404) {
+ return {
+ notFound: true,
+ };
+ }
+
+ return {
+ props: {
+ referenceData: data,
+ },
+ };
+ }
+
+export default function ReferencePage({ referenceData }) {
+ const router = useRouter();
+ if (router.isFallback) {
+ return Loading...
;
+ }
+
+ // Destructure data for easier access
+ const { title, team, partners, partnerDescription, partnerImage, mission, actions } = referenceData;
+
+ return (
+
+
+
{title}
+
+
Team
+
{team.split(',').join(', ')}
+
Partners
+
{partners}
+
Partner Description
+
{partnerDescription}
+
Mission
+
{mission}
+
Actions
+
{actions}
+
+
+ );
+}
diff --git a/public/sitedata/references_catalog.csv b/public/sitedata/references_catalog.csv
new file mode 100644
index 00000000..b71f4e17
--- /dev/null
+++ b/public/sitedata/references_catalog.csv
@@ -0,0 +1,36 @@
+id,title,team,partners,partner-description,partner-image,mission,actions
+tableaux-issy,Construction de tableaux de bord,"etienne-pichot-damon,samuel-goeta",issy-moulineaux,"Ville pionnière en France en matière d’open data, Issy-les-Moulineaux a ouvert un portail en 2012, et l’alimente régulièrement en nouvelles données",/images/partners/issy-moulineaux.png,"Datactivist a accompagné la collectivité dans la préparation de ses données pour réaliser des tableaux de bord partagés.
+
+Dans la plupart des administrations, les différents services maintiennent des tableaux de suivis de leur activité. Problème : ces tableaux sont dans des formats différents selon les services. Chaque année, les indicateurs sont à recalculer, et donc le travail de mise à jour prend du temps.
+
+La ville d’Issy-les-Moulineaux s’est donc lancée dans un premier projet de standardisation de données, avec comme objectif concret de construire des tableaux de bord partagés et actualisés automatiquement.
+
+Le but : faciliter la remontée des données, éviter les doublons de saisies, et permettre aux élus, aux agents et aux citoyens d’accéder en toute simplicité à des indicateurs détaillés sur les services de la ville.
+","Formation et ateliers avec les 7 services pilotes pour partager la stratégie et collecter des données
+
+
+Prototypage des tableaux de bord avec les services
+
+Cartographie des données à utiliser
+
+Structuration, formatage et publication de 50 jeux de données qui alimentent les tableaux de bord
+
+Rédaction d’un guide
+méthodologique, publié sous la forme d’un billet de blog"
+analyse-respire,Analyse et publication de données,"sylvain-lapoix,joel-gombin,mathieu-morey",association-respire,"Respire est une association de citoyens, fondée en février 2011, dédiée à l’amélioration de la qualité de l’air
+",/images/partners/association-respire.png,"Pour sensibiliser le grand public à l’exposition des plus jeunes à la pollution, Datactivist a accompagné Respire dans sa campagne: “De l’air pour nos enfants”.
+
+De l’audit de données à la publication des résultats en open data, l’équipe de Datactivist a assuré toutes les tâches liées à la donnée dans la production des supports de campagne :
+- une base de données historique et géolocalisée de la qualité de l’air;
+- une carte de la pollution aux abords des 12530 crèches, écoles, collèges et lycées d’Île-de-France sur 5 ans;
+- un rapport synthétique à destination du public et des élu·e·s.
+Résultat : une large reprise médiatique, des réactions politiques et des sollicitations d’associations de parents d’élèves pour faire progresser ce dossier auprès des pouvoirs publics.
+
+","Audit de données
+
+Réalisation d’une cartographie de la pollution de l’air dans les écoles
+
+Publication des données en open data
+
+Production des supports de communication pour la campagne"
+,,,,,,,
\ No newline at end of file
From cbf06d3d4dc591dd2c624c7aa7b8ba7dcc53969a Mon Sep 17 00:00:00 2001
From: Etienne0101
Date: Thu, 15 Feb 2024 10:52:50 +0100
Subject: [PATCH 2/6] Update [reference].js
---
pages/references/[reference].js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pages/references/[reference].js b/pages/references/[reference].js
index d7dade28..cfaeff2d 100644
--- a/pages/references/[reference].js
+++ b/pages/references/[reference].js
@@ -1,6 +1,6 @@
// pages/references/[reference].js
import { useRouter } from 'next/router';
-import Layout from '../../components/layout';
+import Layout from '../../components/Layout';
export async function getServerSideProps(context) {
const { params, req } = context;
From 79e87f97737c8c19167608679002dae8c1555efe Mon Sep 17 00:00:00 2001
From: Etienne0101
Date: Fri, 16 Feb 2024 16:09:23 +0100
Subject: [PATCH 3/6] add references
---
components/Layout.js | 4 +-
components/docs/DocsMetadata.js | 32 +-
components/docs/MarkdownDocs.js | 104 +-
components/nav/Partners.js | 57 +-
components/nav/PartnersGallery.js | 2 +-
components/nav/ReferenceCard.js | 23 +
package.json | 2 +
pages/api/authors-list.js | 37 +
pages/api/references.js | 11 +-
pages/authors/[author].js | 70 +-
pages/partners/[partner].js | 92 +-
pages/references/[reference].js | 213 +-
public/icons/action.svg | 7 +
public/images/docs/mediation-algo/1.png | Bin 0 -> 231452 bytes
public/images/docs/mediation-algo/2.png | Bin 0 -> 176504 bytes
public/images/docs/mediation-algo/cover.png | Bin 0 -> 466767 bytes
public/images/partners/ademe.png | Bin 0 -> 8614 bytes
.../images/partners/association-respire.png | Bin 0 -> 18594 bytes
public/images/partners/infrabel.png | Bin 0 -> 14591 bytes
public/images/partners/issy-moulineaux.png | Bin 0 -> 15291 bytes
public/images/references/algo-mel.png | Bin 0 -> 333548 bytes
public/images/references/analyse-respire.png | Bin 0 -> 537643 bytes
public/images/references/hackathon-ademe.png | Bin 0 -> 521400 bytes
.../images/references/hackathon-infrabel.png | Bin 0 -> 775698 bytes
public/images/references/tableaux-issy.png | Bin 0 -> 82618 bytes
public/sitedata/authors.csv | 35 +
public/sitedata/docs_catalog.csv | 145 +-
public/sitedata/partners.json | 10 +
public/sitedata/references_catalog.csv | 69 +-
styles/Authors.module.css | 60 +-
styles/Cards.module.css | 3 +
styles/DocsMetadata.module.css | 4 +-
styles/Layout.module.css | 27 +-
styles/MarkdownContent.module.css | 50 +
styles/ReferenceCard.module.css | 56 +
styles/References.module.css | 207 +
yarn.lock | 3744 +++++++++--------
37 files changed, 2992 insertions(+), 2072 deletions(-)
create mode 100644 components/nav/ReferenceCard.js
create mode 100644 pages/api/authors-list.js
create mode 100644 public/icons/action.svg
create mode 100644 public/images/docs/mediation-algo/1.png
create mode 100644 public/images/docs/mediation-algo/2.png
create mode 100644 public/images/docs/mediation-algo/cover.png
create mode 100644 public/images/partners/ademe.png
create mode 100644 public/images/partners/association-respire.png
create mode 100644 public/images/partners/infrabel.png
create mode 100644 public/images/partners/issy-moulineaux.png
create mode 100644 public/images/references/algo-mel.png
create mode 100644 public/images/references/analyse-respire.png
create mode 100644 public/images/references/hackathon-ademe.png
create mode 100644 public/images/references/hackathon-infrabel.png
create mode 100644 public/images/references/tableaux-issy.png
create mode 100644 public/sitedata/authors.csv
create mode 100644 styles/ReferenceCard.module.css
create mode 100644 styles/References.module.css
diff --git a/components/Layout.js b/components/Layout.js
index 32a31ff4..71e550dc 100644
--- a/components/Layout.js
+++ b/components/Layout.js
@@ -67,8 +67,8 @@ const Layout = ({ children }) => {
diff --git a/components/docs/DocsMetadata.js b/components/docs/DocsMetadata.js
index 676c42a7..b2f745d1 100644
--- a/components/docs/DocsMetadata.js
+++ b/components/docs/DocsMetadata.js
@@ -87,6 +87,22 @@ const DocsMetadata = ({ metadata }) => {
)}
+ {license === 'ccbysa' && (
+
+
+
+
+ 🔄 Vous pouvez partager et adapter ce contenu librement, à
+ condition de le créditer et de le partager sous une licence
+ compatible.
+
+
+
+ )}
{tagsArray && (
@@ -111,22 +127,6 @@ const DocsMetadata = ({ metadata }) => {
/>
)}
- {license === 'ccbysa' && (
-
-
-
-
- 🔄 Vous pouvez partager et adapter ce contenu librement, à
- condition de le créditer et de le partager sous une licence
- compatible.
-
-
-
- )}
);
};
diff --git a/components/docs/MarkdownDocs.js b/components/docs/MarkdownDocs.js
index 1f916cfa..1c7cb504 100644
--- a/components/docs/MarkdownDocs.js
+++ b/components/docs/MarkdownDocs.js
@@ -198,7 +198,6 @@ const MarkdownDocs = ({ filename }) => {
fetchMarkdownContent();
}, [filename]);
-
useEffect(() => {
if (content) {
@@ -211,11 +210,61 @@ const MarkdownDocs = ({ filename }) => {
}
}, [content]);
+ useEffect(() => {
+ const fetchReferencesDetails = async () => {
+ if (metadata.references_catalog) {
+ // Split the references_catalog string into individual IDs
+ const referenceIds = metadata.references_catalog.split(',');
+ // Fetch details for each reference
+ const referencesPromises = referenceIds.map((id) =>
+ fetch(`/api/referenceDetails?id=${id.trim()}`).then((response) =>
+ response.json(),
+ ),
+ );
+ Promise.all(referencesPromises)
+ .then((referencesDetails) => {
+ // Process and display these details as needed
+ console.log(referencesDetails); // This will log the details for each reference
+ // Set state here if you're storing these details in state
+ })
+ .catch((error) =>
+ console.error('Error fetching references details:', error),
+ );
+ }
+ };
+
+ fetchReferencesDetails();
+ }, [metadata.references_catalog]);
+
+ const renderReferences = () => {
+ if (!metadata.references_catalog) return null;
+
+ const referenceIds = metadata.references_catalog.split(',');
+ const referenceTitles = metadata['reference-title']
+ ? metadata['reference-title'].split(',')
+ : [];
+
+ return referenceIds.map((id, index) => {
+ const title = referenceTitles[index]
+ ? referenceTitles[index].trim()
+ : 'Reference';
+ return (
+
+ {title}
+
+ );
+ });
+ };
+
return (
- {metadata?.title}
-
+ {metadata?.title}
+
{
borderRadius: '20px',
}}
>
-
+
{
{metadata?.description}
+ {(metadata.partners && metadata.partners.length > 0) || metadata.references_catalog ? (
+
+ {metadata.partners && metadata.partners.length > 0 && (
+
+ )}
+ {metadata.references_catalog && (
+
+
Référence(s)
+
{renderReferences()}
+
+ )}
+
+ ) : null}
{createContentElements(content)}
- {metadata?.partners && (
-
-
-
- {' '}
- Ce contenu a été défini et testé avec{' '}
- {metadata.partners.length > 1
- ? 'plusieurs partenaires :'
- : 'un partenaire :'}
- {' '}
-
-
-
-
- )}
);
+
};
-export default MarkdownDocs;
\ No newline at end of file
+export default MarkdownDocs;
diff --git a/components/nav/Partners.js b/components/nav/Partners.js
index b4c240ac..d60c9142 100644
--- a/components/nav/Partners.js
+++ b/components/nav/Partners.js
@@ -7,7 +7,6 @@ const Partners = ({ partnersIds, largeText = false }) => {
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
- // Ensure partnersIds are in array format
const validPartnersIds = Array.isArray(partnersIds)
? partnersIds
: typeof partnersIds === 'string'
@@ -32,29 +31,39 @@ const Partners = ({ partnersIds, largeText = false }) => {
}
return (
-
- {validPartnersIds.map((id) => {
- const partner = partnersData[id];
- if (partner) {
- return (
-
-
-
handlePartnerClick(id)}
- >
- {partner.name}
-
-
- );
- }
- return null;
- })}
+
+
Partenaire(s)
+
+
+ {validPartnersIds.map((id) => {
+ const partner = partnersData[id];
+ if (partner) {
+ return (
+
handlePartnerClick(id)} // Déplacez onClick ici pour englober tout le conteneur
+ style={{ cursor: 'pointer' }} // Optionnel, pour montrer visuellement que c'est cliquable
+ >
+
+
+
+ );
+ }
+ return null;
+ })}
+
+
);
};
diff --git a/components/nav/PartnersGallery.js b/components/nav/PartnersGallery.js
index 7e97c908..1218fcd6 100644
--- a/components/nav/PartnersGallery.js
+++ b/components/nav/PartnersGallery.js
@@ -35,7 +35,7 @@ const PartnerGallery = () => {
const partnersArray = Object.entries(partnersData).map(([id, partner]) => ({id, ...partner}));
return (
-
+
{partnersArray.map((partner) => (
router.push(`/partners/${partner.id}`)}>
diff --git a/components/nav/ReferenceCard.js b/components/nav/ReferenceCard.js
new file mode 100644
index 00000000..d01f743e
--- /dev/null
+++ b/components/nav/ReferenceCard.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import Link from 'next/link';
+import styles from '../../styles/ReferenceCard.module.css';
+
+const ReferenceCard = ({ id, title, partnerName, partnerImage }) => {
+ return (
+
+
{/* Apply styles directly to this div */}
+
+
+
+
+
+
{title}
+
{partnerName}
+
+
+
+
+ );
+};
+
+export default ReferenceCard;
diff --git a/package.json b/package.json
index 813a49ba..d5eca4dc 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,9 @@
"fuse.js": "^6.6.2",
"graphql": "^16.6.0",
"gray-matter": "^4.0.3",
+ "html2canvas": "^1.4.1",
"js-yaml": "^4.1.0",
+ "jspdf": "^2.5.1",
"markdown-it": "^13.0.1",
"marked": "^4.3.0",
"moment": "^2.29.4",
diff --git a/pages/api/authors-list.js b/pages/api/authors-list.js
new file mode 100644
index 00000000..40a8fb85
--- /dev/null
+++ b/pages/api/authors-list.js
@@ -0,0 +1,37 @@
+// pages/api/authors.js
+import fs from 'fs';
+import path from 'path';
+import csv from 'csv-parser';
+
+export default function handler(req, res) {
+ if (req.method !== 'GET') {
+ res.status(405).send({ message: 'Only GET requests are allowed' });
+ return;
+ }
+
+ const { id } = req.query; // Extract the 'id' from query parameters
+ const filePath = path.join(process.cwd(), 'public', 'sitedata', 'authors.csv');
+ const authors = [];
+
+fs.createReadStream(filePath)
+ .pipe(csv())
+ .on('data', (data) => {
+ // Omitting tags from the data object
+ const { ...rest } = data;
+ authors.push(rest);
+ })
+ .on('end', () => {
+ if (id) {
+ // Filter the authors array for the author with the matching id
+ const filteredAuthors = authors.filter(author => author['\ufeffid'] === id || author['id'] === id); // Handle UTF-8 BOM if present
+ if (filteredAuthors.length) {
+ res.status(200).json(filteredAuthors[0]); // Send the first matching author
+ } else {
+ res.status(404).send({ message: 'Author not found' });
+ }
+ } else {
+ // If no id is provided, return all authors
+ res.status(200).json(authors);
+ }
+ });
+}
diff --git a/pages/api/references.js b/pages/api/references.js
index 607184bb..ecb33cae 100644
--- a/pages/api/references.js
+++ b/pages/api/references.js
@@ -4,7 +4,7 @@ import path from 'path';
import { parse } from 'csv-parse';
export default async function handler(req, res) {
- const { query: { action, id } } = req;
+ const { query: { action, id, partners: queryPartners } } = req; // Renommez `partners` en `queryPartners` lors de la déstructuration pour éviter les conflits
const filePath = path.join(process.cwd(), 'public', 'sitedata', 'references_catalog.csv');
const fileContents = fs.readFileSync(filePath, 'utf8');
@@ -33,7 +33,12 @@ export default async function handler(req, res) {
} else {
res.status(404).json({ message: 'Reference not found' });
}
- } else {
- res.status(400).json({ message: 'Invalid action or missing id' });
+ } else if (action === 'get' && queryPartners) { // Utilisez `queryPartners` ici
+ const foundPartners = records.find(record => record.partners === queryPartners.trim().toLowerCase()); // `foundPartners` pour éviter le conflit
+ if (foundPartners) {
+ res.status(200).json(foundPartners);
+ } else {
+ res.status(404).json({ message: 'Partners not found' });
+ }
}
}
diff --git a/pages/authors/[author].js b/pages/authors/[author].js
index 9c9223ca..f447ac1b 100644
--- a/pages/authors/[author].js
+++ b/pages/authors/[author].js
@@ -5,6 +5,7 @@ import styles from '../../styles/Authors.module.css';
import Cards from '../../components/nav/Cards';
import Gallery from '../../components/nav/Gallery';
import Layout from '../../components/Layout';
+import ReferenceCard from '../../components/nav/ReferenceCard';
const AuthorPage = () => {
const router = useRouter();
@@ -13,6 +14,35 @@ const AuthorPage = () => {
const [authorData, setAuthorData] = useState(null);
const [partnerData, setPartnerData] = useState(null);
const [authorDocs, setAuthorDocs] = useState([]);
+ const [referencesDetails, setReferencesDetails] = useState([]); // To store details of each reference
+
+ useEffect(() => {
+ if (author) {
+ fetch(`/api/authors-list?id=${author}`)
+ .then((response) => response.json())
+ .then((data) => {
+ setAuthorData(data);
+ // Fetch details for each reference if they exist
+ if (data && data.references_catalog) {
+ const references = data.references_catalog.split(',');
+ references.forEach((ref) => {
+ fetch(`/api/references?action=get&id=${ref.trim()}`)
+ .then((response) => response.json())
+ .then((detail) => {
+ setReferencesDetails((prevDetails) => [
+ ...prevDetails,
+ detail,
+ ]);
+ });
+ });
+ }
+ })
+ .catch((error) => {
+ console.error('Error fetching author data:', error);
+ setAuthorData(null);
+ });
+ }
+ }, [author]);
useEffect(() => {
fetch(`/sitedata/authors.json`)
@@ -67,15 +97,15 @@ const AuthorPage = () => {
return (
-
- {authorData.organisation === 'datactivist' ? (
-
- ...Notre équipe
-
- ) : (
- '...Nos contributeurs'
- )}
-
+
+ {authorData.organisation === 'datactivist' ? (
+
+ ...Notre équipe
+
+ ) : (
+ '...Nos contributeurs'
+ )}
+
@@ -116,7 +146,9 @@ const AuthorPage = () => {
{authorDocs.length > 0 && (
<>
-
Liste des contributions
+
+ Contributions
+
{
>
)}
+ {referencesDetails.length > 0 && (
+
+
+ Références
+
+
+ {referencesDetails.map((reference, index) => (
+
+ ))}
+
+
+ )}
);
diff --git a/pages/partners/[partner].js b/pages/partners/[partner].js
index b9dfa8a7..2ea1b2b0 100644
--- a/pages/partners/[partner].js
+++ b/pages/partners/[partner].js
@@ -4,6 +4,7 @@ import styles from '../../styles/Authors.module.css';
import Cards from '../../components/nav/Cards';
import Gallery from '../../components/nav/Gallery';
import Layout from '../../components/Layout';
+import ReferenceCard from '../../components/nav/ReferenceCard'; // Import the ReferenceCard component
const PartnerPage = () => {
const router = useRouter();
@@ -13,14 +14,15 @@ const PartnerPage = () => {
const [partnerDocuments, setPartnerDocuments] = useState([]);
const [partnerContributors, setPartnerContributors] = useState([]);
const [partnerProducts, setPartnerProducts] = useState([]);
+ const [partnerReferences, setPartnerReferences] = useState([]); // State to store references
useEffect(() => {
if (partner) {
fetch(`/sitedata/products-catalog.json`)
- .then(response => response.json())
- .then(data => {
- const productsByPartner = data.filter(product =>
- product.partners && product.partners.includes(partner)
+ .then((response) => response.json())
+ .then((data) => {
+ const productsByPartner = data.filter(
+ (product) => product.partners && product.partners.includes(partner),
);
setPartnerProducts(productsByPartner);
});
@@ -29,8 +31,8 @@ const PartnerPage = () => {
useEffect(() => {
fetch('/sitedata/authors.json')
- .then(response => response.json())
- .then(data => {
+ .then((response) => response.json())
+ .then((data) => {
const contributorsOfPartner = Object.entries(data)
.filter(([author]) => author.organisation === partner)
.map(([id, author]) => ({ ...author, id }));
@@ -40,8 +42,8 @@ const PartnerPage = () => {
useEffect(() => {
fetch(`/sitedata/partners.json`)
- .then(response => response.json())
- .then(data => {
+ .then((response) => response.json())
+ .then((data) => {
if (Object.prototype.hasOwnProperty.call(data, partner)) {
const partnerInfo = data[partner];
setPartnerData(partnerInfo);
@@ -57,18 +59,48 @@ const PartnerPage = () => {
.then((response) => response.json())
.then((data) => {
const docsByPartner = data.filter(
- (doc) => doc.partners && doc.partners.includes(partner)
+ (doc) => doc.partners && doc.partners.includes(partner),
);
setPartnerDocuments(docsByPartner);
});
}, [partner]);
+ // Right after fetching and before setting the state
+ useEffect(() => {
+ if (partner) {
+ fetch(`/api/references?action=get&partners=${partner}`)
+ .then((response) => response.json())
+ .then((data) => {
+ // Check if data is an object and not an array
+ if (!Array.isArray(data) && typeof data === 'object') {
+ // Wrap the object into an array
+ setPartnerReferences([data]);
+ } else if (Array.isArray(data)) {
+ // Directly set the data if it's already an array
+ setPartnerReferences(data);
+ } else {
+ console.error('Unexpected data format received from API', data);
+ setPartnerReferences([]);
+ }
+ })
+ .catch((error) => {
+ console.error('Error fetching partner references:', error);
+ setPartnerReferences([]); // Reset to empty array in case of error
+ });
+ }
+ }, [partner]);
+
const handleCardClick = (productId) => {
router.push(`/products/${productId}`);
};
if (partnerData === null) {
- return Cette organisation n‘est pas encore partenaire ou cliente 🥲 Mais n‘hésitez pas à lui parler de nous !
;
+ return (
+
+ Cette organisation n‘est pas encore partenaire ou cliente 🥲 Mais
+ n‘hésitez pas à lui parler de nous !{' '}
+
+ );
}
if (!partnerData) {
@@ -81,11 +113,15 @@ const PartnerPage = () => {
-
+
{partnerData.name}
-
+
{partnerData.description}
@@ -94,8 +130,16 @@ const PartnerPage = () => {
Contributeurs
{partnerContributors.map((author) => (
-
router.push(`/authors/${author.id}`)}>
-
+
router.push(`/authors/${author.id}`)}
+ >
+
{author.name}
))}
@@ -105,7 +149,8 @@ const PartnerPage = () => {
-
+
Publications
+
{
showDate={false}
showPartners={false}
/>
- {/* Render partner products using the same logic */}
({ ...product, productId: product.name }))}
+ items={partnerProducts.map((product) => ({
+ ...product,
+ productId: product.name,
+ }))}
onClick={handleCardClick}
tagRoute="products"
showDate={false}
showPartners={false}
/>
+
Références
+ {Array.isArray(partnerReferences) &&
+ partnerReferences.map((reference) => (
+
+ ))}{' '}
diff --git a/pages/references/[reference].js b/pages/references/[reference].js
index cfaeff2d..49c7a965 100644
--- a/pages/references/[reference].js
+++ b/pages/references/[reference].js
@@ -1,58 +1,205 @@
// pages/references/[reference].js
import { useRouter } from 'next/router';
import Layout from '../../components/Layout';
+import styles from '../../styles/References.module.css';
+import FilteredDocsDisplay from '../../components/docs/FilteredDocsDisplay';
-export async function getServerSideProps(context) {
- const { params, req } = context;
- const { reference } = params;
-
- // Construct the URL based on the request's headers
- // This assumes your Next.js app and API are hosted on the same domain
- const protocol = req.headers['x-forwarded-proto'] || 'http';
- const host = req.headers.host;
- const apiUrl = `${protocol}://${host}/api/references?action=get&id=${reference}`;
+function formatDate(dateISO) {
+ const options = { year: 'numeric', month: 'long' };
+ const date = new Date(dateISO);
+ return date.toLocaleDateString('fr-FR', options);
+ }
+
+const renderTextWithLineBreaks = (text) => {
+ return text.split('\n').map((line, index) => {
+ // Remplace les tirets en début de ligne par un point de puce
+ const updatedLine = line.replace(/^\s*-\s*/, '✔︎ ');
+ return (
+
+ {updatedLine}
+
+
+ );
+ });
+};
+
+const renderActionsWithIcons = (actionsText) => {
+ return actionsText
+ .split('\n')
+ .map((action, index) => {
+ // Vérifie si la ligne n'est pas vide avant de l'ajouter
+ if (action.trim() !== '') {
+ return (
+
+
+
{action}
+
+ );
+ }
+ return null; // Pour les lignes vides, ne retourne rien
+ })
+ .filter(Boolean); // Filtre les éléments null pour éviter de les rendre
+};
+
+export async function getServerSideProps(context) {
+ const { params, req } = context;
+ const { reference } = params;
+
+ const protocol = req.headers['x-forwarded-proto'] || 'http';
+ const host = req.headers.host;
+ const apiUrl = `${protocol}://${host}/api/references?action=get&id=${reference}`;
+
+ try {
const res = await fetch(apiUrl);
- const data = await res.json();
-
- if (!data || res.status === 404) {
- return {
- notFound: true,
- };
+ if (!res.ok) {
+ // Vérifie si la réponse est une erreur
+ throw new Error(
+ `Erreur lors de la récupération des données : ${res.status}`,
+ );
}
-
+ const data = await res.json();
+
+ // Retourne les données récupérées dans les props, y compris l'identifiant des documents liés si disponible
return {
props: {
referenceData: data,
+ docsCatalog: data.docs_catalog || null, // Utilisez null ou une valeur par défaut si docs_catalog n'est pas présent
},
};
+ } catch (error) {
+ console.error(error);
+ // Gère le cas où la récupération échoue ou retourne une erreur
+ return {
+ notFound: true,
+ };
}
+}
-export default function ReferencePage({ referenceData }) {
+export default function ReferencePage({ referenceData, docsCatalog }) {
const router = useRouter();
if (router.isFallback) {
return
Loading...
;
}
- // Destructure data for easier access
- const { title, team, partners, partnerDescription, partnerImage, mission, actions } = referenceData;
+ const {
+ title,
+ partners,
+ mission,
+ actions,
+ 'team-names': teamNames,
+ 'team-images': teamImages,
+ 'reference-link': referenceLink,
+ 'date-debut': dateDebutISO,
+ duree,
+ team,
+ } = referenceData;
+ const dateDebutFormatted = formatDate(dateDebutISO); // Formatez ici après déstructuration
+
+ const partnerDescription = referenceData['partner-description'];
+ const partnerImage = referenceData['partner-image'];
+ const partnerName = referenceData['partner-name'];
+ // Supposons que teamImages et teamNames soient des tableaux correspondants
+ const teamImagesArray = teamImages.split(',');
+ const teamArray = team.split(',');
+ const teamNamesArray = teamNames.split(',');
+
+ const referenceImageSrc = `/images/references/${router.query.reference}.png`;
return (
-
-
{title}
-
-
Team
-
{team.split(',').join(', ')}
-
Partners
-
{partners}
-
Partner Description
-
{partnerDescription}
-
Mission
-
{mission}
-
Actions
-
{actions}
-
+
+
{title}
+
+
+
📆 {dateDebutFormatted}
+
⏱️ {duree}
+
+
+
+
+
Équipe
+
+ {teamImagesArray.map((imgSrc, index) => {
+ // Ici, index est bien défini
+ const teamMemberName = teamNamesArray[index].trim(); // Utilisation de index pour accéder au nom
+ const imagePath = imgSrc.trim();
+ const teamMemberSlug = teamArray[index].trim(); // Assurez-vous que teamArray[index] est correct
+
+ return (
+
+ );
+ })}
+
+
+
+
🎯 Mission
+
+ {renderTextWithLineBreaks(mission)}
+
+
+
+
+
{
+ e.target.style.display = 'none';
+ }}
+ />
+{docsCatalog && docsCatalog.length > 0 && (
+
+
Publications liées
+
+
+)}
+
);
}
diff --git a/public/icons/action.svg b/public/icons/action.svg
new file mode 100644
index 00000000..3f55edeb
--- /dev/null
+++ b/public/icons/action.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/public/images/docs/mediation-algo/1.png b/public/images/docs/mediation-algo/1.png
new file mode 100644
index 0000000000000000000000000000000000000000..f99d0a5d62acda9661f7e7c7d9cba7d5ee187cbf
GIT binary patch
literal 231452
zcmeFZWmH_vvM`JV3&DfC4J0@TGH8GRLtwBZxVyvPgG+!R=s=L*5+qn~w;+SN!{7vm
z;1
~AgS66ja4@?y-{{)u`7X=072}nU!9R&q_4h03Z
z5gYU24b7VmLJtoC)-p1xAQ>43Rfwa7wVgQ%3VWQXkW$}TY
zi_;!D`eDN4ni(dh96p06>la}W@?OlHu{Yf8&rmD3Q05vpSsyW6P%`M|VQ)(@+@gl3
z;~V>Zg4vEhIF9Lr$Ipo`f{rV#YaO={m2djYcxJglIETKVdJJg^=WuRchh6mRH>
zj~gwHaN670R1MrJbW$C$BCqTYDIM%^bBZ8JJql6^Jjy{zJPIF5SIU6Iq`dbD0i>c^
z;`wwRm
zBcqnf-@h$iVq#u^@bLTr_rHI8kNy7bPmX@@KujkFER@12nk4FC4
zj;y(hDa6{z)!Na4;ZM89CXQ~d;!I3`I{Me|ALBIlwElNb4laLB>tTXCe;_=3+`K&h
zYWo3I?9W@Esf?w>f`u_v+?;igHQ|sTDBEm2K6Z1bH|6j~^
zF6Iy!NBf5^T_yg_nZJ?$6Zkix7|)-X{|6=hLFT{SKG0bLSB&RhJd?nk3HtH{1w{%4
zB>U>EC+dES^|zY2cguAnK8Gzwlbs7lo1!LG#-gduudrW?SfRY&)ndRzM
z$`AuSDN1bW!XclE)m3}=1Y^Gk_HtvT5@zuerUj(1FlvXBeV$S;Zd$%X|!|}Av*?c;QzQ?dB_=E9dTx3
zkfME%VpPTcpEp8wxPIt=13c7DBSL#!IfC6wtnptE|FqV}P5ocHWycuC4rH3M#4v+#
z{I_ATV?>xGJ^7!5`D=uhAyRSs0%(<8{|o$egP`LR1bac%uR({|n@XS4)-b4Q*C}#R;J{%t6WCm)Z&(x7dZv}C
zEO)S@(l*W}g9w+5aJX(p@{$=@`iqC~9h?}y_MR!HuM6%BJ{#kwY|>wiCfU|-7o)2q
zxmmppktq62hD;iGml`l+^g@=Nnv$WjvUuA{7ySU0a)4D(HZyU+e4*sM%>+KdzGL0<
zV;6-+q#I3cycTx7Zw`z*{-{afiRX-*pzcP&oq^`B>(7t|{2sdbd1dccLa#lBZj*Ib
zQEpmEVgI-URpC+Hud5h<;Eo=PvAucP~vL)v3=sxLuQY`$FkN@KJ<(?ppD
z&6guQM6Xg(?@ksSO2q2wu*?_td7xL8$fTANF6*!Ne_Iv8l7ALK`dZe82cgbj!v3|}
zcGG?Fg~yLf{w<`%VW69%^=IW1*A?eNw-A{P*ZSiRP3vXr1bnfwCv|!}3uKt2K{=)+
zog)iQD8`$&RPeel5g3B!GwI1Y0dFSPeD+W@EV^r+RA$)`itS3Dh`C0ylI1!BL`?g>
zRk8*O?nu5UuHyXkQ6P%W_9pHkj?uWICiE8E1|H8zXt8bz8c8v+llmlGn1gU^woO
z!qtI|1jM!+6ElgM`y?hL1!$AcC1?rc&y@4b1L%G!HQiErj4w}<0R{xq!Jf(sJ3B!q
zdp?r-oL7fejtd=Q#-hnX(pNLv&UZIag7m0CP)4tXo=FiqL(gsCL3D?GziekRPRKj&
z$a}B5oAbScvw2hddi|PWO`e)QQt!2hyPP80z796k*$TRD%yyfIC9na5z`C*~1xMK_
zk0)2%Zi&537)?7{E8TpsAX|2$SNf@GkByibcdb07t<7WfUE{u$BMelak!JeOvJTgj
zOqeRb(3TI!a{f;u8?X8Hok_+}wq;+aR}FG^3x7V_pE+b;(;-m@P_p4Yb2
zc{sGD4T#R<3RQ|#KgIC^%tu!Osx5E(y0C#(xx3$sQh}F^>7yUZ!|G)-mF_v&kItRj
zF0azOVIKBLhvSKDBxh-w^IJ$$G;R}y_q9>dD|NVK(>2XuZ8!=8$s!UBhGP@Ze&q62
zoU)g5McT~4$INc4ZpUa}w;=NXrGX>ETIR%iw#}bZcN$m+-(`NuRCZ@*7kYz$QO?>T6=9U~?##ag9=@lHMUy{CH!Z%!)f#
zS7}cC;rHy`f~lB}ICW5k+R`n{@)(9~xtUM$>m8u`yDP>I2WHCOF-dAD>=j2nUlpcr
zZ2PA(U=iK?@ZRq&KI8NKau^!jfhCg>nyY;TIPgaHp*oKa)1=bb*S7i|<~bU_rELPg
z(16Q}t`ps@=raK<7(II*--FMb{BX%&swU_Rb{!C~qsHmTv8Be{H#
zlqp`IWIkI46-~=Hx`bVZUqxT;2D4UD@;ENU{oYWeC(Rj>X!TW>673Cn#gpJKZfW@I
zZgv3%Oy*dxzw+vqeebkV)rPuDMH3t~<~qMN-zs_Evr!jBmiAL2^F(y~cE`i;mcQ0f
ze_1G40(@(G;nCIdyuWXn(5mU3{67r6_6FvkmU^Ksj-^!pa85u{D=={F7%#0ZEZU(a
zgax1bC?lL8VxlqR1*J&B%9W+|0434E2Pb!~$jgxe;ahm1@U8Go7w+|<_&ni=ep{0O
ztql=`5q37}d=Vb3BY$;~4O78}t1!5BGvS@m$u}Sp-
zK=r0Uymjs1S|ktpyaKl`j^-myoZinD@P;V;Gr^MEE6-U)NuJ#kvbG5w*B86bQ)vYZ
z;9U;hv7*@NBtjrAdI)m+*-LWS_3mjW$MOA+#kaS|krWc^C=&KBfaIGQj7m~@Oh)Y
zmthuqu)=H}`4poX43s
ze)|~mI*<0@syUs#r^kso2xC7Mj~hr=g6SiOnCug%^u>RidNHk0W9OcWH&5#>nI2Jm
zOsRh7%c?s6rZqo9|Gnjb-D5d;;c>`YH>Odiku!V2)FUSpOMbk_dk2;&xem23yZf_X
zf08*vPD(6S;^zx#e9^lWH%%<+zSGP%QB>Z#L^4ekL6PMz#7N_RUgFU2hkWcHyG>QN
zq1Eh>znkbu`-0&ETUraiyMKvPdM=qSiB3dn(IV@AHY$>$2Vg$4u$d1Q@kAlN#r3Y@2N%1c)WSUeBk66o}
zOADRAjeK2JDAN7I6GP_6Q7Dz-VF9Cck)Yi~zs5
zJ?m+@oiBvgFNPZy*ArZrtkJn`a$3RpBGdD`(De*d{hpJAFZd*hFSCNfOiRS}INK2w
zKbMdvt%%OcrI{9Rknn?yj7uWz;tCC5vnjG1it}&aw%(z(-m&s9Sq19R8gN8J
zJNFuf$C!n(jv?(*N@8
zPRE^}gjkvqcW#Dgb871CVedv036@+NoGoTTBG8HXRc*VMD_=>J(p%--Z;f8bUpa}?
zEbkw`564mKXTh@fb{$M8&mdL~@4z1a5N(C77O)@Ppb*&I@3wXtsWIIdTuH*1GMh!D
z(Lq@R77lN}l(?+WO^H?+GX@p43VnVxfWG!_G(jbtilwkl5H3TiTOA${g
zB?&vid-+W1Wbo?L5@TjK3SmK_1s>;owRcyca?#1p5Hjo<-~z^rR9N+p?!E`)cDRe^
z(fcXt)UR81x%acVdFyIq%+80d+q|8WP&o9Nx#x;M*H@LhFdXWh9rz;f_xV22J|0l%!lr?rw{J=O_`2%*qoq0(8z$jRW0wlT
zIsc&wwhKTNw;*Y1l}EW0!@cawX`!)iS#~6zj&+L)XHI2Bb7mvI${0dPr_M?*b3AIs
zFwo_@L!RCn(C|8E52YJP(k=a_D|`0y;+1R<@`d+|uV_&$PPzVa2(7U`)~M59;#^Et
zUAb72pjjV=&4#EsXVQ~XeuMeYmg3xhIKeBnfdP_8x4#7OCpi^rx_SDWW!AL(4~jHU
z3?(co;#7XmCTKHhPr*=JdU-=xr=1sW$hXV%XjTm5j{eT14u}ZO+S;TYw_yNpf4A#S
zTuOr@oo3YU-k{gLQ}+ngc^_+b@axxh0$aj$h$c3JnS#mqOtAablAxYt=|tH*F$T`!
zdY3G6bmj*zNeVC@EXHzTxy<6Y&8q#;;p{a+e~^>;xSCd!uCk)DXm5_W(ZSE+O<|hz
zF9~L-rj5;!jBFUsbi*@5YUp$MsD0BG1OLl~ox{nGb!QVER^ZZ5}MF$b=gL&cq>}
zU7zg`T9sdz(v!oLnGo8MfSeV-$h6Qm~S8r-UQQu1a}rmch=
zXfBTnlBvIfPC|S6Q>FYqOybwwxGdF5o9*P>RqDsgB1r5yj?y>WeAUUciLDTCD-T)V
zYNmWN*>&>n>VOG`c-n1>g~4kQrx~`!JIes!BR7DdlCGw1k#R3o<3ek^9JsLXbO;Y?
z*NSoMQPc7Qpu4bB3%HZ**L#-1cWS&QOY+R(^reCo+c)+jWB*0Y5x3oG`4posbp02t
z(n`@tT?yOx3jMvMu42W9-Cx#DBG*a
z2eabQF_5Baf
z=z%hY`N0tSE%EeCwuG2;#xMWeZ9jeeusU9^>5l(d9XSF5z%}60$d_GDh)t?t1W4n=
z=AA6{P>QcUxQHoc1Gn9VA0iwXvd`U;ZykmbUe=Hb(Hw>bzY$Nnup4~3R&u(qxs5>`
z=$2kITx0*n!tI9)uGPYYw5L%S9%Y9OjjXRM18)r342YnKw<8sI$A
z-|&vXg-UF?cfAXfT>mC7R4H17y_Rs=NJ2`iV>IM_R;iMPUWxY+LRQ
z0J+=iwJT{iT0OnxcdMp1Z6iLhyz{Ml<@O8rW}sL5*)hR+&qC6y98Hn6c}PB|<^w3U
zeb3T%bv#vcjW8^=a1`^(mH-aytFm66k67m!Q%{8WX3`IaCyxtmnk>gnZ_bgk+Mwj?
zE)Ym&(p8~SW45t9Z}0nHhRvWWp3dSNgHl;~iUctyz00$o7cfU+H^!rUb)fbg@5Bk0
z$d(>KOS_qC6$XmbZWzS<1m>?fc+Zz<#ix%1>O;VKH+KhLqGP1tWy6{kmHL2gACI|k
zO0-sKo46F1KpDr#4Bg}Ny0p%%YgWPB(gV<)0ndt3mwqX`T-y)}^1F9Y?y&Lg+QvJog*rWaY
zUlARb*X_&4z`q>EtH)7NX=Y+dpy8#IDG`+r-II#bALnx(exiQd;Aj4#BMB<$k=s9i
zh*g#dT3jyO#l4(|4vESeT8i3M`Ywx{Cr-gM3kWe=!|K$Ob(rmE=@XBey4sWSz9U2_
zaTfN>xpKV^naZTS@QBZeE8dfzCF;GA6Y5nh@TG<+$e?twU=WIU=h*8K!#G5PIxi~^
z=c?b)deyu@Hj3^O58=dInxqRSmokbx2osNIse~#@oLj|CbabDX
zhWjm#4p+(XR0v_`DJS1N&K&S84o;r6=ymPMi#G$g+XeeMe0%Kd5o8qpYV0waG;l7F
zp}4h@Wg*wh0`;I8lWKc+-+x<}ZrI
z$)G*3QoTxmZRD4j-SLTqDKyIJf@n(bvqLTBkH=0--z7>@7LS`u_n(-y6eeC~OT{RsII)7PRl&BiKGkNa2?bPBE
zucj9S6$?Io-cH+M5C(1nR}3cNgt*+=*NdGpId(qEjA)t=*VU33kDOaEn=>tk6AufS
z^}a-QrrV_-5Ymh79n$S#?9EOH;zJZrmsuZKhY7-OTA1VdUqDi7YK4!=B!{yJhMqTZ
zx(tU4BNbf5PsZv)4Zky^o53~
zhJR|vy3eS-Rwzq}At%x1%ZoKVn)eWj6g4Zb!-=4_iegCG`_!+H97XjePpe=s6Qf0*
zDjkJ)cMjyBe)F`HcGU8Gluh{Q2~g4%;U+{IjH2c!xkIEIx6LT;lc$Q_FOoPf%{0~<
zuS%saX+Biih!`x~G8T`qi+mb1l{?-+O*8tU%M;%Fl7+(SS-!Z}l_wvL)_$Z*%G=TW
z!X&pJgu>;ZVYo@FdoUNMNo@gq^&?j#wRRO4d&p(({^XXU_8_;@=g`p%ij)!8_27!LIWA?02+KHIPAXN&wb^<;D}@!ZEyO#Sw}$Vo5Mw
z@Ws$Hg{~tSehH870f_>>clmPe;-tL0;a)R>dT=7+VtU}_wn9QY8VtU=(k1q>7KLUN
z%lB5V@7*Cdj-odC-mqv*uRtTWap}O@$!@D=7{6uT56?Mcu#Lwwg9p7>Xa@wX1d|&K
zkb@s0CC&Lcq)g|%*`AKRY?GfSTI2taxA+ew_7EAS{j=+Z@BH#lbW%kSjhJ?(lg;FV
z>9Z90#ua*@ODh|0J=+skskeJ=Y^eC(n&h)#p}CFcR6Hcqnx0`hmA5`3b@HI{e)@yEUVy$o2NmSSdHnN
ze~nd@=OSOXjk>3VXRthC!qpr_5mg(m3z)jOi8A*-8?)UJVh#41Ru8i}hha`wvc8W`!O(#7+X#_G
z!=9ah{Z$m+EG|aesW8dB)&2D$YhBO^kuSGQQOz8_;OFbnlhn#H
z9WB`vf4=I#B6n)v2R#vc|5!xMv@S2y_9?n^;M3@2Ey`Eyw4%36hj3OoOeV#qm2Uy{
zbRL&{$$Rv+^HzE8FlOZ?Kxpwy#k=z0
z3B@y>0jw*BOcJ63xM=xS%5l+A3wB&VrpC9ve6O?uULbSq<&)H7FZu>+BR0INMf#SW
z*%akK@Zodb)i@*5o>5SQ(zh(w`ZOZZo5yIBey^CobniKoN%)fXV%h^^$kL>8#^j&5=>X%?*i+-R1S(
zo>X*LdqHjdLo!8b_zlX{g4Buh=j$}=G;&6aw@P!u7jaYOI&Ja{Q&j_%N-@1|$Vqit
zpZp4Wz~Ku8b5J7m%{Qx#*s1mGryIKS7R8HG>2PS)CbVA=<7;tU0j*_uzXu*D%*p(Vwu_WEQD
zV9cT(U3AXEqb6{Lo#MIi$@cElkJdqPo+500?P{LfO+7ltM=v?^mV%G_%oIoDpr~_O
z&R<9@{}~IJIA&g*ir%FiDP;?7ff-83*+eu6akc6DKrRehiOpl(mE4D23^tcSW>gtl
z#WNXo`}sC)GRB;3`}wLEbL#OQ61XGQKUY-n3U^szFII@W(mZHr-W_}&M~1S7uBF)jXU~ZVCJTU2zTC6&`_X6fcM_@6Y8Lsz_|$C5&9y2-
zLi1u4QQjE}-Uf=rqb3TmT=jzQ9F~z_@1mQ!zg^K^L=Rac_Y|6s9}-Z0vM?J#OL=_9)N-ceINglA
zyOf1I+rT>GCa;#=pz%RFm$DzX6@2FDo{m#qOwr!!lfsz1(M;-O46w_0NOi&TS`E9>
zB`;1KYai_#H@;QC(uTxs>q$_b!?hOZAkLHT*v1?rQf_R#3>oQ2cWo`2&>dKoWD4{v
zWro1qH+^6#v(YArTZhakuA&Q{qqytCD@5P2F-zclxrX2Yoianx*#{iFhQO{2DGOd~
z?Nb`)SAX7Dfa6$KHBKOqFRKiGP*lHvQC>tR!e41a
zFJ~uu9Dq=Y%nGufcSlTApn8K97-eFw%G5KK+f+rju7tw2y88s#KS3@g`1Y|f<95o{
z;j|j_tfvyu{WF+orH83*fK*Bi*F*P;cwZ>|KDq4Zhs#>lIn}igoNMRwb3ECFB#|Xw
z+%v^Bqu5&*I)DnE9*DozMebUQxM-+L+HR5^^P3O?1pjJgu&!o4Cu$6(o=
z-(n=%C1P9{ebXxX-U1k2V>+Ji6BC`CY3&htXxi&ucMMKzF+#?^mRKlnHBRxoP(CnO
zuU@@Nwc@r(E?P0&e%nxvQ%4`kcR6geZ@Nq))ibM5V_VoUj#l7POD%W!s+W3_yXhH&
zo7E?RZ9Cj%608?1p%%Gf%uBt%`j3yt#b^*U(BnDxUnIe^4M8H6Jm{Ae;X=zbD$MI%
zhc)f-l;u!-?
zZ8foUCC#uly^yDJt%V)}oed!C9~g^D6V}YH%XkdeLJA0o&023M3g+~PM{$lKYbJJ0
zx**DVyl*2Qaz~NdtY64Yj7cZ;2U+Lm+<-UZVR;0y=|;w+9;iF-u*`Lq5lksM$UeTk
z?J+eZi*7oL0xn~A?*(D`oNF|Gu59U1GxT{;rP9|~iStUFw+a+j3w~z;FK7G|pd164
zrawHbNEp?U!?i4w3TpwPeQ#BbyHCq9;1Y=GUG0LV_$@6B{;-g(jga1#rm?<@?E7XR
zw%=-=%-}CD^Qk0!8mVolBR_bhz9WKAYkGywG*S{(aI{9UHgK-tt|p|&tv$U|YAQKZ
zQO=V{YSTpQWDxt!_w7}3UjStzosoxoczw|#zxn%wmune^6F%59I!Cw3$U#a1`w(y-
zb8SopT%j+M;vt^GXbh~{(Dy!d-@jC=Q#(J~P7da{!=B4m`KS?oT=u$R+M-#K_Oaoe
zLC!TOlSSPxQ^{DpB@zJHCSO
zC`t(Trf|TIDUt*k8aoB5xzw2{ANVGlxkxx50kdopzwNkXqR4YXbR2PbOeBi#;7@LE
z-sQ3#LM74%`;xZ@2<;FucDw(`a3j2$XLv}Tvki8sQU7DrF`)ssfEU2;z!64)yy)Gt
zi{D}@q(p!h(RPcZj|I?daW%$nC10z#doAORg<_3gxa}IZl^r>xHx4o+e~9A_#Qb-_`V(vuGQsDgD5!j_utCm)Nsl+I(v-`
zF9$-ci5amI1F$CaSw76$!mLO0;U!R!IU|c#fHb?>*JgD`m@0-!Zu$2^{Aut!oAuiAjOZ9OufFQk
z6m0|FbUebxx2sfpE7=NXQ`}s2xFU<^VG`OBEI`4TO~H(hhvQG|Rki0@(vQGQu!1cb
z$FKR3o7E(UYWZ2DUUWrn%#zE6*E5{j2NpROb!HD
zo(!C^axRg~l06bQOd}7wlXL4>b~d+5ST~NJm(P#{(_cAy#1t1@-aFK}4__`nx6^(7
zFA4knmxQ6Yy!=bT*wBCno_WG^z+5^tWfun^OFk|VhU{mu5mY(_!B%z|>RWKAwC2`J2#zC+z*9_BY
zvQBh06!#gfj2WaN{NiTsKUq-yGc4he1{SUUw&i%Sa+F|@;sPJXv+S~QVz1&?Gk-Bg5ajnaJFf%H
zEypxzO9e1om>1y#Pxhuy*}pyMBF7`7y&a~YaIPC^&dP(8jLb?6d)xMlX9uPwN3D9s
zj;$tBPm!rK_Ie$nmpMnYGU%_-KuYb$>!X}m76KuzR4wK$N<%?_o3GQN+MGK}1~36%WGz=RX}F-)
z?vL@E#gd|~GR_dJb~<{*eRI23UqF#wLwiJh@Fu+x&lwe)ji(uFG0j1dUfi>mZYH7X^1l{Yl0Pmj%%P9_Pa+2fnzMy7
zP`O-x{YufmRWQmf?A~ccNo_zPKoXCiy6O`yk#=!
z$Z0>Xle**SrBZ%Y)q3}o7*!-VkKUFk!|RN6S-jB)pdC4=I1Y5i!4G`Fb=3c*Kl2*F
zq{z?2NvHK5yLo-BSM*-y#@3$(us~8A*i=!a;ig{Ou>?7^vmdFP5IX9o>6GR=f&|)r
zq(0dG8SCP^_=>C6^yYGGj8V-dXFXa-etg8bPNhHmF1gzHtUZ59!z8n2efGSgeei4{7(YzebPG;HK%_;72x++iuE0V`#{zeR4@n(fYjzr)zSI&PML
z-^G)FKe{^YR(>v}5K<~ExY=Uqbx`)(fpX`_bjw%spSd2_%nyt1FTOIeKY6Vnxpw7%
zM6r1C5qhjT`CFgn82q9T(LHH5Pv?AJ5Vo7$aRXywT4$i(OX@bQx)dm|;}?3%w)Fsf
z^AP2YjZZ>6xE>!uXKQAL<3*g03U&_<7=DrZ&3xjygm-ddsY^-Z9i!K7(PBD9*Uq$Y
zewuwxhdf{UmR%W(wUl#taY398ER&zB^Bs*V&nVV_SjQGVU%s(Q1E#y|TYH)6w=`ti
z+>C->cXPA!>JCkV&gNJN=)7s|-XN9>Z#ouRFMH1dOyoMJxb7HsEm9q1e>lgtAJ4Ud
zaHD*lYkFA?cp+dnJ|4cOtIdvQH5L=R^*Yl&fQTrA+U%w1BdT<)UY8*!l@?4MAlpeF
zA#W5_$}aO$mj(}dTW7r3v?Hu#cH7J@#CYTr^J-Sa8|5k-^35)=dL%A;f$Y3_4#G78
z+$)B>wX8n%X~R~I4Q@2%eVN8
zzEh5XSdwB@Jsc&w4W_z1%DcfRD1ONbr{Vo)qYD7vYViY!8_uRygS9MT*3)ItlI#CTO>I)h0g@uz07q>_3jtK
z@iRbDJ-^#q*xHnmM@#z7PG*j-Rn*TU4n*Ko^3RCrFCx#yc+uarUqAenBa%Ui;i?qX
zd)>7x-Ym!?Jy_0Q*DAO)yrU7C
zi-16@`9*m(mJC3({I8^`y#{B>9P<|~@fhh%_~%^F$SQ*e^Z0T#e#*!hmX~wJ2L5`5
z6AW^%b?0yQg9u0Dqdl%+QD5lO#G0N&!3PymSTcoF5EvMx^x>SL9&Fi
zvqb=NvJ_+Ke1)FOxAekT<+A#nF50UFttc@9_o|RR!CXc1geo2k;
za`m+$5#KMnjJoTWMKRwdt3R{y^&`J3OOUvpL3qndF8ARabVJt;^VAh{n8&Zme9jl+
z?8%!I*I{kiZF5%ejUQBxFNeoY^{o}}y)BT?lGX9apf5gobPULi8y^O6`P#4d{
zoCZ9gle=0YU0*>IZ8c*EU|;}rM})gHB$Kui$ArOM*LAHMuPt@9rlG(!IDYzGUVbN)
z66$o^G{?-7#Om!Ms#g;SQSr)W&(vJLLd7dtol0ar$*Qq~D=TXW?cbc8f9bC|2P?AD
z(~ZHN0r%lN%=5r)#ysJjGEkx(bOU;mE#dw)?6*Zcqglqpah(S-0Ri)`6oxD(_4h>b
z@g2_Cs1V}zNdwpjROu`K*!v21`?EMTowI%*#Fg)RGa!suyw5g6H;PWqQD!)NiK$HA
z{|q{>Dxd3`o4rvKW0>irD)MxdDN6kzLaJ|;~}4p&UI
zpWORe=G0?eV;ncS%9lr;6RqM
zwin{tVESWtU#_O5od1U?HSevdN*7nsGU|p1FSvb{AdnAuNa1;qHafB%Bxe2!47OX0X*l-_mghT&JTwh)3_t7L9l#Wle9+S}W7j?)PSzRhbqY
z6b|Xq_nE367|IJQYPlfil#A?>lSb`>J5>}@(RbjsuxO!s9KDy{J2mWmP5zo1%;YL<
zm#~CJZT8XJGl?bIMQWr?Hj;ATiS?-N23PyYelSH4G_-n@UP2d`Cx;escmetja2Gdk
zjCU1Mpf-|GRmO0NYrPWhf|Jlb#?erTPZU;U`NS943!Z(wF!UYldA%)=PnToM)HXYV
zp;Quoyb9$FAKqn$y^nD~7hD!%C6OO~tv#Ui7=^_@?F??_A|Yg(qY@jw-kqB}TU;;N
zk>y*zI>&K2k5hh*=9?@``OV7!-(EvMe0G9AwaHkfOILlLu~FkUVBIJxPWbRk!v~i_vuCWPBrMuH
z$J^QA5}&{3(}B)BU&vjaj>#a^PSu@0wN0osNx=6dd~^yqqTbWO8gQ(T5VILGF7U
z>){JEs0;yXtU(bKLJ=0U>E3;Ne&XNmYRgZ`!9J`|@o|gH7BS#^#
zS_1>IT^HB~Iyu7^ND~b6oYf3dmQLv-n1>s2d?WE-4)c+-5Ue5*f_?qu9#T~|aHTrX
zQFU~kMT$r}%GgtLu}5icue|I7q3?W6WX@L{JG{w%@0(jj_eki2pXAQk&ACKIdT&RU
z0_~R9%9*JfWD)F}A)I`(zYFR~xUdiDY^BhCrwm}LG@x&Co(qu&cyE8VFfSy6)828E
zm%);Ab0Ng(SYhZn<^B61%KWqC)T~Rh624X9=SQ>8vRlyS-WiY%*9S$7d4#{Hoa&UhF(n;z-H$b7Zr8&dGqClC*yc<#ZvFMtF9&fy=h;I`Bc8D7ahntEoAStvMpTS-
z*SnduoBGy}|GiN3*duXP|HlWPYkeBhUfojjwnEHiR&EQ0tI+EQi&gOWx{0NmDa#fj
zFDB&obyIe~AX7jP*K3>GHZ0pIK(Tc~7HOul7
zWTVf`cgvA$WG9Snfl$UzXFSGW1}6|HOOWhEFl-stF##1g^l!4$X~2?6J#smcPw3x>
z?RTDiKV72HyPW9@ZN7|H*Z5ytW21=R-Kz8%;TtUBI$b7XILI@`nOV=5ITTn5%rZz>
zJ*(;z20v@2DR_rw`xZMV)RGf*g2Tg+Q<_$R!xfqwx
zR*o+jRnd79@FH8SfBqLh^_g$Aib+AF?7If;>mB|ms}D@a>3#=i-eN-u?$$0UD^cTf
zasVoEI4~$m?9i3@Ij~4`X2?C%0X<#d)`bwmJxM)=C}vmjnZN-vu#y#jmp5~S5mCUH
z|F{=gF9fN)nED1c1Qy=D5StpyhSQy&fw2+wTv@H
zn{LcC@_Bq3K;{%b=L{2T&GBrpm_CO(OPKCABeA-?bpTp#{paj&<(8?|_$|TWSKj^S
zlO|sj7IuLG#_b!g$1AM?H2I)IOmM0TSA{Rc8Jl3)c9{0_6
zZ1{ToPCMhT6U7O~EFv{ml7=c73cy;Pvlmh)b-Jkw2O#})D?VGa60e3}FZVWitXs>s
z)Ax>^61Dt|;?(8`v$TY2nK_v(V(e-$xE%w%eh74dkN~Q5cTMPmOY@GhdaiMhz2^Y(
z0Ly<{ej(WZs(iumet-ZX$rt!~{){v|be^odX#KnXGiA5dF6P@uHmfU%e*@Iy7KUaD5T%7lC8WM;@e3u*WPMgVwh+6sl?av!Bl%E
z<&fr?1g>lGM*smpwehAo?aP^j)x6NQH;MAkH+KG!aX4DUaFno{;28C5S!|mQ*;A3
zRbPpDq@d#0NUi4@E!(oan|>Y^wC=Q*j2rm{_9Rd8E)v|=lP6;CbW~}*yN+oGebn&D
z5-#Ipv1ojwYeSgxk}o39*dW%|!@$w(reDO>v9DX3YIT_63K?)eY)CQWHz{YRI%qzT
zja%2zI484QZgJ2#AzPag`he!!e;{cYB?xVpIMIwZAyqUSVwhFf9z`F8RgPoEibT^f
z&c8Se!7XK^=EtQ3)<{6>-RFmz-zunY^+4`^Hfm$er}9s|Yx!g{-vZ^It{o9^=7reV
zZ(0RE*+?bgR#38sfR*$kNQaKB+Ho7b$Qvzpm1ZGr-9idFI5
z)bmZvwZF<+Qojvbodjiox2v(}J6MgYhft5?mb$xddlh^$UaFAlY%fk2DR|4RwlGge
zj(vDuSL^6^u(wdWe4lPFE@G8=_4BAQE!cTWg5>!g;0gU4Wr&|;PSJ$PyuneXZ)Oxo
z6srl9dhm%?A5T?LrkdAHF@Cr#_q&}Kq$+2f^pOV7cM(K4=;eGtOG0+WrPH_SO(cc(
zXkV=uB+}2z)hANg<@b0vi3Rh#OG&w5+bey4kLH3nN>Kq4KX4{(u0(bsCwjl@mrwHt
zCXyo6CW8VvXc=@4xCPvBOTfKY8cm$lLhS!z>n*&ZV7q_sA*36nQ(C2^8CpV8K>?BO
z?(UGd>28ol>8_!XZjhLvYv^W(VTQx~ob!9$v(9?g{tvFTuh`%H+26~w2(Q8FA?!PM
za>$MP{|txyYjUyjle70<#z0G2%EoBkN>NK-s#Z|J&zGq1eXvSH#j7~3XL-GK`ITJg
z{01GPt13_2TV`3yzvV4)cS+Y9q;xUVAkE-BfNyqRuyk9EKB;5|%{UBju%owBHd#_^MTD
z?7RhqTv_qNiZ9ikgop;0aG`%ZgFthCv2A~++dF8bx|SYL)1H0tbNgnPT$S6}wTi2D
zS^3q1a;X(zn$xpX*CyPNVAU+j-8I}bOCE|J}|
z2$Y?S7EHHN?xdx!J4*gy=beUs;OCYp6rOCLCh_wlD}mq2_rU~-w#=V_CL9OzbS1~W
zcG@fNYzO&a&t#xg#zU9cam=1?F_&p-il3RHPbbt)mc-FeiY)ev`y$=?s@HVWzM9pq
zqE8MNjcopWzvBtPPBuaqerj4YvIpJ#DIdB#BRonG8f}occHclVK3?tC>fB&T#NWI}
zMEUC0~)6Qf>^iOWO$w+GbZ*A{Y8yY$_JPu2!1BZngm#
z9HAp}ab0aRlY=goJJVb>{?N>hq^|ZDRTg$0U|D}}oHD8CJ}bYSl3a~ziQQI+O5zL_
zn@M;cG0Iz=aPRVN+g!x)p3dJF7Idfn+pI#j#766Z-CZmd;TM7=VS}6g)65r}^#GiD
zAzyO?d(X#Q3=@gZO^Q^+8({qB3gldLvM=12A5~u=gdUWMrRjAg%paL-CVRPIyUVpW
z>{8Fbu{58G&h|Qjj?qZ>>hx-?<1|Hz9X%3;S9zKre@vzA$zdqvMLss?MhPB4q_4`-Kc;iOk^y9ZwU}eBG9gRr|poc+SoCOnuipqAYF$SX
zq(*N&z-gqkXba1}XHcOKlyd{>tJSU>CG^ps(WTNw8{7qFg2!2pO_ZVmw^TQRMk{O`
z%uDlgdH>U&7WVwtia>1O@4r?*;>%dAiAmPU$_pdy5@ItNv=3}F?In3hJ1pR)1#7lH
zWD0?CSGfX(!mIN8ZYC^LBto<|N5T+MkkAsZFflilOpe|NhnoGjt^A3ksq?`5o9(f!k3Fkcg5fz#Xrj(nB0#tYXH)UNJ%{AMMn&BD-Al`)G5lBP7|r(WEfy
zEX5Pq`y35~6{q&?4`h^sYWd=*cRot^%h#RQs=HK}QA)|TDA$G)IiwpYV|!|nD-<;*
z@sDLw_PSQcfc~y}JkHUn9yHfn9pKS;;gMyMkZ2&4I!d1`RK?`58Q@+m(Qd@`?+SjL
zs8RN#U9U~jkPb%JGI}pXbhN8ux(=djMXJ5USmR4l4v&W|f62%4jHL7VK~s_4%tUGS
zk?p9ljeOuwOgO%rL4)2;Q#%q}r7xv~?_*DbdPn!gw+F(8|BB_!ocisdiCc^!4%O(dilyxw&^K`0>uk8Wti$
zM6)qfS$}Gm#24b!<7WV?$v<1
z)sYMUkHUIEp6y|LbXH?Hil5(m(Ij5JdGk`uU8|(#nt1I%#Y2Y~o}%B(=0k*UVbO5j+%C^xOxe-1(|i
zDmO`TKXqyD>=XnNYd3pj8PtU;XMSA&FqxyAJ}o$eo$_S;HKYswK*6b4h)(|1(9%$kCEJ{2ei0|t;l9t&uG7ea;b4MfibCFr
zsj6}sUHK5Ii)UY}=n{!sWM!iNJnrU~Nsr^W6`rbb2Su)^e+sv09xW64H>p->h0I!m
z)0e^?H5YmJx8n^|Uf$VR?TT>}e3bo+jj>D{!ysy7>>(Iw-oT;I@fpRtW+Mc1>BPYE{HmmLn8{K%0Fa-_kThppl=k5@`
z?B9vrJv!HEO$uPk*XXsTm?Nz{%q>fiq|R}{M<*>34Y|&I?rTsZ_~rfj+f!dSkfE;E
zj+Cvh`EUo=cNQ~cnGYxw+rSh^57pq#L>mz|-jW(T6$b?BZdc{nKo#Qxq)M!XFPi@d
z`uK=H{5P3T|7{$3Mppdm+U8YHjHNIwcJoP1-*Nf8~d<`o~q$WdIt;v5#S7C^yM
zCG-AV25b5n0L_O!#JMFo&!$u7tW3D)_|Ul0TJ;Az*Z!KYT=Atb0Vh&$DthWm5xJjo
zl;Y+3Qo}O4lahMAws<{GH@2QKV;}qD0?}`4Pn0cTP)71v}HMhP1a)h3p_^iAl
zfT7+eHFt4j$k9Tn^TF(FW52I-FC9Bh|Cz*WrOP;ey@H0d%~5Rhf|AX&1Ss9iH#QDW8*%~e;po*@!}HT*h$cVm*&Q_
z(XSkj8z?8Sgizb{-rbHKschgyEJE(EWbCN;Xf(F$3{|Ssit~{8i4F8~a-U}EIP3CX
zd7M!_dt#E94`d7;x5kh(MybxILg;Li_m5JrTL3$0mM*Sm%jpMuNuH#s?X>s2t^#Y8
z8+^_ok@^R0@5qH|>Ls#~E)9)El*#l7tEM~LBNjTZy&`i}+N{InK=F(5P6XrAM%XBZ
z>p}a3-24u5Lmb;AfY1?THUb(?gPtuZi>+Y^C*Nl>)K>nVqkEQVYHawt(o6V6YxO~V
zvfd*_+a6@%<)5{ofIkMHc=K^^nJ(2cH8{dQ_ekD&QcPqtgt72;vBTAIJY#>`(=zl#
zdGI8flzYq0Ol*o0TDju8WmeGh-$v>GlkYtB^PZ-xTP(8Yk_L2L2SSno*SI9d*m376
z=?CTH1d)fQN`h+F*u^O1`8I>eD}Sh6WVJ>kU2~kfvIjv0D;eywWJ&2M8adD=wb~uq
zvgo+Rj}%QY6?mIGm)T7{X@4^po;BCS`K@CIeCP4g!Qm0Eg`D)?zh=Yd4U4jKTU}r0
zrpgs^LH&=O$|HF7m=1;Sh3`lhckvlD(~7-Nn-2wPE|ypYmp|(+>UMNdWA9rxs?{qY
zMm{O#PZ30@O_wq|E10mnd3@o>#sH&ODwP&z6=cRB(Nw}vYD<72V_X)&q8>>vqQYeA
zNA%i5@{C$bVo0g0E55j`^NhOrh)H*tM6D#wSfx6d^gQinUk>zY9dgQHh
zd>wscvd389iD^6X-6IL3pO`y?84+y9FU|W&VK~S7_U*Z=$-v7K6lU@_@@5&>7#LQe
zv{cTkmme*Hw`CyUW1O!RPb?v(hoTRMwZ(?nq>B^zJk9%u(Ou^JoTFZj)?KGLr+Zcw
z#u_2b)|h<^~x`(JF5e!h6gnOKH^{
zf6TqkfZL3qqso%@r$0_HKA4dC-na?%ZIq^(d|E=MGUjIp%57hxdmJa|Sp)Gug4|ZD
zP=^l)@+q|zb32MUZ%Kd(3a2cC$*dgfXHC2w>1+f`)?l@T7*bq{U(|pH|0WAk@vaWy
zLc!m$*t$;g=+0}`IpT3b@;1gv5Lezaj|Y!xUxgw|H#Zu{zAS90LFAjiD$hES
z6PY1ZdQ;meO4_}~Qav`)^uUOzz<`dEX1zpp%Ih}gZF%^7nZ&ZhO<5qg+CP&WQa9uB
z+C+|vPM=WKvSM*t4-kK;r;!V8NAwpxsbYbLo27yoqUi=`50*+IcZ>y*{;
zkozA+f^918XEU7kXd*%QGprPA!uH9@Lm81qc+A{Hm1E>wO&!xm)naF;XR7c%^6q(w
zn+-Myv{z#*F}IVkajQWZL#u9rkh=*|y|Qo`u-ADqDRwq~C;6~nbRtWSbn-oR&3|nU
z9#Rw}rAeYUn=nF=h0afKn@I&G9hg$l_!3_yy_HMT`
z3Ut==h-n3I|AyZ-6}7o^5PwnP2p%>l`yU{ciTUVNBn!SC*2VGGi=fx9?RO0Hr&rxi
zsAnU{6Utj)gMO!vZ-?IjpKuk}vimU;b|Lwo({WK!)3|H8#4q2!FNH;kxe%aUTIuY#
z*qe)u>CIHsBJ57`0W#YMG`A5e&gKSJ9<8rt*y3Be%hp`X{E&-ax$@>^&5&oc!bq6d
zocYkc%+|tsq-AWlbPt_$>1qp#N=-Dv@f`ekaUBWCb_VdCs`m8g}W#n~l;WCn$DT(`iL69(K
zkPF2AM2)8_)OTYe$o*+pAQp~mc&7z_=M}iyU}(h_Z=gW0`WKtjg}T-8Pak#^;u#ZX
zN>(l|M9aFZWpCvNB}--xRX4mt6_aHPa03%>2SWJIY^QwJuB=?w=mPHEo-olrXuvPa
zB+*XUjP|rhx!smqV}gB+o6Oa*PG)Q~RY(}EBx_$d*X{h*JnH`e3O6>47OH{c777W%
zOzsX}Q_C$Be-Z5G13_Gy&YV_-EeS`IlgBZUS!%VsY*BwghFv_Q1`7o8Qt`)(7DJe!wA1-lUNRbSt6Z%
zk8$I}#4DoJI;cM>ox9ho5u})7d9@zN#k`2n-Y)Of@?^aKZX(Q85&K{>KQv>gtI%uk*b_C54@AVH
z@%Kquyxwqp%hV|miwsgvcrPPH#