mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-29 01:55:01 +00:00
Merge branch 'master' into ticket-app-master
# Conflicts: # src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php # src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php # src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php # src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue # src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php # src/Bundle/ChillPersonBundle/Resources/public/types.ts # src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
trans,
|
||||
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING,
|
||||
EXPORT_GENERATION_TOO_MANY_RETRIES,
|
||||
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
|
||||
EXPORT_GENERATION_EXPORT_READY,
|
||||
} from "translator";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
|
||||
import { fetchExportGenerationStatus } from "ChillMainAssets/lib/api/export";
|
||||
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||
import { ExportGeneration } from "ChillMainAssets/types";
|
||||
|
||||
interface AppProps {
|
||||
exportGenerationId: string;
|
||||
title: string;
|
||||
createdDate: string;
|
||||
}
|
||||
|
||||
const props = defineProps<AppProps>();
|
||||
|
||||
const exportGeneration = ref<ExportGeneration | null>(null);
|
||||
|
||||
const status = computed<StoredObjectStatus>(
|
||||
() => exportGeneration.value?.status ?? "pending",
|
||||
);
|
||||
const storedObject = computed<null | StoredObject>(() => {
|
||||
if (exportGeneration.value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return exportGeneration.value?.storedObject;
|
||||
});
|
||||
|
||||
const isPending = computed<boolean>(() => status.value === "pending");
|
||||
const isFetching = computed<boolean>(
|
||||
() => tryiesForReady.value < maxTryiesForReady,
|
||||
);
|
||||
const isReady = computed<boolean>(() => status.value === "ready");
|
||||
const isFailure = computed<boolean>(() => status.value === "failure");
|
||||
const filename = computed<string>(() => `${props.title}-${props.createdDate}`);
|
||||
|
||||
/**
|
||||
* counter for the number of times that we check for a new status
|
||||
*/
|
||||
let tryiesForReady = ref<number>(0);
|
||||
|
||||
/**
|
||||
* how many times we may check for a new status, once loaded
|
||||
*/
|
||||
const maxTryiesForReady = 120;
|
||||
|
||||
const checkForReady = function (): void {
|
||||
if (
|
||||
"ready" === status.value ||
|
||||
"empty" === status.value ||
|
||||
"failure" === status.value ||
|
||||
// stop reloading if the page stays opened for a long time
|
||||
tryiesForReady.value > maxTryiesForReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
tryiesForReady.value = tryiesForReady.value + 1;
|
||||
setTimeout(onObjectNewStatusCallback, 5000);
|
||||
};
|
||||
|
||||
const onObjectNewStatusCallback = async function (): Promise<void> {
|
||||
exportGeneration.value = await fetchExportGenerationStatus(
|
||||
props.exportGenerationId,
|
||||
);
|
||||
|
||||
if (isPending.value) {
|
||||
checkForReady();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onObjectNewStatusCallback();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="waiting-screen">
|
||||
<div
|
||||
v-if="isPending && isFetching"
|
||||
class="alert alert-danger text-center"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isPending && !isFetching" class="alert alert-info">
|
||||
<div>
|
||||
<p>
|
||||
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isFailure" class="alert alert-danger text-center">
|
||||
<div>
|
||||
<p>
|
||||
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isReady" class="alert alert-success text-center">
|
||||
<div>
|
||||
<p>
|
||||
{{ trans(EXPORT_GENERATION_EXPORT_READY) }}
|
||||
</p>
|
||||
|
||||
<p v-if="storedObject !== null">
|
||||
<document-action-buttons-group
|
||||
:stored-object="storedObject"
|
||||
:filename="filename"
|
||||
></document-action-buttons-group>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#waiting-screen {
|
||||
> .alert {
|
||||
min-height: 350px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,15 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
const el = document.getElementById("app");
|
||||
|
||||
if (null === el) {
|
||||
console.error("div element app was not found");
|
||||
throw new Error("div element app was not found");
|
||||
}
|
||||
|
||||
const exportGenerationId = el?.dataset.exportGenerationId as string;
|
||||
const title = el?.dataset.exportTitle as string;
|
||||
const createdDate = el?.dataset.exportGenerationDate as string;
|
||||
|
||||
createApp(App, { exportGenerationId, title, createdDate }).mount(el);
|
@@ -2,7 +2,13 @@
|
||||
<div class="grey-card">
|
||||
<ul :class="listClasses" v-if="picked.length && displayPicked">
|
||||
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type + p.id">
|
||||
<span
|
||||
v-if="'me' === p"
|
||||
class="chill_denomination current-user updatedBy"
|
||||
>{{ trans(USER_CURRENT_USER) }}</span
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
:class="getBadgeClass(p)"
|
||||
class="chill_denomination"
|
||||
:style="getBadgeStyle(p)"
|
||||
@@ -12,7 +18,18 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li class="add-persons">
|
||||
<li v-if="isCurrentUserPicker" class="btn btn-sm btn-misc">
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
:checked="picked.indexOf('me') >= 0 ? true : null"
|
||||
ref="itsMeCheckbox"
|
||||
:type="multiple ? 'checkbox' : 'radio'"
|
||||
@change="selectItsMe"
|
||||
/>
|
||||
{{ trans(USER_CURRENT_USER) }}
|
||||
</label>
|
||||
</li>
|
||||
<li class="add-persons">
|
||||
<add-persons
|
||||
:options="addPersonsOptions"
|
||||
:key="uniqid"
|
||||
@@ -44,6 +61,7 @@ import {
|
||||
PICK_ENTITY_USER_GROUP,
|
||||
PICK_ENTITY_PERSON,
|
||||
PICK_ENTITY_THIRDPARTY,
|
||||
USER_CURRENT_USER,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { addNewEntities } from "ChillMainAssets/types";
|
||||
@@ -62,6 +80,7 @@ const props = defineProps<{
|
||||
displayPicked?: boolean;
|
||||
suggested?: Entities[];
|
||||
label?: string;
|
||||
isCurrentUserPicker: boolean // must default to false
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
@@ -70,6 +89,7 @@ const emits = defineEmits<{
|
||||
(e: "addNewEntityProcessEnded"): void;
|
||||
}>();
|
||||
|
||||
const itsMeCheckbox = ref(null);
|
||||
const addPersons = ref();
|
||||
|
||||
const addPersonsOptions = computed(
|
||||
@@ -118,10 +138,13 @@ const translatedListOfTypes = computed(() => {
|
||||
|
||||
const listClasses = computed(() => ({
|
||||
"badge-suggest": true,
|
||||
"remove-items": props.removableIfSet !== false,
|
||||
"remove-items": props.removableIfSet,
|
||||
inline: true,
|
||||
}));
|
||||
|
||||
const selectItsMe = (event) =>
|
||||
event.target.checked ? addNewSuggested("me") : removeEntity("me");
|
||||
|
||||
function addNewSuggested(entity: Entities) {
|
||||
emits("addNewEntity", { entity });
|
||||
}
|
||||
@@ -135,30 +158,32 @@ function addNewEntity({ selected }: addNewEntities) {
|
||||
emits("addNewEntityProcessEnded");
|
||||
}
|
||||
|
||||
function removeEntity(entity: Entities) {
|
||||
if (props.removableIfSet === false) {
|
||||
return;
|
||||
}
|
||||
emits("removeEntity", { entity });
|
||||
}
|
||||
|
||||
const removeEntity = (entity) => {
|
||||
if (!props.removableIfSet) return;
|
||||
if (entity === "me" && itsMeCheckbox.value) {
|
||||
itsMeCheckbox.value.checked = false;
|
||||
}
|
||||
emits("removeEntity", { entity });
|
||||
};
|
||||
|
||||
function getBadgeClass(entities: Entities) {
|
||||
if (entities.type !== "user_group") {
|
||||
return entities.type;
|
||||
}
|
||||
return "";
|
||||
if (entities.type !== "user_group") {
|
||||
return entities.type;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getBadgeStyle(entities: Entities) {
|
||||
if (entities.type === "user_group") {
|
||||
return [
|
||||
`ul.badge-suggest li > span {
|
||||
if (entities.type === "user_group") {
|
||||
return [
|
||||
`ul.badge-suggest li > span {
|
||||
color: ${entities.foregroundColor}!important;
|
||||
border-bottom-color: ${entities.backgroundColor};
|
||||
}`,
|
||||
];
|
||||
}
|
||||
return [];
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -265,4 +290,8 @@ ul.badge-suggest li > span.person {
|
||||
ul.badge-suggest li > span.thirdparty {
|
||||
border-bottom-color: rgb(198.9, 72, 98.1);
|
||||
}
|
||||
.current-user {
|
||||
color: var(--bs-body-color);
|
||||
background-color: var(--bs-chill-l-gray) !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import GenerateButton from "ChillMainAssets/vuejs/SavedExportButtons/Component/GenerateButton.vue";
|
||||
|
||||
interface SavedExportButtonsConfig {
|
||||
savedExportUuid: string;
|
||||
savedExportAlias: string;
|
||||
}
|
||||
|
||||
const props = defineProps<SavedExportButtonsConfig>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<generate-button
|
||||
:saved-export-uuid="props.savedExportUuid"
|
||||
></generate-button>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@@ -0,0 +1,186 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
trans,
|
||||
SAVED_EXPORT_EXECUTE,
|
||||
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING_SHORT,
|
||||
EXPORT_GENERATION_TOO_MANY_RETRIES,
|
||||
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
|
||||
EXPORT_GENERATION_EXPORT_READY,
|
||||
} from "translator";
|
||||
import {
|
||||
fetchExportGenerationStatus,
|
||||
generateFromSavedExport,
|
||||
} from "ChillMainAssets/lib/api/export";
|
||||
import { computed, ref } from "vue";
|
||||
import { ExportGeneration } from "ChillMainAssets/types";
|
||||
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
|
||||
import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DownloadButton.vue";
|
||||
import { useToast } from "vue-toast-notification";
|
||||
import "vue-toast-notification/dist/theme-sugar.css";
|
||||
|
||||
interface SavedExportButtonGenerateConfig {
|
||||
savedExportUuid: string;
|
||||
}
|
||||
|
||||
const props = defineProps<SavedExportButtonGenerateConfig>();
|
||||
const emits = defineEmits<{
|
||||
(e: "generate");
|
||||
}>();
|
||||
|
||||
const toast = useToast();
|
||||
const exportGeneration = ref<ExportGeneration | null>(null);
|
||||
|
||||
const status = computed<StoredObjectStatus | "inactive">(
|
||||
() => exportGeneration.value?.status ?? "inactive",
|
||||
);
|
||||
const storedObject = computed<null | StoredObject>(() => {
|
||||
if (exportGeneration.value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return exportGeneration.value?.storedObject;
|
||||
});
|
||||
|
||||
const isInactive = computed<boolean>(() => status.value === "inactive");
|
||||
const isPending = computed<boolean>(() => status.value === "pending");
|
||||
const isFetching = computed<boolean>(
|
||||
() => tryiesForReady.value < maxTryiesForReady,
|
||||
);
|
||||
const isReady = computed<boolean>(() => status.value === "ready");
|
||||
const isFailure = computed<boolean>(() => status.value === "failure");
|
||||
const filename = computed<string>(() => {
|
||||
if (null === exportGeneration.value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `${exportGeneration.value?.storedObject.title}-${exportGeneration.value?.createdAt?.datetime8601}`;
|
||||
});
|
||||
const externalDownloadLink = computed<string>(
|
||||
() => `/fr/main/export-generation/${exportGeneration.value?.id}/wait`,
|
||||
);
|
||||
const classes = computed<Record<string, boolean>>(() => {
|
||||
return {};
|
||||
});
|
||||
const buttonClasses = computed<Record<string, boolean>>(() => {
|
||||
return { btn: true, "btn-outline-primary": true };
|
||||
});
|
||||
|
||||
/**
|
||||
* counter for the number of times that we check for a new status
|
||||
*/
|
||||
let tryiesForReady = ref<number>(0);
|
||||
|
||||
/**
|
||||
* how many times we may check for a new status, once loaded
|
||||
*/
|
||||
const maxTryiesForReady = 120;
|
||||
|
||||
const checkForReady = function (): void {
|
||||
if (
|
||||
"ready" === status.value ||
|
||||
"empty" === status.value ||
|
||||
"failure" === status.value ||
|
||||
// stop reloading if the page stays opened for a long time
|
||||
tryiesForReady.value > maxTryiesForReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
tryiesForReady.value = tryiesForReady.value + 1;
|
||||
setTimeout(
|
||||
onObjectNewStatusCallback,
|
||||
tryiesForReady.value < 10 ? 1500 : 5000,
|
||||
);
|
||||
};
|
||||
|
||||
const onExportGenerationSuccess = function (): void {
|
||||
toast.success(trans(EXPORT_GENERATION_EXPORT_READY));
|
||||
};
|
||||
|
||||
const onObjectNewStatusCallback = async function (): Promise<void> {
|
||||
if (null === exportGeneration.value) {
|
||||
checkForReady();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const newExportGeneration = await fetchExportGenerationStatus(
|
||||
exportGeneration.value?.id,
|
||||
);
|
||||
|
||||
if (newExportGeneration.status !== exportGeneration.value.status) {
|
||||
if (newExportGeneration.status === "ready") {
|
||||
onExportGenerationSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
exportGeneration.value = newExportGeneration;
|
||||
|
||||
if (isPending.value) {
|
||||
checkForReady();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const onClickGenerate = async (): Promise<void> => {
|
||||
emits("generate");
|
||||
exportGeneration.value = await generateFromSavedExport(
|
||||
props.savedExportUuid,
|
||||
);
|
||||
|
||||
onObjectNewStatusCallback();
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
v-if="isInactive"
|
||||
:class="buttonClasses"
|
||||
type="button"
|
||||
@click="onClickGenerate"
|
||||
>
|
||||
<i class="fa fa-cog"></i> {{ trans(SAVED_EXPORT_EXECUTE) }}
|
||||
</button>
|
||||
<template v-if="isPending && isFetching">
|
||||
<span class="btn">
|
||||
<i class="fa fa-cog fa-spin fa-fw"></i>
|
||||
<span class="pending-message">{{
|
||||
trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING_SHORT)
|
||||
}}</span>
|
||||
<a :href="externalDownloadLink" class="externalDownloadLink">
|
||||
<i class="bi bi-box-arrow-up-right"></i>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
<div v-if="isPending && !isFetching" :class="buttonClasses">
|
||||
<span>{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}</span>
|
||||
</div>
|
||||
<download-button
|
||||
v-else-if="isReady && storedObject?.currentVersion !== null"
|
||||
:classes="buttonClasses"
|
||||
:stored-object="storedObject"
|
||||
:at-version="storedObject?.currentVersion"
|
||||
:filename="filename"
|
||||
></download-button>
|
||||
<div v-else-if="isFailure" :class="classes">
|
||||
<span class="btn">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<span class="pending-message">{{
|
||||
trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT)
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pending-message {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.externalDownloadLink {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,13 @@
|
||||
import { createApp } from "vue";
|
||||
|
||||
import App from "./App.vue";
|
||||
|
||||
const buttons = document.querySelectorAll<HTMLDivElement>(
|
||||
"[data-generate-export-button]",
|
||||
);
|
||||
|
||||
buttons.forEach((button) => {
|
||||
const savedExportUuid = button.dataset.savedExportUuid as string;
|
||||
|
||||
createApp(App, { savedExportUuid, savedExportAlias: "" }).mount(button);
|
||||
});
|
Reference in New Issue
Block a user