34 Commits

Author SHA1 Message Date
14a2a59786 merging
Some checks failed
CI / build (push) Failing after 7s
2025-03-26 11:57:13 +01:00
0ae6e7dfda merging 2025-03-26 11:53:49 +01:00
15ccb5630a MERGING 2025-03-26 11:52:04 +01:00
4ec292cca7 fix: merging 2025-03-26 11:50:23 +01:00
14a953536a merging 2025-03-26 11:45:40 +01:00
288f983816 merging 2025-03-26 11:44:49 +01:00
dd6032f3ef fix: css.. 2025-03-26 11:38:33 +01:00
7e2f5bc506 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks are pending
CI / build (push) Waiting to run
2025-03-26 11:32:14 +01:00
4ef92efd0e fix: js to ts in header 2025-03-26 11:31:54 +01:00
e769dd6757 fix: css fixing (again) 2025-03-26 11:30:59 +01:00
550a51523f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-26 11:24:28 +01:00
323cb05388 fix: css fixing 2025-03-26 11:07:50 +01:00
7e0851bfef Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-24 12:43:04 +01:00
98b6d167e8 fix: minor bugs on views 2025-03-24 12:34:17 +01:00
79baddb8f6 fix + feat: fixed some bugs + added a mock parser since the damn backend is not working, not finished tho (bugs) 2025-03-23 21:59:27 +01:00
0f8c83c2e2 feat: adding the endpoints' doc to the branch for an easy access 2025-03-21 01:20:34 +01:00
fad52644d2 fix : merged the main with the front branch and fixed all possible issues 2025-03-21 01:16:49 +01:00
5f51a1008b fix: fixing conflicts 2025-03-20 22:42:49 +01:00
279c171ba2 fix: Doc added (for me) 2025-03-19 11:54:34 +01:00
9ba8e3e84e feat: fake-data can be edited, switching to end-points branch 2025-03-19 10:52:12 +01:00
6de38a9725 feat: edit-mode added, to test it use the fake api in the 'fake_data' folder 2025-03-19 09:48:04 +01:00
c32eea8a40 Merge pull request 'Mise en place d'un linter, de formatteur et d'actions afin de vérifier que le code du frontend compile bien' (#3) from linter into main
All checks were successful
CI / build (push) Successful in 12s
Reviewed-on: #3
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Vu que vous ne voulez pas faire avancer le projet, je vais abuser de mes droits d'administrateur et le faire.
2025-02-10 22:44:34 +01:00
30344a60b7 fix: removed import
All checks were successful
CI / build (push) Successful in 13s
2025-02-09 16:12:43 +01:00
2465545b6b fix: removed temp modal
Some checks failed
CI / build (push) Failing after 10s
2025-02-09 16:11:54 +01:00
83cbeb7a2e feat: single workflow that check prettier, linter and build
Some checks failed
CI / build (push) Failing after 11s
2025-02-09 16:07:40 +01:00
645a10477d feat: now respect codestyle
All checks were successful
CI / build (push) Successful in 4s
2025-02-09 15:59:30 +01:00
a859871265 fix: prettier now fail instead of doing nothing
Some checks failed
CI / build (push) Failing after 5s
2025-02-09 15:57:40 +01:00
0eab9a8063 feat: implemented prettier
All checks were successful
CI / build (push) Successful in 4s
2025-02-09 15:54:19 +01:00
1dff7573ff feat: now compliant with eslint
All checks were successful
CI / build (push) Successful in 10s
2025-02-09 15:28:09 +01:00
8af40bfe50 fix: linter action: installed required modules
Some checks failed
CI / build (push) Failing after 11s
2025-02-09 14:04:12 +01:00
afa4d34ec8 fix: linter action
Some checks failed
CI / build (push) Failing after 7s
2025-02-09 14:02:34 +01:00
c5fc5b600e fix: linter action
Some checks failed
CI / build (push) Failing after 12s
2025-02-09 14:00:49 +01:00
a4939737fe feat: trying to setup linter
Some checks failed
CI / build (push) Failing after 35s
2025-02-09 13:57:17 +01:00
e7ebcc0d3a feat: created eslint config 2025-02-09 12:36:43 +01:00
28 changed files with 3110 additions and 334 deletions

View File

@ -0,0 +1,24 @@
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install everything
working-directory: ./front/MyINPulse-front
run: npm i
- name: Run ESLint
working-directory: ./front/MyINPulse-front
run: npx eslint
- name: Run prettier
working-directory: ./front/MyINPulse-front
run: npx prettier src --check
- name: Build frontend
working-directory: ./front/MyINPulse-front
run: npm run build

View File

@ -0,0 +1,741 @@
openapi: 3.0.3
info:
title: MyInpulse Backend Api
description: this document servers as a documentation for the backend api.
version: 0.0.0
tags:
- name: Entrepreneurs API
description: La partie de l'api dédiée aux entrepreneurs
- name: Admin API
description: La partie de l'api dédiée aux entrepreneurs
- name: Shared API
description: La partie de l'api dédiée aux entrepreneurs et admins
components:
schemas:
user:
type: object
properties:
nom:
type: string
prenom:
type: string
email:
type: string
example: "example@exmaple.com"
secondaryEmail:
type: string
example: "example@exmaple.com"
tel:
type: string
example: "0612345678"
user-entrepreneur:
type: object
properties:
user:
$ref: "#/components/schemas/user"
entrepreneur:
type: object
properties:
ecole:
type: string
example: "enseirb"
filiere:
type: string
example: "info"
status:
type: boolean
example: false
user-admin:
type: object
properties:
admin:
$ref: "#/components/schemas/user"
securitySchemes:
MyINPulse:
type: oauth2
flows:
implicit:
authorizationUrl: https://petstore3.swagger.io/oauth/authorize
scopes:
MyINPulse-admin: Administrateur
MyINPulse-entrepreneur: Utilisateur
paths:
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
# / _ \ | | | | |\/| || || \| | / _ \ | |_) | |
# / ___ \| |_| | | | || || |\ | / ___ \| __/| |
# /_/ \_\____/|_| |_|___|_| \_| /_/ \_\_| |___|
#
/admin/projects:
get:
summary: Retourne la liste of projets associés à l'admin
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
description:
JSON array of who's elements are objects containing necessary information for the view
(project name, entrepreneur names, etc..)
of the projects an admin is watching over.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
E_names:
type: string
description: entrepreneur names
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/admin/projects/pending/decision:
post:
summary: valider un projet en attente de validation
tags:
- Admin API
description:
if the request is accepted the status of the
project is changed to ongoing, entrepreneur
account is confirmed and the project is linked
to the admin accepting the request and the
entrepreneur requesting it. Else the pending
project and user info are deleted.
security:
- MyINPulse:
- MyINPulse-admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
pedingProjectId:
type: integer
decision:
type: boolean
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/add:
post:
summary: Ajout manuel d'un projet
description:
Adds a project with the
inputed details
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/appointments/report/{appointmentId}:
put:
summary: enregistrer un rapport du rendez-vous
description:
Generate a PDF file formatted
from input text and links it
to the appointement.
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
body:
type: string
conclusion:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
post:
summary: modifier un rapport déja éxistant du rendez-vous
description:
Modifies the report file to input
text and links it to the appointement.
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
body:
type: string
conclusion:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/remove/{projectId}:
delete:
summary: supression d'un project
description:
Removes the project
with the inputed id projectId
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: projectId
required: true
schema:
type: integer
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/pending:
get:
summary: Retourne la liste des projets en attente de validation
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
description:
JSON array of who's elements are objects containing
necessary information for the view (project name,
entrepreneur names, etc..) of all pending projects.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
#
# ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
# \___ \| '_ \ / _` | '__/ _ \/ _` | / _ \ | |_) | |
# ___) | | | | (_| | | | __/ (_| | / ___ \| __/| |
# |____/|_| |_|\__,_|_| \___|\__,_| /_/ \_\_| |___|
#
/shared/appointments/upcoming:
get:
summary: Retourne la list des prochains rendez-vous de l'utilisateur
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of upcoming appointment data (name, date, time etc..) for a user.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
date:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/lcsection/{projectId}/{title}/{date}:
get:
summary: Retourne la liste de sections de LC avec un titre donné
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array containing Lean Canvas
section data with a title for the
current date (or given date if the
date parameter is passed)
parameters:
- in: path
required: true
name: projectId
schema:
type: integer
- in: path
required: true
description: this number can be 1, 2,...,8. It is associated with the title of the lcsection
name: title
schema:
type: integer
enum: [1, 2, 3, 4, 5, 6, 7, 8]
- in: path
required: true
name: date
description: the date corresponding to the wanted version of lc section. "Nan" for the latest version
example: "NaN"
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
section:
type: string
txt:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/entrepreneurs/{projectId}:
get:
summary: Retourne la liste d'entrepreneurs associée à un projet donné
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of entrepreneur
names associated with a project
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/user-entrepreneur"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/admin/{projectId}:
get:
summary: Retourne les informations de l'admin qui accompagne le projet
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON object containing information (name, gmail, tel, etc..)
the admin supervising the project with id projectID.
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/user-admin"
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/{projectId}:
get:
summary: Retourne les rendez-vous du projet
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of upcoming and past appointment
data for the project with id projectID.
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
appointementId:
type: integer
name:
type: string
date:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/report/{apointementId}:
get:
summary: Retourne le rapport pdf du rendez-vous
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
PDF file containing the ap-
pointment report
parameters:
- in: path
name: apointementId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/pdf:
schema:
type: string
format: binary
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/appointments/request:
post:
summary: demander un rendez-vous
description:
will add an appointement request request by the applicant
to have an appointment to be confirmed or denied by the
specified participants of the appointement.
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-entrepreneur
- MyINPulse-admin
requestBody:
description: \"participants\" property is an array containing userids of the participants in the appointement
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
start_time:
type: string
end_time:
type: string
place:
type: string
applicantId:
type: integer
participants:
#/* */
type: array
items:
type: integer
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
# _____ _ _ _____ ____ _____ ____ ____ _____ _ _ _____ _ _ ____
# | ____| \ | |_ _| _ \| ____| _ \| _ \| ____| \ | | ____| | | | _ \
# | _| | \| | | | | |_) | _| | |_) | |_) | _| | \| | _| | | | | |_) |
# | |___| |\ | | | | _ <| |___| __/| _ <| |___| |\ | |___| |_| | _ <
# |_____|_|_\_| |_| |_| \_\_____|_| |_| \_\_____|_| \_|_____|\___/|_| \_\
# / \ | _ \_ _|
# / _ \ | |_) | |
# / ___ \| __/| |
# /_/ \_\_| |___|
#
/entrepreneur/projects/request:
post:
summary: demander la création et validation d'un projet
tags:
- Entrepreneurs API
description:
Adds project to pending projects
to then be accepted or rejected by
an admin
security:
- MyINPulse:
- MyINPulse-entrepreneur
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/add/{projectId}:
post:
summary: ajouter une sections au LC
description:
Adds input data to the user's LC
with a specified title.
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
txt:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/modify/{sectionId}:
put:
summary: modifier les données d'une section LC
description:
Modifies input Lean Canvas section by changing it to
the information in the request body and changes the
time stamp.
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: sectionId
schema:
type: integer
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
txt:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/remove/{sectionId}:
delete:
summary: supprimer une section LC.
description:
Deletes section from Lean Canvas
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: sectionId
schema:
type: integer
required: true
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid

0
front/Dockerfile Normal file → Executable file
View File

View File

@ -36,4 +36,4 @@ playwright-report/
# Custom # Custom
.installed .installed
package-lock.json ./package-lock.json

View File

@ -0,0 +1,7 @@
{
"useTabs":false,
"semi":true,
"trailingComma":"es5",
"arrowParens":"always",
"tabWidth":4
}

View File

@ -0,0 +1,29 @@
import eslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginVue from "eslint-plugin-vue";
import globals from "globals";
import typescriptEslint from "typescript-eslint";
export default typescriptEslint.config(
{ ignores: ["*.d.ts", "**/coverage", "**/dist"] },
{
extends: [
eslint.configs.recommended,
...typescriptEslint.configs.recommended,
...eslintPluginVue.configs["flat/recommended"],
],
files: ["**/*.{ts,vue}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: globals.browser,
parserOptions: {
parser: typescriptEslint.parser,
},
},
rules: {
// your rules
},
},
eslintConfigPrettier
);

View File

@ -5,6 +5,59 @@
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" } { "id": 3, "name": "Charlie", "email": "charlie@example.com" }
], ],
"data": [ "data": [
{ "canva_data": "this is a fake data to test api" } {
"projectId": 1,
"title": 1,
"title_text": "1. Problème",
"description": "3 problèmes essentiels à résoudre pour le client"
},
{
"projectId": 1,
"title": 2,
"title_text": "2. Segments",
"description": "Les segments de clientèle visés"
},
{
"projectId": 1,
"title": 3,
"title_text": "3. Valeur",
"description": "La proposition de valeur"
},
{
"projectId": 1,
"title": 4,
"title_text": "4. Solution",
"description": "Les solutions proposées"
},
{
"projectId": 1,
"title": 5,
"title_text": "5. Avantage",
"description": "Les avantages concurrentiels"
},
{
"projectId": 1,
"title": 6,
"title_text": "6. Canaux",
"description": "Les canaux de distribution"
},
{
"projectId": 1,
"title": 7,
"title_text": "7. Indicateurs",
"description": "Les indicateurs clés de performance"
},
{
"projectId": 1,
"title": 8,
"title_text": "8. Coûts",
"description": "Les coûts associés"
},
{
"projectId": 1,
"title": 9,
"title_text": "9. Revenus",
"description": "Les sources de revenus"
}
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@ -26,8 +26,15 @@
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
"eslint": "^9.20.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-vue": "^9.32.0",
"globals": "^15.14.0",
"jiti": "^2.4.2",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"prettier": "3.5.0",
"typescript": "~5.7.3", "typescript": "~5.7.3",
"typescript-eslint": "^8.23.0",
"vite": "^6.0.11", "vite": "^6.0.11",
"vite-plugin-vue-devtools": "^7.7.0", "vite-plugin-vue-devtools": "^7.7.0",
"vue-tsc": "^2.2.0" "vue-tsc": "^2.2.0"

View File

@ -2,25 +2,25 @@
<header> <header>
<img src="./icons/logo inpulse.png" alt="INPulse" /> <img src="./icons/logo inpulse.png" alt="INPulse" />
</header> </header>
</template> </template>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'Header', name: "HeaderComponent",
}; };
</script> </script>
<style scoped> <style scoped>
header img { header img {
width: 100px; width: 100px;
} }
header{ header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 20px; padding: 10px 20px;
background-color: #fff; background-color: #fff;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
} }
</style> </style>

View File

@ -15,6 +15,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from "vue"; import { defineProps } from "vue";
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'

View File

@ -1,46 +1,181 @@
<template> <template>
<div :class="['cell', { expanded }]" <div :class="['cell', { expanded }]" @click="handleClick">
@click="toggleExpand" <h3>{{ title_text }}</h3>
:style="{ justifyContent: expanded ? 'flex-start' : 'center' }"> <!-- Looking for finding a way
to make this style in the toggleExpand event --> <div class="section-bloc" v-for="(desc, index) in currentDescriptions" :key="index">
<!-- Mode affichage -->
<template v-if="!isEditing[index]">
<div class="description">
<p>{{ desc }}</p>
</div>
<div class="button-container">
<button v-if="expanded" @click.stop="startEditing(index)" class="edit-button">Éditer</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea v-model="editedDescriptions[index]" class="edit-input"></textarea>
<div class="button-container">
<button @click.stop="saveEdit(index)" class="save-button">Enregistrer</button>
<button @click.stop="cancelEdit(index)" class="cancel-button">Annuler</button>
</div>
</template>
</div>
<h3>{{ title }}</h3>
<p>{{ currentDescription }}</p>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineProps, onMounted } from "vue"; import { ref, defineProps } from "vue";
import axios from "axios"; import axios from "axios";
const IS_MOCK_MODE = true;
const props = defineProps<{ const props = defineProps<{
title: string; projectId: number;
title: number;
title_text: string;
description: string; description: string;
}>(); }>();
const expanded = ref(false); const expanded = ref(false);
const currentDescription = ref(props.description); const currentDescriptions = ref<string[]>([]);
const editedDescriptions = ref<string[]>([]);
const isEditing = ref<boolean[]>([]);
/*
const fetchData = async () => { const fetchData = async () => {
try { try {
const response = await axios.get("http://localhost:5000/data"); // Update the URL if needed const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL
if (response.data.length > 0) {
currentDescription.value = response.data[0].canva_data; currentDescription.value = response.data[0].canva_data;
editedDescription.value = response.data[0].canva_data;
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
// Fonction de simulation de l'API
const mockFetch = async (projectId: number, title: number, date: string) => {
console.log(`Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}`);
return new Promise<{ txt: string }[]>((resolve) => {
setTimeout(() => {
resolve([
{ txt: "Ceci est une description 1 pour tester the damn front, Why are u still reading dumbass this is just some nonsense sentence XD" },
{ txt: "Ceci est une description 1 pour tester the damn front, Bruh are u still here?" },
{ txt: "Ceci est une description 1 pour tester the damn front, .-. BRUH" }
]);
}, 500); // Simule un délai réseau de 500ms
});
};
// Fonction fetchData avec possibilité d'utiliser le mock
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => {
try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (await axios.get<{ txt: string }[]>(
`http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}`
)).data;
if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt);
editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false);
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) { } catch (error) {
console.error("Erreur lors de la récupération des données :", error); console.error("Erreur lors de la récupération des données :", error);
} }
}; };
const toggleExpand = async () => { // Utilisation du mock dans handleClick pour tester sans serveur
const handleClick = async () => {
if (!expanded.value) { if (!expanded.value) {
await fetchData(); await fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE); // true pour activer le mock
} else { } else if (!isEditing.value.includes(true)) {
currentDescription.value = props.description; // Réinitialiser les descriptions si aucune édition n'est en cours
currentDescriptions.value = [props.description];
editedDescriptions.value = [props.description];
} }
if (!isEditing.value.includes(true)) {
expanded.value = !expanded.value; expanded.value = !expanded.value;
}
}; };
</script>
const startEditing = (index: number) => {
isEditing.value[index] = true;
};
/*
const saveEdit = async (index: number) => {
try {
const id = index + 1; // À adapter selon l'ID réel des données
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescriptions.value[index]
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
};
*/
// Fonction de mock pour l'enregistrement
const mockSaveEdit = async (index: number) => {
try {
const id = index + 1; // À adapter selon l'ID réel des données
console.log(`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`);
// Simuler un délai d'enregistrement comme une requête réseau
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulation de délai réseau
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données mockées :", error);
}
};
// Utilisation de `mockSaveEdit` au lieu de `saveEdit` dans `handleClick` ou tout autre endroit
const saveEdit = async (index: number) => {
if (IS_MOCK_MODE) {
await mockSaveEdit(index);
} else {
try {
const id = index + 1;
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescriptions.value[index]
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
}
};
const cancelEdit = (index: number) => {
editedDescriptions.value[index] = currentDescriptions.value[index];
isEditing.value[index] = false;
};
</script>
<style scoped> <style scoped>
@import "@/components/canvas/style-project.css"; @import "@/components/canvas/style-project.css";
@ -56,6 +191,10 @@ const toggleExpand = async () => {
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
} }
.expanded-content {
justify-content: flex-start !important;
}
.cell:not(.expanded):hover { .cell:not(.expanded):hover {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
@ -82,7 +221,108 @@ const toggleExpand = async () => {
z-index: 10; z-index: 10;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
} }
.description {
display: flex;
align-items: center; /* Centre verticalement */
justify-content: center; /* Centre horizontalement */
text-align: center; /* Centre le texte à l'intérieur */
width: 100%;
height: 100%;
font-size: 16px;
margin-top: 10px;
margin-left: 2%;
margin-right: 4%;
}
.description + .p {
align-items: center;
justify-content: center;
text-align: center;
}
.edit-input {
width: 100%;
height: 100%;
font-size: 16px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-top: 10px;
box-sizing: border-box;
margin-left: 2%;
}
.button-container {
display: block;
margin-top: 20px;
justify-content: center;
align-items: center;
gap: 10px;
padding-right: 1%;
}
.section-bloc ,.editing-section-bloc {
width: 100%;
justify-content: center;
align-items: center;
display: flex;
margin-right: 10%;
margin: 10px;
}
.edit-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-right: 20px;
}
.save-button, .cancel-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-bottom: 5px;
}
.edit-button {
background-color: #007bff;
color: white;
}
.save-button {
background-color: #28a745;
color: white;
}
.cancel-button {
background-color: #dc3545;
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.save-button:hover {
background-color: #218838;
}
.cancel-button:hover {
background-color: #c82333;
}
</style> </style>

View File

@ -19,7 +19,7 @@
</template> </template>
<script setup> <script setup lang="ts">
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import axios from "axios"; import axios from "axios";
@ -34,7 +34,7 @@ const toggleDropdown = () => {
const fetchEntrepreneurs = async () => { const fetchEntrepreneurs = async () => {
try { try {
const response = await axios.get("http://localhost:5000/entrepreneurs"); const response = await axios.get("http://localhost:5000/entrepreneurs");
entrepreneurEmails.value = response.data.map(e => e.email); entrepreneurEmails.value = response.data.map((e: { email: string }) => e.email);
} catch (error) { } catch (error) {
console.error("Erreur lors de la récupération des entrepreneurs:", error); console.error("Erreur lors de la récupération des entrepreneurs:", error);
} }

View File

@ -4,7 +4,9 @@
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
:title="item.title" :title="item.title"
:title_text="item.title_text"
:description="item.description" :description="item.description"
:projectId="item.projectId"
:class="item.class" :class="item.class"
/> />
</div> </div>
@ -15,16 +17,17 @@ import { ref } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue"; import CanvasItem from "@/components/canvas/CanvasItem.vue";
const items = ref([ const items = ref([
{ title: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" }, { projectId: 1, title: 1, title_text: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" },
{ title: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" }, { projectId: 1, title: 2, title_text: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" },
{ title: "3. Valeur", description: "La proposition de valeur", class: "Valeur" }, { projectId: 1, title: 3, title_text: "3. Valeur", description: "La proposition de valeur", class: "Valeur" },
{ title: "4. Solution", description: "Les solutions proposées", class: "Solution" }, { projectId: 1, title: 4, title_text: "4. Solution", description: "Les solutions proposées", class: "Solution" },
{ title: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" }, { projectId: 1, title: 5, title_text: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" },
{ title: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" }, { projectId: 1, title: 6, title_text: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" },
{ title: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" }, { projectId: 1, title: 7, title_text: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" },
{ title: "8. Coûts", description: "Les coûts associés", class: "Couts" }, { projectId: 1, title: 8, title_text: "8. Coûts", description: "Les coûts associés", class: "Couts" },
{ title: "9. Revenus", description: "Les sources de revenus", class: "Revenus" } { projectId: 1, title: 9, title_text: "9. Revenus", description: "Les sources de revenus", class: "Revenus" }
]); ]);
</script> </script>
<style scoped> <style scoped>

View File

@ -1,17 +1,43 @@
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps(['data']); import { color } from "@/services/popupDisplayer.ts";
import type { PropType } from "vue";
defineProps({
errorMessage: {
type: Object as PropType<string>,
required: true,
},
errorColor: {
type: Object as PropType<color>,
default: color.Red,
},
});
</script> </script>
<template> <template>
<div :class='["red", "yellow", "blue", "green"][data.type]' class="error-modal"> <div
<p>{{["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][data.type]}}</p> :class="['red', 'yellow', 'blue', 'green'][errorColor]"
<p>{{data.errorMessage}}</p> class="error-modal"
<div class="loading" :class='["red-loader", "yellow-loader", "blue-loader", "green-loader"][data.type]'></div> >
<p>
{{
["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][errorColor]
}}
</p>
<p>{{ errorMessage }}</p>
<div
class="loading"
:class="
['red-loader', 'yellow-loader', 'blue-loader', 'green-loader'][
errorColor
]
"
></div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.error-modal{ .error-modal {
margin-bottom: 1em; margin-bottom: 1em;
padding: 1em; padding: 1em;
border-radius: 1em; border-radius: 1em;
@ -19,41 +45,45 @@ const props = defineProps(['data']);
overflow: hidden; overflow: hidden;
position: relative; position: relative;
animation: disappear 5s linear forwards; animation: disappear 5s linear forwards;
} }
.red{ .red {
background-color: #ee6055; background-color: #ee6055;
color: white; color: white;
} }
.red-loader {
.red-loader {
background-color: #fa8383; background-color: #fa8383;
} }
.yellow{ .yellow {
background-color: #FF9D23; background-color: #ff9d23;
color: white; color: white;
} }
.yellow-loader{
background-color: #ffbf81;
}
.blue { .yellow-loader {
background-color: #ffbf81;
}
.blue {
background-color: #809bce; background-color: #809bce;
color: white; color: white;
} }
.blue-loader{
background-color: #95b8d1;
}
.green { .blue-loader {
background-color: #95b8d1;
}
.green {
background-color: green; background-color: green;
color: white; color: white;
} }
.green-loader {
background-color: darkgreen;
}
.loading { .green-loader {
background-color: darkgreen;
}
.loading {
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
padding: 0; padding: 0;
@ -62,19 +92,19 @@ const props = defineProps(['data']);
height: 1em; height: 1em;
width: 0; width: 0;
animation: loading 4s linear forwards; animation: loading 4s linear forwards;
} }
/* Animation for the loading bar */ /* Animation for the loading bar */
@keyframes loading { @keyframes loading {
0% { 0% {
width: 100%; width: 100%;
} }
100% { 100% {
width: 0; width: 0;
} }
} }
@keyframes disappear { @keyframes disappear {
0% { 0% {
height: 100px; height: 100px;
padding: 1em; padding: 1em;
@ -90,5 +120,5 @@ const props = defineProps(['data']);
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
} }
</style> </style>

View File

@ -1,30 +1,30 @@
import { createApp } from 'vue' import { createApp } from "vue";
import App from './App.vue' import App from "./App.vue";
import router from './router/router.ts' import router from "./router/router.ts";
import {createPinia} from "pinia"; import { createPinia } from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import AuthStorePlugin from './plugins/authStore'; import keycloakService from "./services/keycloak";
import keycloakService from './services/keycloak'; import { type AuthStore, useAuthStore } from "@/stores/authStore.ts";
import {useAuthStore} from "@/stores/authStore.ts";
let store: any; let store: AuthStore;
keycloakService.CallInit(() => { keycloakService.CallInit(() => {
try { try {
const app = createApp(App) const app = createApp(App);
// Setup pinia store, allowing user to keep logged in status after refresh // Setup pinia store, allowing user to keep logged in status after refresh
const pinia = createPinia(); const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); pinia.use(piniaPluginPersistedstate);
app.use(pinia); app.use(pinia);
app.use(AuthStorePlugin, { pinia });
store = useAuthStore(); store = useAuthStore();
app.use(router) keycloakService.CallInitStore(store);
app.use(router);
app.mount('#app'); app.mount("#app");
} catch (e) { } catch (e) {
console.error("Error while initiating Keycloak.") console.error("Error while initiating Keycloak.");
console.error(e) console.error(e);
createApp(App).mount('#app'); createApp(App).mount("#app");
} }
}) })
@ -74,4 +74,4 @@ app.use(VueKeyCloak,{
export {store}; export { store };

View File

@ -1,4 +1,4 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -9,7 +9,7 @@ const router = createRouter({
// route level code-splitting // route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route // this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import('../views/test.vue'), component: () => import('../views/testComponent.vue'),
}, },
{ {
@ -27,4 +27,4 @@ const router = createRouter({
], ],
}) })
export default router export default router;

View File

@ -1,31 +1,36 @@
import axios from "axios"; import axios, { type AxiosError, type AxiosResponse } from "axios";
import {store} from "@/main.ts"; import { store } from "@/main.ts";
import {addNewMessage, color} from "@/services/popupDisplayer.ts"; import { addNewMessage, color } from "@/services/popupDisplayer.ts";
const axiosInstance = axios.create({ const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_BACKEND_URL, baseURL: import.meta.env.VITE_BACKEND_URL,
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
}, },
}); });
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
response => response, // Directly return successful responses. (response) => response, // Directly return successful responses.
async error => { async (error) => {
const originalRequest = error.config; const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry && store.authenticated) { if (
error.response.status === 401 &&
!originalRequest._retry &&
store.authenticated
) {
originalRequest._retry = true; // Mark the request as retried to avoid infinite loops. originalRequest._retry = true; // Mark the request as retried to avoid infinite loops.
try { try {
await store.refreshUserToken(); await store.refreshUserToken();
// Update the authorization header with the new access token. // Update the authorization header with the new access token.
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${store.user.token}`; axiosInstance.defaults.headers.common["Authorization"] =
`Bearer ${store.user.token}`;
return axiosInstance(originalRequest); // Retry the original request with the new access token. return axiosInstance(originalRequest); // Retry the original request with the new access token.
} catch (refreshError) { } catch (refreshError) {
// Handle refresh token errors by clearing stored tokens and redirecting to the login page. // Handle refresh token errors by clearing stored tokens and redirecting to the login page.
console.error('Token refresh failed:', refreshError); console.error("Token refresh failed:", refreshError);
localStorage.removeItem('accessToken'); localStorage.removeItem("accessToken");
localStorage.removeItem('refreshToken'); localStorage.removeItem("refreshToken");
window.location.href = '/login'; window.location.href = "/login";
return Promise.reject(refreshError); return Promise.reject(refreshError);
} }
} }
@ -34,23 +39,29 @@ axiosInstance.interceptors.response.use(
); );
// TODO: spawn a error modal // TODO: spawn a error modal
function defaultApiErrorHandler(err: string){ function defaultApiErrorHandler(err: AxiosError) {
addNewMessage(err, color.Red); addNewMessage(err.message, color.Red);
} }
function defaultApiSuccessHandler(response: any){ function defaultApiSuccessHandler(response: AxiosResponse) {
addNewMessage(response.data, color.green) addNewMessage(response.data, color.Green);
} }
function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void {
axiosInstance.get(endpoint).then( function callApi(
onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler endpoint: string,
).catch( onSuccessHandler?: (response: AxiosResponse) => void,
(err) => { onErrorHandler?: (error: AxiosError) => void
onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err); ): void {
throw err; axiosInstance
} .get(endpoint)
.then(
onSuccessHandler == null
? defaultApiSuccessHandler
: onSuccessHandler
) )
.catch(
onErrorHandler == null ? defaultApiErrorHandler : onErrorHandler
);
} }
export { callApi };
export {callApi}

View File

@ -1,33 +1,31 @@
import Keycloak from 'keycloak-js'; import Keycloak from "keycloak-js";
import type { AuthStore } from "@/stores/authStore.ts";
const options = { const options = {
url: import.meta.env.VITE_KEYCLOAK_URL, url: import.meta.env.VITE_KEYCLOAK_URL,
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID, clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
realm: import.meta.env.VITE_KEYCLOAK_REALM realm: import.meta.env.VITE_KEYCLOAK_REALM,
} };
const keycloak = new Keycloak(options); const keycloak = new Keycloak(options);
let authenticated: boolean | undefined; let authenticated: boolean | undefined;
let store = null; let store = null;
async function login(){ async function login() {
try { try {
await keycloak.login() // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login await keycloak.login(); // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
return keycloak; return keycloak;
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} }
async function signup(){ async function signup() {
try { try {
await keycloak.login( await keycloak.login({ action: "register" }); // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
{action: "register"}
) // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
return keycloak; return keycloak;
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} }
@ -42,31 +40,33 @@ async function init(onInitCallback: () => void) {
onLoad: "check-sso", onLoad: "check-sso",
silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.htm`, silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.htm`,
responseMode: "query", responseMode: "query",
}) });
onInitCallback() onInitCallback();
} catch (error) { } catch (error) {
console.error("Keycloak init failed") console.error("Keycloak init failed");
console.error(error) console.error(error);
} }
}; }
/** /**
* Initializes store with Keycloak user data * Initializes store with Keycloak user data
* *
*/ */
async function initStore(storeInstance: any) { async function initStore(storeInstance: AuthStore) {
try { try {
store = storeInstance store = storeInstance;
console.log(keycloak) console.log(keycloak);
await store.initOauth(keycloak) await store.initOauth(keycloak);
// Show alert if user is not authenticated // Show alert if user is not authenticated
if (!authenticated) { console.warn("not authenticated") } if (!authenticated) {
} catch (error) { console.warn("not authenticated");
console.error("Keycloak init failed")
console.error(error)
} }
}; } catch (error) {
console.error("Keycloak init failed");
console.error(error);
}
}
/** /**
* Logout user * Logout user
@ -83,7 +83,7 @@ async function refreshToken() {
await keycloak.updateToken(480); await keycloak.updateToken(480);
return keycloak; return keycloak;
} catch (error) { } catch (error) {
console.error('Failed to refresh token'); console.error("Failed to refresh token");
console.error(error); console.error(error);
} }
} }

View File

@ -1,19 +1,43 @@
import {ref} from "vue"; import { ref, type Ref } from "vue";
enum color {Red, Yellow, Blue, green}
function addNewMessage(errorMessage: string, type?: color, timeout?: number){ enum color {
if (timeout == null){ Red,
Yellow,
Blue,
Green,
}
type ErrorMessageContent = {
message: string;
color: color;
id: number;
timeout: number;
};
let id: number = 0;
const getId = () => {
id = id + 1;
return id;
};
function addNewMessage(errorMessage: string, type?: color, timeout?: number) {
if (timeout == null) {
timeout = 5000; timeout = 5000;
} }
if (type == null){ if (type == null) {
type = color.Red; type = color.Red;
} }
const data = {errorMessage: errorMessage, timeout: timeout, type: type, uid: Math.random()*100000}; const data: ErrorMessageContent = {
errorList.value.push(data) message: errorMessage,
setTimeout(() => errorList.value.slice(0, 1), timeout) timeout: timeout,
color: type,
id: getId(),
};
errorList.value.push(data);
setTimeout(() => errorList.value.slice(0, 1), timeout);
} }
const errorList: any= ref([]) const errorList: Ref<ErrorMessageContent[]> = ref([]);
export {addNewMessage, errorList, color} export { addNewMessage, errorList, color, type ErrorMessageContent };

View File

@ -1,54 +1,61 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import keycloakService from '@/services/keycloak'; import keycloakService from "@/services/keycloak";
import type Keycloak from "keycloak-js"; import type Keycloak from "keycloak-js";
export const useAuthStore = defineStore("storeAuth", {
const useAuthStore = defineStore("storeAuth", {
state: () => { state: () => {
return { return {
testv: true,
authenticated: false, authenticated: false,
user: { user: {
token: "", token: "",
refreshToken: "", refreshToken: "",
username: "", username: "",
}, },
} };
}, },
persist: true, persist: true,
getters: {}, getters: {},
actions: { actions: {
// Initialize Keycloak OAuth // Initialize Keycloak OAuth
async initOauth(keycloak: Keycloak, clearData = true) { async initOauth(keycloak: Keycloak, clearData = true) {
if(clearData) { await this.clearUserData(); } if (clearData) {
await this.clearUserData();
}
this.authenticated = !!keycloak.authenticated; // the !! removes undefined this.authenticated = !!keycloak.authenticated; // the !! removes undefined
if (this.authenticated && keycloak.token && keycloak.idTokenParsed && keycloak.refreshToken){ if (
this.authenticated &&
keycloak.token &&
keycloak.idTokenParsed &&
keycloak.refreshToken
) {
this.user.username = keycloak.idTokenParsed.given_name; this.user.username = keycloak.idTokenParsed.given_name;
this.user.token = keycloak.token; this.user.token = keycloak.token;
this.user.refreshToken = keycloak.refreshToken; this.user.refreshToken = keycloak.refreshToken;
} }
}, },
async login(){ async login() {
try { try {
const keycloak = await keycloakService.callLogin(); const keycloak = await keycloakService.callLogin();
if (keycloak) if (keycloak) await this.initOauth(keycloak);
await this.initOauth(keycloak);
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
}, },
async signup() { async signup() {
try { try {
const keycloak = await keycloakService.callSignup(); const keycloak = await keycloakService.callSignup();
if (keycloak) if (keycloak) await this.initOauth(keycloak);
await this.initOauth(keycloak);
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
}, },
// Logout user // Logout user
async logout() { async logout() {
try { try {
await keycloakService.CallLogout(import.meta.env.VITE_APP_URL + "/test"); await keycloakService.CallLogout(
import.meta.env.VITE_APP_URL + "/test"
);
await this.clearUserData(); await this.clearUserData();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -58,15 +65,11 @@ export const useAuthStore = defineStore("storeAuth", {
async refreshUserToken() { async refreshUserToken() {
try { try {
const keycloak = await keycloakService.CallTokenRefresh(); const keycloak = await keycloakService.CallTokenRefresh();
if (keycloak) if (keycloak) await this.initOauth(keycloak, false);
await this.initOauth(keycloak, false);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}, },
test() {
this.testv = !this.testv;
},
// Clear user's store data // Clear user's store data
clearUserData() { clearUserData() {
this.authenticated = false; this.authenticated = false;
@ -75,6 +78,10 @@ export const useAuthStore = defineStore("storeAuth", {
refreshToken: "", refreshToken: "",
username: "", username: "",
}; };
} },
} },
}); });
type AuthStore = ReturnType<typeof useAuthStore>;
export { useAuthStore, type AuthStore };

View File

@ -0,0 +1,215 @@
<template>
<div :class="['cell', { expanded }]" @click="handleClick">
<h3>{{ title_text }}</h3>
<!-- Mode affichage -->
<template v-if="!isEditing">
<p>{{ currentDescription }}</p>
<div class="button-container">
<button v-if="expanded" @click.stop="startEditing" class="edit-button">Éditer</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea v-model="editedDescription" class="edit-input"></textarea>
<div class="button-container">
<button @click.stop="saveEdit" class="save-button">Enregistrer</button>
<button @click.stop="cancelEdit" class="cancel-button">Annuler</button>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps } from "vue";
import axios from "axios";
const props = defineProps<{
projectId: number;
title: number;
title_text: string;
description: string;
}>();
const expanded = ref(false);
const isEditing = ref(false);
const currentDescription = ref(props.description);
const editedDescription = ref(props.description);
/*
const fetchData = async () => {
try {
const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL
if (response.data.length > 0) {
currentDescription.value = response.data[0].canva_data;
editedDescription.value = response.data[0].canva_data;
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
const fetchData = async (projectId: number, title: number, date: string) => {
try {
const response = await axios.get(
`http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}`
);
if (response.data.length > 0) {
currentDescription.value = response.data[0].txt;
editedDescription.value = response.data[0].txt;
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
const handleClick = async () => {
if (!expanded.value) {
await fetchData(props.projectId, props.title, "NaN");
} else if (!isEditing.value) {
currentDescription.value = props.description;
editedDescription.value = props.description;
}
if (!isEditing.value) {
expanded.value = !expanded.value;
}
};
const startEditing = () => {
isEditing.value = true;
};
const saveEdit = async () => {
try {
const id = 1;
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescription.value
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescription.value = editedDescription.value;
isEditing.value = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
};
const cancelEdit = () => {
editedDescription.value = currentDescription.value;
isEditing.value = false;
};
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
}
.expanded-content {
justify-content: flex-start !important;
}
.cell:not(.expanded):hover {
transform: scale(1.05);
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
}
.cell h3 {
font-size: 20px;
font-weight: 500;
/*margin-bottom: 10px;*/
}
.cell p {
font-size: 14px;
color: #666;
}
.expanded {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
z-index: 10;
display: flex;
align-items: center;
justify-content: flex-start;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.edit-input {
width: 80%;
height: 100px;
font-size: 16px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-top: 10px;
}
.button-container {
position: absolute;
bottom: 40px;
right: 40px;
display: flex;
gap: 10px;
}
.edit-button, .save-button, .cancel-button {
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 16px;
}
.edit-button {
background-color: #007bff;
color: white;
}
.save-button {
background-color: #28a745;
color: white;
}
.cancel-button {
background-color: #dc3545;
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.save-button:hover {
background-color: #218838;
}
.cancel-button:hover {
background-color: #c82333;
}
</style>

View File

@ -1,15 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

View File

@ -18,9 +18,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Header from '../components/Header.vue'; import Header from '../components/HeaderComponent.vue';
import Agenda from "../components/Agenda.vue" import Agenda from "../components/Agenda.vue"
import ProjectComp from '../components/Project-comp.vue'; import ProjectComp from '../components/ProjectComponent.vue';
import { ref } from "vue"; import { ref } from "vue";

View File

@ -11,7 +11,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import HeaderCanvas from '../components/canvas/HeaderCanvas.vue'; // @ts-ignore
import HeaderCanvas from "../components/canvas/HeaderCanvas.vue";
import LeanCanvas from '../components/canvas/LeanCanvas.vue'; import LeanCanvas from '../components/canvas/LeanCanvas.vue';
</script> </script>

View File

@ -1,20 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { errorList } from "@/services/popupDisplayer.ts";
import {errorList} from "@/services/popupDisplayer.ts";
import ErrorModal from "@/components/errorModal.vue"; import ErrorModal from "@/components/errorModal.vue";
</script> </script>
<template> <template>
<div class="error-wrapper"> <div class="error-wrapper">
<error-modal v-for="elm in errorList" :data=elm></error-modal> <error-modal
</div> v-for="elm in errorList"
:key="elm.id"
:error-message="elm.message"
:error-color="elm.color"
/>
</div>
</template> </template>
<style scoped> <style scoped>
.error-wrapper{ .error-wrapper{
position: absolute; position: absolute;
left: 70%; left: 70%;
//background-color: blue; /*background-color: blue;*/
height: 100%; height: 100%;
width: 30%; width: 30%;

View File

@ -1,75 +0,0 @@
<script setup lang="ts">
import {store} from "../main.ts";
import {callApi} from "@/services/api.ts";
import ErrorModal from "@/components/errorModal.vue";
import {errorList} from "@/services/popupDisplayer.ts";
//import TempModal from "@/components/temp-modal.vue";
import ErrorWrapper from "@/App.vue";
function addResToTable(id: any){
return (req: any) => {
console.log(req)
}
}
</script>
<template>
<h1>Test page</h1>
<table class="test" style="width:100%">
<tbody>
<tr>
<td>Is Currently Authenticated ? </td>
<td>{{store.authenticated}}</td>
<td><button @click="store.login">Login</button></td>
<td><button @click="store.logout">Logout</button></td>
<td><button @click="store.signup">Signup</button></td>
</tr>
<tr>
<td>current token</td>
<td>{{store.user.token}}</td>
<td><button @click="store.refreshUserToken">Refresh</button></td>
</tr>
<tr>
<td>Current refresh token</td>
<td>{{store.user.refreshToken}}</td>
</tr>
<tr>
<td>Entrepreneur API call</td>
<td><button @click="callApi('random')">call</button></td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Admin API call</td>
<td><button @click="callApi('random2')">call</button></td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Unauth API call</td>
<td><button @click="callApi('random3')">call</button></td>
<td>res</td>
<td id="3"></td>
</tr>
</tbody>
</table>
<temp-modal></temp-modal>
</template>
<style scoped>
table {
width: 100px;
table-layout: fixed
}
tr {
width: 100%;
height: 100%;
}
td {
border: solid 1px black;
width: 20%;
height: 100%;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,79 @@
<script lang="ts" setup>
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
</script>
<template>
<h1>Test page</h1>
<table class="test" style="width: 100%">
<tbody>
<tr>
<td>Is Currently Authenticated ?</td>
<td>{{ store.authenticated }}</td>
<td>
<button @click="store.login">Login</button>
</td>
<td>
<button @click="store.logout">Logout</button>
</td>
<td>
<button @click="store.signup">Signup</button>
</td>
</tr>
<tr>
<td>current token</td>
<td>{{ store.user.token }}</td>
<td>
<button @click="store.refreshUserToken">Refresh</button>
</td>
</tr>
<tr>
<td>Current refresh token</td>
<td>{{ store.user.refreshToken }}</td>
</tr>
<tr>
<td>Entrepreneur API call</td>
<td>
<button @click="callApi('random')">call</button>
</td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Admin API call</td>
<td>
<button @click="callApi('random2')">call</button>
</td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Unauth API call</td>
<td>
<button @click="callApi('random3')">call</button>
</td>
<td>res</td>
<td id="3"></td>
</tr>
</tbody>
</table>
</template>
<style scoped>
table {
width: 100px;
table-layout: fixed;
}
tr {
width: 100%;
height: 100%;
}
td {
border: solid 1px black;
width: 20%;
height: 100%;
overflow: hidden;
}
</style>