project creation request form working
This commit is contained in:
parent
6c84f723f7
commit
c750001a2c
14 changed files with 194 additions and 89 deletions
|
|
@ -14,6 +14,15 @@ tabs:
|
||||||
columns:
|
columns:
|
||||||
- width: 1/1
|
- width: 1/1
|
||||||
fields:
|
fields:
|
||||||
|
isClientRequest:
|
||||||
|
type: hidden
|
||||||
|
default: "false"
|
||||||
|
requestDetails:
|
||||||
|
label: Demande client
|
||||||
|
type: textarea
|
||||||
|
disabled: true
|
||||||
|
when:
|
||||||
|
isClientRequest: "true"
|
||||||
currentStep:
|
currentStep:
|
||||||
label: Étape en cours
|
label: Étape en cours
|
||||||
type: radio
|
type: radio
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,34 @@
|
||||||
title: Site
|
title: Projets
|
||||||
|
|
||||||
tabs:
|
tabs:
|
||||||
dashboard:
|
contentTab:
|
||||||
label: Dashboard
|
label: Contenu
|
||||||
icon: dashboard
|
|
||||||
columns:
|
columns:
|
||||||
|
- width: 1/1
|
||||||
|
sections:
|
||||||
|
drafts:
|
||||||
|
extends: sections/projects
|
||||||
|
headline: Brouillons
|
||||||
|
status: draft
|
||||||
- width: 1/1
|
- width: 1/1
|
||||||
sections:
|
sections:
|
||||||
listed:
|
listed:
|
||||||
extends: sections/projects
|
extends: sections/projects
|
||||||
headline: Projets en cours
|
headline: En cours
|
||||||
query: user.currentProjects
|
query: user.currentProjects
|
||||||
sortBy: modified desc
|
sortBy: modified desc
|
||||||
- width: 1/1
|
- width: 1/1
|
||||||
sections:
|
sections:
|
||||||
unlisted:
|
unlisted:
|
||||||
extends: sections/projects
|
extends: sections/projects
|
||||||
headline: Projets archivés
|
headline: Archivés
|
||||||
query: user.archivedProjects
|
query: user.archivedProjects
|
||||||
sortBy: modified desc
|
sortBy: modified desc
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
label: Paramètres
|
label: Réglages
|
||||||
icon: cog
|
icon: cog
|
||||||
columns:
|
fields:
|
||||||
- width: 1/1
|
clientBriefImageTags:
|
||||||
sections:
|
label: Tags des images ajoutées aux briefs
|
||||||
settings:
|
type: tags
|
||||||
label: Paramètres
|
|
||||||
type: fields
|
|
||||||
fields:
|
|
||||||
description:
|
|
||||||
label: Description
|
|
||||||
type: text
|
|
||||||
max: 160
|
|
||||||
help: Description en une phrase du service
|
|
||||||
moodboardTags:
|
|
||||||
label: Tags des images de Brief client
|
|
||||||
help: Ensemble des tags d’images disponibles lors de la création du Brief client
|
|
||||||
type: tags
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
title: Client
|
|
||||||
description: Ne peut pas accéder au Panel, peut accéder aux Projets auxquels il est assigné côté front.
|
|
||||||
permissions:
|
|
||||||
access:
|
|
||||||
panel: false
|
|
||||||
|
|
||||||
fields:
|
|
||||||
projects:
|
|
||||||
label: Projets
|
|
||||||
type: pages
|
|
||||||
query: page('projects').children
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
title: Interne Pochet
|
|
||||||
description: Peux accéder au Panel mais pas aux Utilisateurs, peut accéder aux Projets auxquels il est assigné par un Admin.
|
|
||||||
home: /panel/pages/projects
|
|
||||||
permissions:
|
|
||||||
access:
|
|
||||||
users: false
|
|
||||||
|
|
||||||
fields:
|
|
||||||
job:
|
|
||||||
label: Métier
|
|
||||||
type: select
|
|
||||||
options:
|
|
||||||
- Project Manager
|
|
||||||
- Sales Manager
|
|
||||||
default: Project Panager
|
|
||||||
width: 1/4
|
|
||||||
projects:
|
|
||||||
label: Projets
|
|
||||||
type: pages
|
|
||||||
query: page('projects').children
|
|
||||||
width: 3/4
|
|
||||||
|
|
@ -5,7 +5,18 @@ permissions:
|
||||||
panel: false
|
panel: false
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
|
client:
|
||||||
|
type: pages
|
||||||
|
multiple: false
|
||||||
|
query: site.find("clients").childrenAndDrafts
|
||||||
|
subpages: false
|
||||||
|
image:
|
||||||
|
query: page.logo.toFile
|
||||||
|
layout: cardlets
|
||||||
|
required: true
|
||||||
|
width: 1/3
|
||||||
projects:
|
projects:
|
||||||
label: Projets
|
label: Projets
|
||||||
type: pages
|
type: pages
|
||||||
query: page('projects').children
|
query: page('projects').children
|
||||||
|
width: 2/3
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ return [
|
||||||
require(__DIR__ . '/routes/remove-file.php'),
|
require(__DIR__ . '/routes/remove-file.php'),
|
||||||
require(__DIR__ . '/routes/upload-pdf.php'),
|
require(__DIR__ . '/routes/upload-pdf.php'),
|
||||||
require(__DIR__ . '/routes/validate-brief.php'),
|
require(__DIR__ . '/routes/validate-brief.php'),
|
||||||
|
require(__DIR__ . '/routes/request-project-creation.php'),
|
||||||
],
|
],
|
||||||
'hooks' => [
|
'hooks' => [
|
||||||
'page.create:after' => require_once(__DIR__ . '/hooks/create-steps.php'),
|
'page.create:after' => require_once(__DIR__ . '/hooks/create-steps.php'),
|
||||||
|
|
|
||||||
37
public/site/config/routes/request-project-creation.php
Normal file
37
public/site/config/routes/request-project-creation.php
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'pattern' => 'request-project-creation.json',
|
||||||
|
'method' => 'POST',
|
||||||
|
'action' => function () {
|
||||||
|
$json = file_get_contents('php://input');
|
||||||
|
$data = json_decode($json);
|
||||||
|
|
||||||
|
$client = kirby()->user()->client()->toPage()->uuid();
|
||||||
|
|
||||||
|
$projectData = [
|
||||||
|
"slug" => esc(Str::slug($data->title)),
|
||||||
|
"template" => "project",
|
||||||
|
"content" => [
|
||||||
|
"title" => esc($data->title),
|
||||||
|
"requestDetails" => esc("Demande de " . kirby()->user()->name() . " (" . kirby()->user()->email() . ") : \n\n" . $data->details),
|
||||||
|
"client" => [$client],
|
||||||
|
"isClientRequest" => "true",
|
||||||
|
"isDTLEnabled" => esc($data->isDTLEnabled)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$projects = page("projects");
|
||||||
|
try {
|
||||||
|
$projects->createChild($projectData);
|
||||||
|
return [
|
||||||
|
"status" => "success",
|
||||||
|
];
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return [
|
||||||
|
"status" => "error",
|
||||||
|
"message" => $th->getMessage() . " in " . $th->getFile() . " line " . $th->getLine()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
16
src/App.vue
16
src/App.vue
|
|
@ -4,28 +4,12 @@
|
||||||
<Menu />
|
<Menu />
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
<DTLPanel v-if="isDTLPanelOpen" @close="isDTLPanelOpen = false" />
|
|
||||||
<DTLButton
|
|
||||||
v-if="page?.designToLight?.length > 0"
|
|
||||||
@click="openDTLPanel($event)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import Menu from "./components/Menu.vue";
|
import Menu from "./components/Menu.vue";
|
||||||
import { usePageStore } from "./stores/page";
|
import { usePageStore } from "./stores/page";
|
||||||
import DTLButton from "./components/design-to-light/DTLButton.vue";
|
|
||||||
import DTLPanel from "./components/design-to-light/DTLPanel.vue";
|
|
||||||
import { ref } from "vue";
|
|
||||||
|
|
||||||
const { page } = storeToRefs(usePageStore());
|
const { page } = storeToRefs(usePageStore());
|
||||||
|
|
||||||
const isDTLPanelOpen = ref(false);
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
function openDTLPanel(event) {
|
|
||||||
isDTLPanelOpen.value = true;
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<template>
|
|
||||||
<button :data-size="size">Label</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const { size } = defineProps({
|
|
||||||
size: String,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
11
src/components/ProjectRequestButton.vue
Normal file
11
src/components/ProjectRequestButton.vue
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<button class="btn">Demander la création d'un projet</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
61
src/components/ProjectRequestDialog.vue
Normal file
61
src/components/ProjectRequestDialog.vue
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<input type="text" v-model="title" placeholder="Nom du projet" required />
|
||||||
|
<textarea
|
||||||
|
name="details"
|
||||||
|
v-model="details"
|
||||||
|
cols="30"
|
||||||
|
rows="10"
|
||||||
|
placeholder="Détails du projet"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
<input type="checkbox" v-model="isDTLEnabled" />
|
||||||
|
<button type="submit">Soumettre</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useApiStore } from "../stores/api";
|
||||||
|
|
||||||
|
const title = ref("");
|
||||||
|
const details = ref("");
|
||||||
|
const isDTLEnabled = ref(false);
|
||||||
|
const api = useApiStore();
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
const formData = {
|
||||||
|
title: title.value,
|
||||||
|
details: details.value,
|
||||||
|
isDTLEnabled: isDTLEnabled.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await api.requestProjectCreation(formData);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
form {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -75,6 +75,28 @@ export const useApiStore = defineStore("api", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function requestProjectCreation(data) {
|
||||||
|
const headers = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/request-project-creation.json", headers);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Une erreur s'est produite lors de l'ajout du commentaire :",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function updateComment(comment) {
|
async function updateComment(comment) {
|
||||||
const headers = {
|
const headers = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -144,12 +166,6 @@ export const useApiStore = defineStore("api", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} comment
|
|
||||||
* @param {string} projectId UUID or URI
|
|
||||||
* @returns status with message if error
|
|
||||||
*/
|
|
||||||
async function readNotification(notificationId, projectId) {
|
async function readNotification(notificationId, projectId) {
|
||||||
const headers = {
|
const headers = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -233,5 +249,6 @@ export const useApiStore = defineStore("api", () => {
|
||||||
readNotification,
|
readNotification,
|
||||||
readAllNotifications,
|
readAllNotifications,
|
||||||
validateBrief,
|
validateBrief,
|
||||||
|
requestProjectCreation,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<main class="flex flex-col" style="--row-gap: var(--space-32)">
|
<main class="flex flex-col" style="--row-gap: var(--space-32)">
|
||||||
<Projects />
|
<Projects />
|
||||||
|
<ProjectRequestDialog v-if="isProjectRequestDialogOpen" />
|
||||||
|
<ProjectRequestButton @click="isProjectRequestDialogOpen = true" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Projects from "../components/Projects.vue";
|
import Projects from "../components/Projects.vue";
|
||||||
|
import ProjectRequestButton from "../components/ProjectRequestButton.vue";
|
||||||
|
import ProjectRequestDialog from "../components/ProjectRequestDialog.vue";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const isProjectRequestDialogOpen = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
<div class="kanban">
|
<div class="kanban">
|
||||||
<ProjectStep v-for="step in page.steps" :key="step" :step="step" />
|
<ProjectStep v-for="step in page.steps" :key="step" :step="step" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<DTLPanel v-if="isDTLPanelOpen" @close="isDTLPanelOpen = false" />
|
||||||
|
<DTLButton
|
||||||
|
v-if="page?.designToLight?.length > 0"
|
||||||
|
@click="openDTLPanel($event)"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -16,9 +22,11 @@ import Header from "../components/project/Header.vue";
|
||||||
import DialogWrapper from "../components/project/DialogWrapper.vue";
|
import DialogWrapper from "../components/project/DialogWrapper.vue";
|
||||||
import { usePageStore } from "../stores/page";
|
import { usePageStore } from "../stores/page";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { watch } from "vue";
|
import { watch, ref } from "vue";
|
||||||
import { useDialogStore } from "../stores/dialog";
|
import { useDialogStore } from "../stores/dialog";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import DTLButton from "../components/design-to-light/DTLButton.vue";
|
||||||
|
import DTLPanel from "../components/design-to-light/DTLPanel.vue";
|
||||||
|
|
||||||
const { page } = storeToRefs(usePageStore());
|
const { page } = storeToRefs(usePageStore());
|
||||||
const dialog = useDialogStore();
|
const dialog = useDialogStore();
|
||||||
|
|
@ -29,6 +37,8 @@ if (route.query.dialog) {
|
||||||
openDialog(route.query.dialog);
|
openDialog(route.query.dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDTLPanelOpen = ref(false);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.query.dialog,
|
() => route.query.dialog,
|
||||||
(targetStepSlug) => {
|
(targetStepSlug) => {
|
||||||
|
|
@ -43,10 +53,16 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Functions
|
||||||
function openDialog(targetStepSlug) {
|
function openDialog(targetStepSlug) {
|
||||||
const targetStep = page.value.steps.find(
|
const targetStep = page.value.steps.find(
|
||||||
(step) => step.slug === targetStepSlug
|
(step) => step.slug === targetStepSlug
|
||||||
);
|
);
|
||||||
dialog.content = targetStep;
|
dialog.content = targetStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openDTLPanel(event) {
|
||||||
|
isDTLPanelOpen.value = true;
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue