Skip to content

Commit

Permalink
feat: add quick add
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentin-Guillemin committed Dec 15, 2023
1 parent a476dfb commit e362686
Show file tree
Hide file tree
Showing 17 changed files with 264 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/main/java/fr/recia/glc/db/dto/AlertDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ public class AlertDto {
private String title;
private String text;
private AlertType type;
private boolean action;

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,7 @@ public interface DisciplineRepository<T extends Discipline> extends AbstractRepo
"ORDER BY d.source")
List<String> findAllNonSarapisSources();

@Query(value = "select d.id from discipline d where d.code = :code and (d.source = :source or d.source = CONCAT('SarapisUi_', :source))", nativeQuery = true)
Long findByCode(String code, String source);

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public interface FonctionRepository<T extends Fonction> extends AbstractReposito
"inner join typefonctionfiliere tff on f.filiere_fk = tff.id " +
"inner join discipline d on f.discipline_poste_fk = d.id " +
"where f.astructure_fk = :structureId " +
"and (tff.codeFiliere != '-' and d.code != '-' and f.discipline_poste_fk is not null) " +
"and (tff.codeFiliere != '-' and d.code != '-' and f.discipline_poste_fk is not null) " +
"group by af.personne_fk " +
"having count(f.filiere_fk) > 0" +
") " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,7 @@ public interface TypeFonctionFiliereRepository<T extends TypeFonctionFiliere> ex
"ORDER BY tff.libelleFiliere")
List<TypeFonctionFiliereDto> findWithoutSource();

@Query(value = "select tff.id from typefonctionfiliere tff where tff.codeFiliere = :code and (tff.source = :source OR tff.source = CONCAT('SarapisUi_', :source))", nativeQuery = true)
Long findByCode(String code, String source);

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public class JsonAdditionalFonctionBody {

private Long structureId;
private List<String> additional;
private String additionalCode;

}
17 changes: 17 additions & 0 deletions src/main/java/fr/recia/glc/services/db/FonctionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,23 @@ public List<SimplePersonneDto> getPersonnesWithoutFunctions(Long structureId) {
return aPersonneRepository.findByPersonneIds(new HashSet<>(personnesIds));
}

public boolean saveAdditionalFonction(Long personneId, Long structureId, String additional) {
SimplePersonneDto personne = personneService.getPersonneSimple(personneId);
if (personne == null) return false;
if (!List.of(Etat.Invalide, Etat.Valide, Etat.Bloque).contains(personne.getEtat())) return false;
String source = personne.getSource().startsWith(Constants.SARAPISUI_)
? personne.getSource().substring(Constants.SARAPISUI_.length())
: personne.getSource();

final String[] split = additional.split("-");
final Long filiere = typeFonctionFiliereRepository.findByCode(split[0], source);
final Long discipline = disciplineRepository.findByCode(split[1], source);
final String fonction = filiere + "-" + discipline;
log.debug("==>\n - {}\n - {}\n - {}\n<==", split, source, fonction);

return saveAdditionalFonctions(personneId, structureId, List.of(fonction));
}

public boolean saveAdditionalFonctions(Long personneId, Long structureId, List<String> additional) {
SimplePersonneDto personne = personneService.getPersonneSimple(personneId);
if (personne == null) return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ public ResponseEntity<EtablissementDto> getEtablissement(@PathVariable Long id)
etablissement.setWithoutFunctions(withoutFunction);

List<AlertDto> alerts = new ArrayList<>();
if (!fonctionService.isDiscipline(id, "GEST")) alerts.add(AlertDto.builder().title("ADF.GEST").type(AlertType.error).build());
if (!fonctionService.isDiscipline(id, "D0010")) alerts.add(AlertDto.builder().title("DIR.D0010").type(AlertType.error).build());
if (!fonctionService.isDiscipline(id, "GEST")) alerts.add(AlertDto.builder().title("ADF.GEST").type(AlertType.error).action(true).build());
if (!fonctionService.isDiscipline(id, "D0010")) alerts.add(AlertDto.builder().title("DIR.D0010").type(AlertType.error).action(true).build());
etablissement.setAlerts(alerts);

List<FonctionDto> fonctions = fonctionService.getStructureFonctions(id);
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/fr/recia/glc/web/rest/PersonneController.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ public ResponseEntity setPersonneAdditionalFonctions(@PathVariable Long id, @Req
throw new AccessDeniedException("Access is denied to anonymous !");
}

boolean success = fonctionService.saveAdditionalFonctions(id, body.getStructureId(), body.getAdditional());
boolean success;
if (body.getAdditionalCode() == null) {
success = fonctionService.saveAdditionalFonctions(id, body.getStructureId(), body.getAdditional());
} else {
success = fonctionService.saveAdditionalFonction(id, body.getStructureId(), body.getAdditionalCode());
}
if (!success) return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

return new ResponseEntity<>(HttpStatus.OK);
Expand Down
63 changes: 63 additions & 0 deletions src/main/webapp/src/components/AlertManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang="ts">
import { useConfigurationStore } from '@/stores/configurationStore.ts';
import { usePersonneStore } from '@/stores/personneStore.ts';
import { useStructureStore } from '@/stores/structureStore.ts';
import type { Alert } from '@/types/alertType.ts';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const configurationStore = useConfigurationStore();
const { isQuickAdd, requestAdd } = storeToRefs(configurationStore);
const personneStore = usePersonneStore();
const { administrativeList } = storeToRefs(personneStore);
const structureStore = useStructureStore();
const { currentEtab } = storeToRefs(structureStore);
const doAlert = (alert: Alert): void => {
if (alert.action) {
switch (alert.title) {
case 'ADF.GEST':
requestAdd.value = {
i18n: 'additional.add.ADF.GEST',
function: 'ADF-GEST',
type: 'code',
searchList: administrativeList.value,
};
isQuickAdd.value = true;
break;
case 'DIR.D0010':
requestAdd.value = {
i18n: 'additional.add.DIR.D0010',
function: 'DIR-D0010',
type: 'code',
searchList: administrativeList.value,
};
isQuickAdd.value = true;
break;
}
}
};
</script>

<template>
<v-alert
v-for="(alert, index) in currentEtab?.alerts"
:key="index"
:title="alert.title && t(`alert.title.${alert.title}`)"
:text="alert.text && t(`alert.text.${alert.text}`)"
:type="alert.type"
rounded="lg"
:class="[alert.action ? 'clicable' : '', 'mb-4']"
@click="doAlert(alert)"
/>
</template>

<style scoped lang="scss">
.clicable {
cursor: pointer;
}
</style>
122 changes: 122 additions & 0 deletions src/main/webapp/src/components/dialogs/QuickAddDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script setup lang="ts">
import PersonneSearch from '@/components/search/PersonneSearch.vue';
import { setPersonneAdditionalWithCode, setPersonneAdditionalWithId } from '@/services/personneService.ts';
import { useConfigurationStore } from '@/stores/configurationStore.ts';
import { usePersonneStore } from '@/stores/personneStore.ts';
import { toIdentifier } from '@/utils/accountUtils.ts';
import { errorHandler } from '@/utils/axiosUtils.ts';
import debounce from 'lodash.debounce';
import { storeToRefs } from 'pinia';
import { computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useToast } from 'vue-toastification';
const { t } = useI18n();
const toast = useToast();
const configurationStore = useConfigurationStore();
const { isEditAllowed } = configurationStore;
const { currentStructureId, isQuickAdd, requestAdd } = storeToRefs(configurationStore);
const personneStore = usePersonneStore();
const { initCurrentPersonne, refreshCurrentPersonne } = personneStore;
const { currentPersonne, structureFonctions, structureAdditionalFonctions } = storeToRefs(personneStore);
const modelValue = computed<boolean>({
get() {
return isQuickAdd.value;
},
set() {},
});
const setSelectedUser = (id: number | undefined) => {
currentPersonne.value = undefined;
if (id) initCurrentPersonne(id, false);
else currentPersonne.value = undefined;
};
const canSave = computed<boolean>(() => {
const functions: Array<string> = [
...new Set(toIdentifier(structureFonctions.value).concat(toIdentifier(structureAdditionalFonctions.value))),
];
const alreadyHasFunction = requestAdd.value?.function && functions.includes(requestAdd.value.function);
if (alreadyHasFunction) toast.error(t('toast.'));
return currentPersonne.value ? !alreadyHasFunction : false;
});
const save = async () => {
if (requestAdd.value?.function) {
try {
if (requestAdd.value.type == 'id') {
await setPersonneAdditionalWithId(
currentPersonne.value!.id,
currentStructureId.value!,
requestAdd.value.function,
);
} else {
await setPersonneAdditionalWithCode(
currentPersonne.value!.id,
currentStructureId.value!,
requestAdd.value.function,
);
}
closeAndResetModal(true);
} catch (e) {
errorHandler(e);
closeAndResetModal(false);
}
}
};
const closeAndResetModal = (success?: boolean) => {
if (success) {
refreshCurrentPersonne();
toast.success(t('toast.additional.success.save'));
} else if (!success && success != undefined) {
toast.error(t('toast.additional.error.save'));
}
if (isQuickAdd.value) isQuickAdd.value = false;
const reset = debounce(() => {
currentPersonne.value = undefined;
}, 200);
reset();
};
watch(currentPersonne, (newValue) => {
if (isQuickAdd.value && newValue) {
if (!isEditAllowed(newValue.etat)) toast.error(t('toast.editStatusDenied'));
}
});
</script>

<template>
<v-dialog v-model="modelValue" scrollable :max-width="1024">
<v-card>
<v-toolbar color="rgba(255, 255, 255, 0)">
<v-toolbar-title class="text-h6">{{ requestAdd?.i18n && t(requestAdd.i18n) }}</v-toolbar-title>
<template #append>
<v-btn icon="fas fa-xmark" color="default" variant="plain" @click="closeAndResetModal()" />
</template>
</v-toolbar>
<v-card-text class="py-0">
<personne-search
:search-list="requestAdd?.searchList"
@update:select="setSelectedUser"
:class="currentPersonne && isEditAllowed(currentPersonne.etat) ? 'mb-4' : 'mb-6'"
/>
</v-card-text>
<v-card-actions v-if="currentPersonne && isEditAllowed(currentPersonne.etat)">
<v-spacer />
<v-btn
color="success"
prepend-icon="fas fa-floppy-disk"
:text="t('button.save')"
:disabled="!canSave"
@click="save"
/>
</v-card-actions>
</v-card>
</v-dialog>
</template>
8 changes: 8 additions & 0 deletions src/main/webapp/src/locales/en/additionals.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
},
"fonction": {
"add": "Manage additional functions"
},
"add": {
"ADF": {
"GEST": "Add a manager"
},
"DIR": {
"D0010": "Add a director"
}
}
}
}
8 changes: 8 additions & 0 deletions src/main/webapp/src/locales/fr/additionals.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
},
"fonction": {
"add": "Gestion des fonctions complémentaires"
},
"add": {
"ADF": {
"GEST": "Ajouter un Gestionnaire"
},
"DIR": {
"D0010": "Ajouter un directeur"
}
}
}
}
14 changes: 13 additions & 1 deletion src/main/webapp/src/services/personneService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,16 @@ const searchPersonne = async (name: string) => await axios.get(`/api/personne?na
const setPersonneAdditional = async (id: number, structureId: number, additional: Array<string>) =>
await axios.post(`/api/personne/${id}/fonction`, { structureId, additional });

export { getPersonne, searchPersonne, setPersonneAdditional };
const setPersonneAdditionalWithId = async (id: number, structureId: number, additional: string) =>
await setPersonneAdditional(id, structureId, [additional]);

const setPersonneAdditionalWithCode = async (id: number, structureId: number, additionalCode: string) =>
await axios.post(`/api/personne/${id}/fonction`, { structureId, additionalCode });

export {
getPersonne,
searchPersonne,
setPersonneAdditional,
setPersonneAdditionalWithId,
setPersonneAdditionalWithCode,
};
11 changes: 11 additions & 0 deletions src/main/webapp/src/stores/configurationStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Configuration } from '@/types/configurationType.ts';
import type { enumValues } from '@/types/enumValuesType.ts';
import { Tabs } from '@/types/enums/Tabs.ts';
import type { Identity } from '@/types/identityType.ts';
import type { SimplePersonne } from '@/types/personneType.ts';
import type { StructureConfiguration } from '@/types/structureConfigurationType.ts';
import { getEtat } from '@/utils/accountUtils.ts';
import { errorHandler } from '@/utils/axiosUtils.ts';
Expand Down Expand Up @@ -120,6 +121,14 @@ export const useConfigurationStore = defineStore('configuration', () => {
const isAdditional = ref<boolean>(false);
const isAddMode = ref<boolean>(false);

const isQuickAdd = ref<boolean>(false);
const requestAdd = ref<{
i18n?: string;
function?: string;
type: 'id' | 'code';
searchList?: Array<SimplePersonne>;
}>();

/* -- Gestion de l'authentification -- */

const identity = ref<Identity | undefined>();
Expand All @@ -144,6 +153,8 @@ export const useConfigurationStore = defineStore('configuration', () => {
isLoading,
isAdditional,
isAddMode,
isQuickAdd,
requestAdd,
identity,
isAuthenticated,
};
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/src/types/alertType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type Alert = {
title?: string;
text?: string;
type: 'success' | 'info' | 'warning' | 'error';
action: boolean;
};
2 changes: 2 additions & 0 deletions src/main/webapp/src/views/StructureView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import AdditionalDialog from '@/components/dialogs/AdditionalDialog.vue';
import PersonneDialog from '@/components/dialogs/PersonneDialog.vue';
import QuickAddDialog from '@/components/dialogs/QuickAddDialog.vue';
import { useConfigurationStore } from '@/stores/configurationStore.ts';
import { useStructureStore } from '@/stores/structureStore.ts';
import { Tabs } from '@/types/enums/Tabs.ts';
Expand Down Expand Up @@ -76,6 +77,7 @@ watch(
</v-window>
<personne-dialog />
<additional-dialog />
<quick-add-dialog />
</template>

<style scoped lang="scss">
Expand Down
11 changes: 2 additions & 9 deletions src/main/webapp/src/views/structure/DashboardView.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import AlertManager from '@/components/AlertManager.vue';
import CustomPagination from '@/components/CustomPagination.vue';
import PersonneCard from '@/components/PersonneCard.vue';
import { usePersonneStore } from '@/stores/personneStore.ts';
Expand Down Expand Up @@ -49,15 +50,7 @@ const panel = ref<Array<DashboardPanel>>([DashboardPanel.DeletingAccounts]);

<template>
<v-container fluid>
<v-alert
v-for="(alert, index) in currentEtab?.alerts"
:key="index"
:title="alert.title && t(`alert.title.${alert.title}`)"
:text="alert.text && t(`alert.text.${alert.text}`)"
:type="alert.type"
rounded="lg"
class="mb-4"
/>
<alert-manager />

<v-expansion-panels v-model="panel">
<v-expansion-panel :value="DashboardPanel.DeletingAccounts" :elevation="0" rounded="lg">
Expand Down

0 comments on commit e362686

Please sign in to comment.