Merge branch '331-manage-attachments-to-workflow' into 'master'

Add attachments to workflow

Closes #331

See merge request Chill-Projet/chill-bundles!764
This commit is contained in:
2025-02-03 21:15:00 +00:00
106 changed files with 3455 additions and 619 deletions

View File

@@ -480,7 +480,7 @@ div.workflow {
section.step {
border: 1px solid $chill-l-gray;
padding: 1em 2em;
div.flex-table {
> div.flex-table {
margin: 1.5em -2em;
}
}

View File

@@ -83,6 +83,10 @@ export const makeFetch = <Input, Output>(
opts = Object.assign(opts, options);
}
return fetch(url, opts).then((response) => {
if (response.status === 204) {
return Promise.resolve();
}
if (response.ok) {
return response.json();
}
@@ -173,18 +177,26 @@ function _fetchAction<T>(
throw new Error("other network error");
})
.catch((reason: any) => {
console.error(reason);
throw new Error(reason);
});
.catch(
(
reason:
| NotFoundExceptionInterface
| ServerExceptionInterface
| ValidationExceptionInterface
| TransportExceptionInterface,
) => {
console.error(reason);
throw reason;
},
);
}
export const fetchResults = async <T>(
uri: string,
params?: FetchParams,
): Promise<T[]> => {
let promises: Promise<T[]>[] = [],
page = 1;
const promises: Promise<T[]>[] = [];
let page = 1;
const firstData: PaginationResponse<T> = (await _fetchAction(
page,
uri,
@@ -229,6 +241,7 @@ const ValidationException = (
return error;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const AccessException = (response: Response): AccessExceptionInterface => {
const error = {} as AccessExceptionInterface;
error.name = "AccessException";
@@ -237,6 +250,7 @@ const AccessException = (response: Response): AccessExceptionInterface => {
return error;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const NotFoundException = (response: Response): NotFoundExceptionInterface => {
const error = {} as NotFoundExceptionInterface;
error.name = "NotFoundException";
@@ -257,6 +271,7 @@ const ServerException = (
};
const ConflictHttpException = (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
response: Response,
): ConflictHttpExceptionInterface => {
const error = {} as ConflictHttpExceptionInterface;

View File

@@ -0,0 +1,22 @@
import { WorkflowAttachment } from "ChillMainAssets/types";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
export const find_attachments_by_workflow = async (
workflowId: number,
): Promise<WorkflowAttachment[]> =>
makeFetch("GET", `/api/1.0/main/workflow/${workflowId}/attachment`);
export const create_attachment = async (
workflowId: number,
genericDoc: GenericDocForAccompanyingPeriod,
): Promise<WorkflowAttachment> =>
makeFetch("POST", `/api/1.0/main/workflow/${workflowId}/attachment`, {
relatedGenericDocKey: genericDoc.key,
relatedGenericDocIdentifiers: genericDoc.identifiers,
});
export const delete_attachment = async (
attachment: WorkflowAttachment,
): Promise<void> =>
makeFetch("DELETE", `/api/1.0/main/workflow/attachment/${attachment.id}`);

View File

@@ -1,3 +1,5 @@
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
export interface DateTime {
datetime: string;
datetime8601: string;
@@ -190,3 +192,14 @@ export interface WorkflowAvailable {
name: string;
text: string;
}
export interface WorkflowAttachment {
id: number;
relatedGenericDocKey: string;
relatedGenericDocIdentifiers: object;
createdAt: DateTime | null;
createdBy: User | null;
updatedAt: DateTime | null;
updatedBy: User | null;
genericDoc: null | GenericDoc;
}

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { computed, useTemplateRef } from "vue";
import type { WorkflowAttachment } from "ChillMainAssets/types";
import PickGenericDocModal from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocModal.vue";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import AttachmentList from "ChillMainAssets/vuejs/WorkflowAttachment/Component/AttachmentList.vue";
import { GenericDoc } from "ChillDocStoreAssets/types";
interface AppConfig {
workflowId: number;
accompanyingPeriodId: number;
attachments: WorkflowAttachment[];
}
const emit = defineEmits<{
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
}>();
type PickGenericModalType = InstanceType<typeof PickGenericDocModal>;
const pickDocModal = useTemplateRef<PickGenericModalType>("pickDocModal");
const props = defineProps<AppConfig>();
const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>(
() =>
props.attachments
.map((a: WorkflowAttachment) => a.genericDoc)
.filter(
(g: GenericDoc | null) => g !== null,
) as GenericDocForAccompanyingPeriod[],
);
const openModal = function () {
pickDocModal.value?.openModal();
};
const onPickGenericDoc = ({
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
}) => {
emit("pickGenericDoc", { genericDoc });
};
</script>
<template>
<pick-generic-doc-modal
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc"
ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal>
<attachment-list
:attachments="props.attachments"
@removeAttachment="(payload) => emit('removeAttachment', payload)"
></attachment-list>
<ul class="record_actions">
<li>
<button type="button" class="btn btn-create" @click="openModal">
Ajouter une pièce jointe
</button>
</li>
</ul>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,52 @@
<script setup lang="ts">
import { WorkflowAttachment } from "ChillMainAssets/types";
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface AttachmentListProps {
attachments: WorkflowAttachment[];
}
const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
}>();
const props = defineProps<AttachmentListProps>();
</script>
<template>
<p
v-if="props.attachments.length === 0"
class="chill-no-data-statement text-center"
>
Aucune pièce jointe
</p>
<!-- TODO translate -->
<div else class="flex-table">
<div v-for="a in props.attachments" :key="a.id" class="item-bloc">
<generic-doc-item-box
v-if="a.genericDoc !== null"
:generic-doc="a.genericDoc"
></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li v-if="a.genericDoc?.storedObject !== null">
<document-action-buttons-group
:stored-object="a.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<button
type="button"
class="btn btn-delete"
@click="emit('removeAttachment', { attachment: a })"
></button>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
interface GenericDocItemBoxProps {
genericDoc: GenericDocForAccompanyingPeriod;
}
const props = defineProps<GenericDocItemBoxProps>();
</script>
<template>
<div
v-if="'html' in props.genericDoc.metadata"
v-html="props.genericDoc.metadata.html"
></div>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,281 @@
<script setup lang="ts">
import {
GenericDoc,
GenericDocForAccompanyingPeriod,
} from "ChillDocStoreAssets/types/generic_doc";
import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue";
import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api";
import { computed, onMounted, ref } from "vue";
interface PickGenericDocProps {
accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[];
}
const props = defineProps<PickGenericDocProps>();
const emit = defineEmits<{
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
}>();
const genericDocs = ref<GenericDocForAccompanyingPeriod[]>([]);
const loaded = ref(false);
const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean =>
props.pickedList.findIndex(
(element: GenericDocForAccompanyingPeriod) =>
element.uniqueKey === genericDoc.uniqueKey,
) !== -1;
onMounted(async () => {
genericDocs.value = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId,
);
loaded.value = true;
});
const textFilter = ref<string>("");
const dateFromFilter = ref<string | null>(null);
const dateToFilter = ref<string | null>(null);
const placesFilter = ref<string[]>([]);
const availablePlaces = computed<string[]>(() => {
const places = new Set<string>(
genericDocs.value.map((genericDoc: GenericDoc) => genericDoc.key),
);
return Array.from(places).sort((a, b) => (a < b ? -1 : a === b ? 0 : 1));
});
const placeTrans = (str: string): string => {
switch (str) {
case "accompanying_course_document":
return "Documents du parcours";
case "person_document":
return "Documents de l'usager";
case "accompanying_period_calendar_document":
return "Document des rendez-vous des parcours";
case "accompanying_period_activity_document":
return "Document des échanges des parcours";
case "accompanying_period_work_evaluation_document":
return "Document des actions d'accompagnement";
default:
return str;
}
};
const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
if (false === loaded.value) {
return [];
}
return genericDocs.value
.filter(
(genericDoc: GenericDocForAccompanyingPeriod) =>
!props.toRemove
.map((g: GenericDocForAccompanyingPeriod) => g.uniqueKey)
.includes(genericDoc.uniqueKey),
)
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (textFilter.value === "") {
return true;
}
const needles = textFilter.value
.trim()
.split(" ")
.map((str: string) => str.trim().toLowerCase())
.filter((str: string) => str.length > 0);
const title: string =
"title" in genericDoc.metadata
? (genericDoc.metadata.title as string)
: "";
if (title === "") {
return false;
}
return needles.every((n: string) =>
title.toLowerCase().includes(n),
);
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (placesFilter.value.length === 0) {
return true;
}
return placesFilter.value.includes(genericDoc.key);
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateToFilter.value === null) {
return true;
}
return genericDoc.doc_date.datetime8601 < dateToFilter.value;
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateFromFilter.value === null) {
return true;
}
return genericDoc.doc_date.datetime8601 > dateFromFilter.value;
});
});
</script>
<template>
<div v-if="loaded">
<div>
<form name="f" method="get">
<div class="accordion my-3" id="filterOrderAccordion">
<h2 class="accordion-header" id="filterOrderHeading">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#filterOrderCollapse"
aria-expanded="true"
aria-controls="filterOrderCollapse"
>
<strong
><i class="fa fa-fw fa-filter"></i>Filtrer la
liste</strong
>
</button>
</h2>
<div
class="accordion-collapse collapse"
id="filterOrderCollapse"
aria-labelledby="filterOrderHeading"
data-bs-parent="#filterOrderAccordion"
style=""
>
<div
class="accordion-body chill_filter_order container-xxl p-5 py-2"
>
<div class="row my-2">
<div class="col-sm-12">
<div class="input-group">
<input
v-model="textFilter"
type="search"
id="f_q"
name="f[q]"
placeholder="Chercher dans la liste"
class="form-control"
/>
<button
type="submit"
class="btn btn-misc"
>
<i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
<div class="row my-2">
<legend
class="col-form-label col-sm-4 required"
>
Date du document
</legend>
<div class="col-sm-8 pt-1">
<div class="input-group">
<span class="input-group-text">Du</span>
<input
v-model="dateFromFilter"
type="date"
id="f_dateRanges_dateRange_from"
name="f[dateRanges][dateRange][from]"
class="form-control"
/>
<span class="input-group-text">Au</span>
<input
v-model="dateToFilter"
type="date"
id="f_dateRanges_dateRange_to"
name="f[dateRanges][dateRange][to]"
class="form-control"
/>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-sm-4 col-form-label">
Filtrer par
</div>
<div class="col-sm-8 pt-2">
<div
class="form-check"
v-for="p in availablePlaces"
:key="p"
>
<input
type="checkbox"
v-model="placesFilter"
name="f[checkboxes][places][]"
class="form-check-input"
:value="p"
/>
<label class="form-check-label">{{
placeTrans(p)
}}</label>
</div>
</div>
</div>
<div class="row my-2">
<button
type="submit"
class="btn btn-sm btn-misc"
>
<i class="fa fa-fw fa-filter"></i>Filtrer
</button>
</div>
</div>
</div>
<div></div>
</div>
</form>
</div>
<div v-if="genericDocs.length > 0" class="flex-table chill-task-list">
<pick-generic-doc-item
v-for="g in filteredDocuments"
:key="g.uniqueKey"
:accompanying-period-id="accompanyingPeriodId"
:genericDoc="g"
:is-picked="isPicked(g)"
@pickGenericDoc="(payload) => emit('pickGenericDoc', payload)"
@removeGenericDoc="
(payload) => emit('removeGenericDoc', payload)
"
></pick-generic-doc-item>
</div>
<div v-else class="text-center chill-no-data-statement">
Aucun document dans ce parcours
</div>
</div>
<div v-else>
<div class="d-flex align-items-center">
<strong>Chargement</strong>
<div
class="spinner-border ms-auto"
role="status"
aria-hidden="true"
></div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface PickGenericDocItemProps {
genericDoc: GenericDocForAccompanyingPeriod;
accompanyingPeriodId: number;
isPicked: boolean;
}
const props = defineProps<PickGenericDocItemProps>();
const emit = defineEmits<{
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
}>();
const clickOnAddButton = () => {
emit("pickGenericDoc", { genericDoc: props.genericDoc });
};
</script>
<template>
<div class="item-bloc" :class="{ isPicked: isPicked }">
<generic-doc-item-box
:generic-doc="props.genericDoc"
></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li v-if="props.genericDoc.storedObject !== null">
<document-action-buttons-group
:stored-object="props.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<button
v-if="!isPicked"
type="button"
class="btn btn-chill-green text-white"
@click="clickOnAddButton"
>
<i class="bi bi-cart-plus"></i>
</button>
<button
v-else
type="button"
class="btn btn-chill-red text-white"
@click="
emit('removeGenericDoc', {
genericDoc: props.genericDoc,
})
"
>
<i class="bi bi-cart-dash"></i>
</button>
</li>
</ul>
</div>
</div>
</template>
<style scoped lang="scss">
.item-bloc {
&.isPicked {
background: linear-gradient(
180deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
270deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
0deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
90deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
);
}
}
</style>

View File

@@ -0,0 +1,113 @@
<script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { computed, ref, useTemplateRef } from "vue";
import PickGenericDoc from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDoc.vue";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
interface PickGenericDocModalProps {
accompanyingPeriodId: number;
toRemove: GenericDocForAccompanyingPeriod[];
}
type PickGenericDocType = InstanceType<typeof PickGenericDoc>;
const props = defineProps<PickGenericDocModalProps>();
const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
}>();
const picker = useTemplateRef<PickGenericDocType>("picker");
const modalOpened = ref<boolean>(false);
const pickeds = ref<GenericDocForAccompanyingPeriod[]>([]);
const modalClasses = { "modal-xl": true, "modal-dialog-scrollable": true };
const numberOfPicked = computed<number>(() => pickeds.value.length);
const onPicked = ({
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
}) => {
pickeds.value.push(genericDoc);
};
const onRemove = ({
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
}) => {
const index = pickeds.value.findIndex(
(item) => item.uniqueKey === genericDoc.uniqueKey,
);
if (index === -1) {
throw new Error("Remove generic doc that doesn't exist");
}
pickeds.value.splice(index, 1);
};
const onConfirm = () => {
for (let genericDoc of pickeds.value) {
emit("pickGenericDoc", { genericDoc });
}
pickeds.value = [];
closeModal();
};
const closeModal = function () {
modalOpened.value = false;
};
const openModal = function () {
modalOpened.value = true;
};
defineExpose({ openModal, closeModal });
</script>
<template>
<modal
v-if="modalOpened"
@close="closeModal"
:modal-dialog-class="modalClasses"
>
<template v-slot:header>
<h2 class="modal-title">Ajouter une pièce jointe</h2>
</template>
<template v-slot:body>
<pick-generic-doc
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="props.toRemove"
:picked-list="pickeds"
ref="picker"
@pickGenericDoc="onPicked"
@removeGenericDoc="onRemove"
></pick-generic-doc>
</template>
<template v-slot:footer>
<ul v-if="numberOfPicked > 0" class="record_actions">
<li>
<button
type="button"
class="btn btn-chill-green text-white"
@click="onConfirm"
>
<template v-if="numberOfPicked > 1">
<i class="fa fa-plus"></i> Ajouter
{{ numberOfPicked }} pièces jointes
</template>
<template v-else>
<i class="fa fa-plus"></i> Ajouter une pièce jointe
</template>
</button>
</li>
</ul>
</template>
</modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,109 @@
import { createApp } from "vue";
import App from "./App.vue";
import { _createI18n } from "../_js/i18n";
import { WorkflowAttachment } from "ChillMainAssets/types";
import {
create_attachment,
delete_attachment,
find_attachments_by_workflow,
} from "ChillMainAssets/lib/workflow/attachments";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import ToastPlugin from "vue-toast-notification";
import "vue-toast-notification/dist/theme-bootstrap.css";
window.addEventListener("DOMContentLoaded", () => {
const attachments = document.querySelectorAll<HTMLDivElement>(
'div[data-app="workflow_attachments"]',
);
attachments.forEach(async (el) => {
const workflowId = parseInt(el.dataset.entityWorkflowId || "");
const accompanyingPeriodId = parseInt(
el.dataset.relatedAccompanyingPeriodId || "",
);
const attachments = await find_attachments_by_workflow(workflowId);
const app = createApp({
template:
'<app :workflowId="workflowId" :accompanyingPeriodId="accompanyingPeriodId" :attachments="attachments" @pickGenericDoc="onPickGenericDoc" @removeAttachment="onRemoveAttachment"></app>',
components: { App },
data: function () {
return { workflowId, accompanyingPeriodId, attachments };
},
methods: {
onRemoveAttachment: async function ({
attachment,
}: {
attachment: WorkflowAttachment;
}): Promise<void> {
const index = this.$data.attachments.findIndex(
(el: WorkflowAttachment) => el.id === attachment.id,
);
if (-1 === index) {
console.warn(
"this attachment is not associated with the workflow",
attachment,
);
this.$toast.error(
"This attachment is not associated with the workflow",
);
return;
}
try {
await delete_attachment(attachment);
} catch (error) {
console.error(error);
this.$toast.error("Error while removing element");
throw error;
}
this.$data.attachments.splice(index, 1);
this.$toast.success("Pièce jointe supprimée");
},
onPickGenericDoc: async function ({
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
}): Promise<void> {
console.log("picked generic doc", genericDoc);
// prevent to create double attachment:
if (
-1 !==
this.$data.attachments.findIndex(
(el: WorkflowAttachment) =>
el.genericDoc?.key === genericDoc.key &&
JSON.stringify(el.genericDoc?.identifiers) ==
JSON.stringify(genericDoc.identifiers),
)
) {
console.warn(
"this document is already attached to the workflow",
genericDoc,
);
this.$toast.error(
"Ce document est déjà attaché au workflow",
);
return;
}
try {
const attachment = await create_attachment(
workflowId,
genericDoc,
);
this.$data.attachments.push(attachment);
} catch (error) {
console.error(error);
throw error;
}
},
},
});
const i18n = _createI18n({});
app.use(i18n);
app.use(ToastPlugin);
app.mount(el);
});
});

View File

@@ -1,123 +1,5 @@
{# TODO
Check if this template is used
Adapt condition or Delete it
#}
{% if random(1) == 0 %}
{# For a document #}
<h2>{{ 'Document'|trans ~ 'target'|trans }}</h2>
<div class="row justify-content-center mt-5">
<div class="col-2">
<i class="fa fa-4x fa-file-text-o text-success"></i>
</div>
<div class="col-8">
<h3>Imprimé unique, parcours n°14635</h3>
<small>Document PDF (6.2 Mo)</small>
<p class="mt-2">
Description du document. Sed euismod nisi porta lorem mollis aliquam. Non curabitur gravida arcu ac tortor.
</p>
</div>
</div>
{% else %}
{# For an action #}
<h2>{{ 'Accompanying Course Action'|trans ~ 'target'|trans }}</h2>
<div class="flex-table accompanying-course-work">
{# dynamic insertion
::: TODO delete all static insertion, remove condition and pass work object in inclusion
#}{% if dynamic is defined %}
{% set work = '<pass work object here>' %}
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with { 'w': work } %}
{% else %}
{# BEGIN static insertion #}
<div class="item-bloc">
<div class="item-row">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">Exercer un AEB &gt; Conclure l'AEB
<ul class="small_in_title columns mt-1">
<li><span class="item-key">Date de début : </span><b>25/11/2021</b></li>
<li><span class="item-key">Date de fin : </span><b>10/03/2022</b></li>
</ul>
</span>
</h2>
</div>
<div class="item-row separator">
<div class="wrap-list">
<div class="wl-row">
<div class="wl-col title"><h3>Référent</h3></div>
<div class="wl-col list"><p class="wl-item">Fred</p></div>
</div>
<div class="wl-row">
<div class="wl-col title"><h3>Usagers du parcours</h3></div>
<div class="wl-col list"><span class="wl-item">
<span class="onthefly-container" data-target-name="person" data-target-id="1937" data-action="show" data-button-text="Vernon SUBUTEX" data-display-badge="true" data-v-app=""><a data-v-0c1a1125=""><span class="chill-entity entity-person badge-person" data-v-0c1a1125="">Vernon SUBUTEX</span></a><!--teleport start--><!--teleport end--></span></span>
<span class="wl-item"><span class="onthefly-container" data-target-name="person" data-target-id="1941" data-action="show" data-button-text="Juan RAMON" data-display-badge="true" data-v-app=""><a data-v-0c1a1125=""><span class="chill-entity entity-person badge-person" data-v-0c1a1125="">Juan RAMON</span></a><!--teleport start--><!--teleport end--></span></span>
</div>
</div>
<div class="wl-row">
<div class="wl-col title"><h3>Problématique sociale</h3></div>
<div class="wl-col list">
<p class="wl-item social-issues">
<span class="chill-entity entity-social-issue"><span class="badge bg-chill-l-gray text-dark"><span class="parent-0">AD - PREVENTION, ACCES AUX DROITS, BUDGET &gt;</span><span class="child">SOUTIEN EQUILIBRE BUDGET</span></span></span>
</p>
</div>
</div>
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval smallfont my-3">
<thead>
<tr><th class="obj"><h4 class="title_label">Objectif - motif - dispositif</h4></th>
<th class="res"><h4 class="title_label">Résultats - orientations</h4></th>
</tr></thead>
<tbody>
<tr>
<td class="obj">
<p class="chill-no-data-statement">Aucun objectif - motif - dispositif</p>
</td>
<td class="res">
<ul class="result_list">
<li>Résultat : Arrêt à l'initiative du ménage pour déménagement</li>
<li>Orientation vers une MASP</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<div class="item-row separator">
<div class="updatedBy">
Dernière mise à jour par
<b><span class="chill-entity entity-user">Fred<span class="user-job">(Responsable tous les territoires)</span><span class="main-scope">(ASE)</span></span></b>,<br>
le 3 décembre 2021 à 15:19
</div>
</div>
</div>
{# END static insertion #}
{% endif %}
</div>
{% if related_accompanying_period is not null %}
<h2>{{ 'workflow.attachments.title'|trans }}</h2>
<div data-app="workflow_attachments" data-workflow-id="{{ entity_workflow.id }}" data-related-accompanying-period-id="{{ related_accompanying_period.id }}" data-entity-workflow-id="{{ entity_workflow.id }}" ></div>
{% endif %}
<ul class="record_actions">
<li>
<button type="button" class="btn btn-misc">
<i class="fa fa-download fa-fw"></i>{{ 'Download'|trans }}
</button>
</li>
<li>
{% set x = random(1) %}
<button class="btn btn-update change-icon {% if x == 1 %}disabled{% endif %}">
<i class="fa fa-fw fa-{% if x == 0 %}un{% endif %}lock"></i>
{{ 'Edit'|trans }}
</button>
</li>
</ul>

View File

@@ -11,6 +11,7 @@
{{ encore_entry_script_tags('page_workflow_show') }}
{{ encore_entry_script_tags('mod_wopi_link') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
{{ encore_entry_script_tags('mod_workflow_attachment') }}
{% endblock %}
{% block css %}
@@ -20,6 +21,7 @@
{{ encore_entry_link_tags('page_workflow_show') }}
{{ encore_entry_link_tags('mod_wopi_link') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
{{ encore_entry_link_tags('mod_workflow_attachment') }}
{% endblock %}
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %}
@@ -58,6 +60,8 @@
{% endif %}
</section>
<section class="step my-4">{% include '@ChillMain/Workflow/_attachment.html.twig' %}</section>
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
{% if signatures|length > 0 %}
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>