Skip to content
Commits on Source (11)
......@@ -11,6 +11,14 @@ export default function Navigation() {
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/analytics">
<a>
<p>Analytics</p>
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/articles">
<a>
......@@ -18,6 +26,13 @@ export default function Navigation() {
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/asamextensions">
<a>
<p>AsamExtensions</p>
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/bookmarks">
<a>
......@@ -39,6 +54,13 @@ export default function Navigation() {
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/forms">
<a>
<p>Forms</p>
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/eventsAgenda">
<a>
......@@ -46,6 +68,13 @@ export default function Navigation() {
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/globalInfos">
<a>
<p>GlobalInfos</p>
</a>
</Link>
</div>
<div className={styles.link}>
<Link href="/groups">
<a>
......
import mongoose from "mongoose";
const AnalyticsSchema = new mongoose.Schema(
{
_id: { type: String },
content: { type: String },
createdAt: { type: Date },
structureId: { type: String },
target: { type: String },
count: { type: Number },
},
{ collection: "analytics" },
{ timestamps: true }
);
export default mongoose.models.Analytics ||
mongoose.model("Analytics", AnalyticsSchema);
import mongoose from "mongoose";
const AsamExtensions = new mongoose.Schema(
{
_id: { type: String },
extension: { type: String },
entiteNomCourt: { type: String },
entiteNomLong: { type: String },
familleNomCourt: { type: String },
familleNomLong: { type: String },
structureId: { type: String },
},
{ collection: "asamextensions" },
{ timestamps: true }
);
export default mongoose.models.AsamExtensions ||
mongoose.model("AsamExtensions", AsamExtensions);
import mongoose from "mongoose";
const Forms = new mongoose.Schema(
{
_id: { type: String },
title: { type: String },
description: { type: String },
owner: { type: String },
isModel: { type: Boolean },
isPublic: { type: Boolean },
editableAnswers: { type: Boolean },
groups: [String],
components: { type: Array },
active: { type: Boolean },
createdAt: { type: Date },
},
{ collection: "forms" },
{ timestamps: true }
);
export default mongoose.models.Forms || mongoose.model("Forms", Forms);
import mongoose from "mongoose";
const GlobalInfos = new mongoose.Schema(
{
_id: { type: String },
language: { type: String },
content: { type: String },
expirationDate: { type: Date },
createdAt: { type: Date },
updatedAt: { type: Date },
structureId: [String],
},
{ collection: "globalinfos" },
{ timestamps: true }
);
export default mongoose.models.GlobalInfos ||
mongoose.model("GlobalInfos", GlobalInfos);
......@@ -31,6 +31,7 @@ const UsersSchema = new mongoose.Schema(
lastArticle: { type: Date },
avatar: { type: String },
mezigName: { type: String },
structure: { type: String },
},
{ collection: "users" },
{ timestamps: true }
......
**AJOUT D'UNE COLLECTION A L'OUTIL SUPERCRUD**
**CREATION DU SCHEMA (MODEL)**
_./database/models/YourModel_
le champ collection est le nom exact de la collection dans la DB.
**CREATION ROUTE API (CONTROLLER)**
_./pages/api/folderCollectionName/index.js_
Chaque sous dossier du dossier API est une route HTTP pour le requetage .
On peut traiter différemment chaque collection .
**CREATION DE LA PAGE (VIEW)**
_./pages/_
Chaque fichier dans le dossier page est une route ( navigation ) de l'app .
Chaque page fait appel au composant d'affichage Dashboard .
La page sert à parametrer les actions et le rendu de la collection .
On définit le header du dashboard et on lui associe la clé de la data correspondante .
On peut ajouter des actions ( exemple : copytoclipboard )
On peut éditer la taille des cellules etc .
**AJOUT DE LA PAGE A LA NAVIGATION**
_./Components/Navigation.jsx_
# Changelog
## [1.2.0](https://gitlab.mim-libre.fr/alphabet/supercrud/compare/release/1.1.0...release/1.2.0) (2024-01-30)
### Features
* **collection:** add new collections ([eb53600](https://gitlab.mim-libre.fr/alphabet/supercrud/commit/eb536003fb751393aa0517cc004f4c3499eca48e))
### Bug Fixes
* **doc:** fix typo ([bfe4c77](https://gitlab.mim-libre.fr/alphabet/supercrud/commit/bfe4c77e8e9573b418215a96169e459ad1e962ba))
### Documentation
* **app:** add doc for maintain project ([29492b0](https://gitlab.mim-libre.fr/alphabet/supercrud/commit/29492b0cbfcca657a05727e16a9a389f7f4d228b))
## [1.1.0](https://gitlab.mim-libre.fr/alphabet/supercrud/compare/release/1.0.0...release/1.1.0) (2023-08-24)
......
**COMPOSANT DASHBOARD**
Ce composant est la **table** .
On peut parametrer tout l'affichage et les options de la table ici . (global a toutes les collections ) .
On utilise la fonction **formatData** qui permet de caster le typage pour être certain que la donnée sera renvoyée au type attendu par la DB .
**FORMATTAGE DU DASHBOARD**
_./pages/yourPage_
**data simple** par exemple {id:1}
=> on utilise **accessorKey**
**data imbriquée** par exemple :{role:{id:1, name:"test"}}
=> on utilise **accessorFn**
**besoin de traitement pour l'affichage** par exemple ( couper la chaîne de caractères )
=> on utilise Cell . On peut formater la data ou retourner de l'html .
**Attributs utiles**
**enableEditting**: permet de définir les champs possible à editer . ( par defaut true )
**enableClickToCopy** : permet de copier au survol dans le presse papier ( par defaut false )
**enableGlobalFilter**: permet de retirer ce champ de la recherche globale ( par defaut true )
**muiTableBodyCellEditTextFieldProps**: permet de definir le type d'input lors de l'update du champ (par defaut string )
**REGLES DE L'APPLICATION**
_.env_
**MONGODB_URI**=
**KEYCLOAK_SECRET**=
**KEYCLOAK_ID**=
**KEYCLOAK_ISSUER**= le realm keycloak
**NEXTAUTH_URL**= url de l'APP
**NEXTAUTH_SECRET**=
**NEXT_PUBLIC_AUTHORIZED**= tableau d'email autorisé à consulter l'app
**NEXT_PUBLIC_EDITING_ACCESS**= tableau d'email autorisé a éditer les champs
**NEXT_PUBLIC_BASE_URL**= url de l'APP
**Bien penser a mettre a jour le tableau d email pour donner des droits de lecture ou d ecriture .**
_next.config.js_
les **variables d environnement** sont séparés en **2 objets** permettant d'exposer les variables d'environnement **uniquement coté serveur** ou **coté client** .
Ici les env seront exposées que côté serveur
**serverRuntimeConfig**
**MONGODB_URI**
**KEYCLOAK_SECRET**
**KEYCLOAK_ID**
**KEYCLOAK_ISSUER**
**NEXTAUTH_URL**
**NEXTAUTH_SECRET**
Ici les env seront éxposées côté serveur ET côté client
**publicRuntimeConfig**
**NEXT_PUBLIC_AUTHORIZED**
**NEXT_PUBLIC_EDITING_ACCESS**
**NEXT_PUBLIC_BASE_URL**
{
"name": "supercrudv2",
"version": "1.1.0",
"version": "1.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "supercrudv2",
"version": "1.1.0",
"version": "1.2.0",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
......
{
"name": "supercrudv2",
"version": "1.1.0",
"version": "1.2.0",
"private": true,
"scripts": {
"dev": "next dev -p 3050",
......
import { useMemo } from "react";
import DashBoard from "../Components/DashBoard";
const Analytics = () => {
const columns = useMemo(
() => [
{
accessorKey: "_id",
header: "id",
enableClickToCopy: true,
enableEditing: false,
size: 200,
},
{
accessorKey: "content",
header: "content",
},
{
accessorKey: "createdAt",
header: "createdAt",
enableEditing: false,
enableGlobalFilter: false,
Cell: ({ cell }) => {
if (cell.getValue()) {
return (
<details>
<summary>{cell.getValue().substr(0, 12)}</summary>
{cell.getValue()}
</details>
);
}
},
},
{
accessorKey: "structureId",
header: "structureId",
enableClickToCopy: true,
Cell: ({ cell }) => cell.getValue()?.toString(),
},
{
accessorKey: "target",
header: "target",
enableClickToCopy: true,
},
{
accessorKey: "count",
header: "count",
enableEditing: false,
enableGlobalFilter: false,
muiTableBodyCellEditTextFieldProps: () => ({
type: "number",
}),
},
],
[]
);
return (
<>
<DashBoard columns={columns} apiRouteName={"analytics"} />
</>
);
};
export default Analytics;
import { unstable_getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import connectDB from "../../../database/connectDB";
import Analytics from "../../../database/models/Analytics";
export default async function handler(req, res) {
const session = await unstable_getServerSession(req, res, authOptions);
const { method } = req;
const { tableName } = req.query;
if (!session)
return res.end(
"You must be signed in to view the protected content on this page."
);
if (!tableName) return res.end("Cannot access API by URL");
await connectDB();
switch (method) {
case "GET":
try {
const analytics = await Analytics.find({});
res.status(200).json({ data: analytics });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "DELETE":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed.id;
const result = await Analytics.findOneAndDelete({ _id: id });
res.status(200).json({
success: true,
result,
message: "supprimé de la Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "PUT":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed._id;
const result = await Analytics.findByIdAndUpdate(
{ _id: id },
{ ...bodyParsed }
);
res.status(200).json({
success: true,
result,
message: "Modifié en Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
}
}
import { unstable_getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import connectDB from "../../../database/connectDB";
import AsamExtensions from "../../../database/models/AsamExtensions";
export default async function handler(req, res) {
const session = await unstable_getServerSession(req, res, authOptions);
const { method } = req;
const { tableName } = req.query;
if (!session)
return res.end(
"You must be signed in to view the protected content on this page."
);
if (!tableName) return res.end("Cannot access API by URL");
await connectDB();
switch (method) {
case "GET":
try {
const asamExtensions = await AsamExtensions.find({});
res.status(200).json({ data: asamExtensions });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "DELETE":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed.id;
const result = await AsamExtensions.findOneAndDelete({ _id: id });
res.status(200).json({
success: true,
result,
message: "supprimé de la Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "PUT":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed._id;
const result = await AsamExtensions.findByIdAndUpdate(
{ _id: id },
{ ...bodyParsed }
);
res.status(200).json({
success: true,
result,
message: "Modifié en Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
}
}
import { unstable_getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import connectDB from "../../../database/connectDB";
import Forms from "../../../database/models/Forms";
export default async function handler(req, res) {
const session = await unstable_getServerSession(req, res, authOptions);
const { method } = req;
const { tableName } = req.query;
if (!session)
return res.end(
"You must be signed in to view the protected content on this page."
);
if (!tableName) return res.end("Cannot access API by URL");
await connectDB();
switch (method) {
case "GET":
try {
const forms = await Forms.find({});
res.status(200).json({ data: forms });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "DELETE":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed.id;
const result = await Forms.findOneAndDelete({ _id: id });
res.status(200).json({
success: true,
result,
message: "supprimé de la Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "PUT":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed._id;
const result = await Forms.findByIdAndUpdate(
{ _id: id },
{ ...bodyParsed }
);
res.status(200).json({
success: true,
result,
message: "Modifié en Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
}
}
import { unstable_getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import connectDB from "../../../database/connectDB";
import GlobalInfos from "../../../database/models/GlobalInfos";
export default async function handler(req, res) {
const session = await unstable_getServerSession(req, res, authOptions);
const { method } = req;
const { tableName } = req.query;
if (!session)
return res.end(
"You must be signed in to view the protected content on this page."
);
if (!tableName) return res.end("Cannot access API by URL");
await connectDB();
switch (method) {
case "GET":
try {
const globalInfos = await GlobalInfos.find({});
res.status(200).json({ data: globalInfos });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "DELETE":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed.id;
const result = await GlobalInfos.findOneAndDelete({ _id: id });
res.status(200).json({
success: true,
result,
message: "supprimé de la Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
case "PUT":
try {
const bodyParsed = JSON.parse(req.body);
const id = bodyParsed._id;
const result = await GlobalInfos.findByIdAndUpdate(
{ _id: id },
{ ...bodyParsed }
);
res.status(200).json({
success: true,
result,
message: "Modifié en Base de Donnée !",
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
break;
}
}
import { useMemo } from "react";
import DashBoard from "../Components/DashBoard";
const AsamExtensions = () => {
const columns = useMemo(
() => [
{
accessorKey: "_id",
header: "id",
enableEditing: false,
size: 200,
},
{
accessorKey: "extension",
header: "extension",
},
{
accessorKey: "entiteNomCourt",
header: "entiteNomCourt",
},
{
accessorKey: "entiteNomLong",
header: "entiteNomLong",
},
{
accessorKey: "familleNomCourt",
header: "familleNomCourt",
},
{
accessorKey: "familleNomLong",
header: "familleNomLong",
},
{
accessorKey: "structureId",
header: "structureId",
enableClickToCopy: true,
Cell: ({ cell }) => cell.getValue()?.toString(),
},
],
[]
);
return (
<>
<DashBoard columns={columns} apiRouteName={"asamextensions"} />
</>
);
};
export default AsamExtensions;