diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts index 9bfb952b5..887a376f3 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts @@ -5,11 +5,16 @@ export type fetchOption = Record; export type Params = Record; +export interface Pagination { + first: number; + items_per_page: number; + more: boolean; + next: string | null; + previous: string | null; +} + export interface PaginationResponse { - pagination: { - more: boolean; - items_per_page: number; - }; + pagination: Pagination; results: T[]; count: number; } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts index b2fa260d2..354c9f8af 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts @@ -8,6 +8,26 @@ import { TranslatableString } from "ChillMainAssets/types"; * @param locale defaults to browser locale * @returns The localized string or null if no translation is available */ + +/** + * Prepends the current HTML lang code to the given URL. + * Example: If lang="fr" and url="/about", returns "/fr/about" + * + * @param url The URL to localize + * @returns The localized URL + */ +export function localizedUrl(url: string): string { + const lang = + document.documentElement.lang || navigator.language.split("-")[0] || "fr"; + // Ensure url starts with a slash and does not already start with /{lang}/ + const normalizedUrl = url.startsWith("/") ? url : `/${url}`; + const langPrefix = `/${lang}`; + if (normalizedUrl.startsWith(langPrefix + "/")) { + return normalizedUrl; + } + return `${langPrefix}${normalizedUrl}`; +} + export function localizeString( translatableString: TranslatableString | null | undefined, locale?: string, diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue index 10d7099c8..8e96a1595 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue @@ -1,6 +1,6 @@ diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Motive/MotiveSelectorComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Motive/MotiveSelectorComponent.vue index fd276191f..ffba8041b 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Motive/MotiveSelectorComponent.vue +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Motive/MotiveSelectorComponent.vue @@ -16,7 +16,6 @@ :selected-label="trans(MULTISELECT_SELECTED_LABEL)" :options="motives" v-model="motive" - class="mb-4" /> diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonComponent.vue index a5b7b22d0..744417e25 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonComponent.vue +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonComponent.vue @@ -11,7 +11,6 @@ > -
Aucune personne concernée
diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonsSelectorComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonsSelectorComponent.vue index 804cb416e..72096b2a2 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonsSelectorComponent.vue +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/Person/PersonsSelectorComponent.vue @@ -20,10 +20,10 @@ import { ref, watch, defineProps, defineEmits, computed } from "vue"; import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue"; // Types -import { Entities, EntityType } from "ChillPersonAssets/types"; +import { Entities, EntitiesOrMe, EntityType } from "ChillPersonAssets/types"; const props = defineProps<{ - modelValue: Entities[] | Entities | null; + modelValue: EntitiesOrMe[] | EntitiesOrMe | null; suggested: Entities[]; multiple: boolean; types: EntityType[]; @@ -31,15 +31,13 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - "update:modelValue": [value: Entities[] | Entities | null]; + "update:modelValue": [value: EntitiesOrMe[] | EntitiesOrMe | null]; }>(); -// Valeurs par défaut const multiple = props.multiple; const types = props.types; const label = props.label; -// État local const selectedEntities = ref( multiple ? [...((props.modelValue as Entities[]) || [])] @@ -88,17 +86,17 @@ watch( { immediate: true, deep: true }, ); -function addNewEntity({ entity }: { entity: Entities }) { +function addNewEntity({ entity }: { entity: EntitiesOrMe }) { if (multiple) { - selectedEntities.value.push(entity); + selectedEntities.value.push(entity as Entities); emit("update:modelValue", selectedEntities.value); } else { - selectedEntities.value = [entity]; + selectedEntities.value = [entity as Entities]; emit("update:modelValue", entity); } } -function removeEntity({ entity }: { entity: Entities }) { +function removeEntity({ entity }: { entity: EntitiesOrMe }) { if (multiple) { const index = selectedEntities.value.findIndex( (selectedEntity) => selectedEntity === entity, diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/PreviousTicketsComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/PreviousTicketsComponent.vue index 7789986be..cd826d9e9 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/PreviousTicketsComponent.vue +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/PreviousTicketsComponent.vue @@ -36,51 +36,14 @@ - - - - - - - - @@ -90,23 +53,22 @@ import { useStore } from "vuex"; // Components import Modal from "../../../../../../../ChillMainBundle/Resources/public/vuejs/_components/Modal.vue"; -import TicketListComponent from "./TicketListComponent.vue"; -import TicketHistoryListComponent from "./TicketHistoryListComponent.vue"; +import TicketListComponent from "../../TicketList/components/TicketListComponent.vue"; // Translations import { trans, + CHILL_TICKET_LIST_NO_TICKETS, CHILL_TICKET_TICKET_PREVIOUS_TICKETS, CHILL_TICKET_LIST_TITLE_PREVIOUS_TICKETS, - EDIT, } from "translator"; // Types import { Person } from "ChillPersonAssets/types"; -import { TicketHistoryLine, TicketSimple } from "../../../types"; +import { TicketSimple } from "../../../types"; // Utils -import { getTicketTitle } from "../utils/utils"; +import { localizedUrl } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; const store = useStore(); const showPreviousTicketModal = ref(false); @@ -117,18 +79,14 @@ const currentPersons = computed( () => store.getters.getCurrentPersons as Person[], ); const previousTickets = computed( - () => store.getters.getPreviousTickets as TicketSimple[], + () => store.getters.getTicketList as TicketSimple[], ); -const previousTicketHistory = computed( - () => store.getters.getPreviousTicketHistory as TicketHistoryLine[], -); -const previousTicketDetails = computed(() => { - return store.getters.getPreviousTicketDetails as TicketSimple; -}); onMounted(async () => { try { - await store.dispatch("fetchTicketsByPerson"); + await store.dispatch("fetchTicketList", { + byPerson: currentPersons.value.map((person) => person.id), + }); } catch (error) { console.error("Erreur lors du chargement des tickets:", error); } @@ -147,19 +105,17 @@ async function handleViewTicket(ticketId: number) { showTicketHistoryModal.value = true; try { - await store.dispatch("fetchPreviousTicketDetails", ticketId); + await store.dispatch("fetchTicket", ticketId); } catch (error) { - console.error("Erreur lors du chargement de l'historique:", error); + console.error("Erreur lors du chargement du ticket:", error); } } function handleEditTicket(ticketId: number) { - window.location.href = `/fr/ticket/ticket/${ticketId}/edit`; -} - -function closeHistoryModal() { - showTicketHistoryModal.value = false; - selectedTicketId.value = null; + const returnPath = localizedUrl(`/ticket/ticket/list`); + window.location.href = localizedUrl( + `/ticket/ticket/${ticketId}/edit?returnPath=${returnPath}`, + ); } diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketListComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketListComponent.vue deleted file mode 100644 index 335c32544..000000000 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketListComponent.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/index.ts b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/index.ts index 6ddcdb4b3..1d6305cc7 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/index.ts +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/index.ts @@ -4,6 +4,10 @@ import { State as TicketStates, moduleTicket } from "./modules/ticket"; import { State as CommentStates, moduleComment } from "./modules/comment"; import { State as AddresseeStates, moduleAddressee } from "./modules/addressee"; import { State as PersonsState, modulePersons } from "./modules/persons"; +import { + State as TicketListState, + moduleTicketList, +} from "./modules/ticket_list"; export interface RootState { motive: MotiveStates; @@ -11,6 +15,7 @@ export interface RootState { comment: CommentStates; addressee: AddresseeStates; persons: PersonsState; + ticketList: TicketListState; } export const store = createStore({ @@ -20,5 +25,6 @@ export const store = createStore({ comment: moduleComment, addressee: moduleAddressee, persons: modulePersons, + ticketList: moduleTicketList, }, }); diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket.ts b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket.ts index fa73223f5..4949087cb 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket.ts +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket.ts @@ -1,23 +1,19 @@ import { Module } from "vuex"; import { RootState } from ".."; -import { Ticket, TicketEmergencyState, TicketSimple } from "../../../../types"; +import { Ticket, TicketEmergencyState } from "../../../../types"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { ApiException } from "../../../../../../../../ChillMainBundle/Resources/public/types"; import { getSinceCreated } from "../../utils/utils"; export interface State { ticket: Ticket; - previous_tickets: TicketSimple[]; - previous_ticket_details: Ticket; action_icons: object; } export const moduleTicket: Module = { state: () => ({ ticket: {} as Ticket, - previous_tickets: [] as TicketSimple[], - previous_ticket_details: {} as Ticket, action_icons: { add_person: "fa fa-user-plus", add_comment: "fa fa-comment", @@ -42,21 +38,6 @@ export const moduleTicket: Module = { ); return state.ticket; }, - getTicketHistory(state) { - return state.ticket.history; - }, - getPreviousTickets(state) { - return state.previous_tickets; - }, - getPreviousTicketHistory(state) { - return state.previous_ticket_details.history; - }, - getPreviousTicketDetails(state) { - return state.previous_ticket_details; - }, - getCurrentPersons(state) { - return state.ticket.currentPersons ?? []; - }, getActionIcons(state) { return state.action_icons; }, @@ -66,18 +47,18 @@ export const moduleTicket: Module = { } return getSinceCreated(state.ticket.createdAt.datetime, currentTime); }, + getTicketHistory: (state) => { + return state.ticket.history; + }, + getCurrentPersons(state) { + return state.ticket.currentPersons; + }, }, mutations: { setTicket(state, ticket: Ticket) { state.ticket = ticket; }, - setPreviousTickets(state, previousTickets: TicketSimple[]) { - state.previous_tickets = previousTickets; - }, - setPreviousTicketDetails(state, ticket: Ticket) { - state.previous_ticket_details = ticket; - }, }, actions: { async closeTicket({ commit, state }) { @@ -116,39 +97,5 @@ export const moduleTicket: Module = { throw error.name; } }, - - async fetchTicketsByPerson({ commit, state }) { - try { - if (state.ticket.currentPersons.length === 0) { - commit("setPreviousTickets", []); - return; - } - const { results }: { results: TicketSimple[] } = await makeFetch( - "GET", - `/api/1.0/ticket/ticket/list?byPerson=${state.ticket.currentPersons.map((person) => person.id).join(",")}`, - ); - const excludeCurrentTicket = results.filter( - (ticket) => ticket.id !== state.ticket.id, - ); - commit("setPreviousTickets", excludeCurrentTicket); - } catch (e: unknown) { - const error = e as ApiException; - throw error.name; - } - }, - - async fetchPreviousTicketDetails({ commit }, ticketId: number) { - try { - const ticket: Ticket = await makeFetch( - "GET", - `/api/1.0/ticket/ticket/${ticketId}`, - ); - commit("setPreviousTicketDetails", ticket); - return ticket; - } catch (e: unknown) { - const error = e as ApiException; - throw error.name; - } - }, }, }; diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket_list.ts b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket_list.ts new file mode 100644 index 000000000..8f9264928 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket_list.ts @@ -0,0 +1,142 @@ +import { Module } from "vuex"; +import { RootState } from ".."; + +import { Ticket, TicketFilterParams, TicketSimple } from "../../../../types"; +import { + makeFetch, + Pagination, + PaginationResponse, +} from "ChillMainAssets/lib/api/apiMethods"; +import { + ApiException, + User, +} from "../../../../../../../../ChillMainBundle/Resources/public/types"; + +export interface State { + ticket_list: TicketSimple[]; + ticket_details: Ticket | null; + pagination: Pagination; + count: number; + user: User; +} + +export const moduleTicketList: Module = { + state: () => ({ + ticket_list: [], + ticket_details: null, + pagination: { + first: 0, + items_per_page: 50, + more: false, + next: null, + previous: null, + }, + count: 0, + user: {} as User, + }), + getters: { + getTicketList(state): TicketSimple[] { + return state.ticket_list; + }, + getTicketDetails(state): Ticket | null { + return state.ticket_details; + }, + getPagination(state) { + return state.pagination; + }, + getUser(state): User { + return state.user; + }, + getCount(state): number { + return state.count; + }, + }, + mutations: { + setTicketList(state, ticketList: TicketSimple[]) { + state.ticket_list = ticketList; + }, + setTicketDetails(state, ticket: Ticket | null) { + state.ticket_details = ticket; + }, + setPagination(state, pagination: State["pagination"]) { + state.pagination = pagination; + }, + setUser(state, user: User) { + state.user = user; + }, + setCount(state, count: number) { + state.count = count; + }, + }, + actions: { + async fetchTicketList({ commit }, ticketFilterParams: TicketFilterParams) { + try { + const params = new URLSearchParams( + ticketFilterParams as Record, + ).toString(); + const { results, pagination, count } = (await makeFetch( + "GET", + `/api/1.0/ticket/ticket/list/?${params}`, + )) as PaginationResponse; + + commit("setTicketList", results); + commit("setCount", count); + commit("setPagination", pagination); + } catch (e: unknown) { + const error = e as ApiException; + console.error( + "Erreur lors du chargement de la liste des tickets:", + error, + ); + } + }, + async fetchConnectedUser({ commit }) { + try { + const user = await makeFetch( + "GET", + "http://localhost:8000/api/1.0/main/whoami.json", + ); + commit("setUser", user); + return user; + } catch (error) { + console.error( + "Erreur lors de la récupération de l'utilisateur connecté:", + error as ApiException, + ); + throw error; + } + }, + async fetchTicketDetails({ commit }, ticketId: number) { + try { + const ticket: Ticket = await makeFetch( + "GET", + `/api/1.0/ticket/ticket/${ticketId}`, + ); + commit("setTicketDetails", ticket); + } catch (error) { + console.error( + "Erreur lors du chargement du ticket:", + error as ApiException, + ); + throw error; + } + }, + async fetchTicketListByUrl({ commit, state }, url: string) { + try { + const { results, pagination, count } = (await makeFetch( + "GET", + url, + )) as PaginationResponse; + commit("setTicketList", [...state.ticket_list, ...results]); + commit("setCount", count); + commit("setPagination", pagination); + } catch (e: unknown) { + const error = e as ApiException; + console.error( + "Erreur lors du chargement de la liste des tickets par URL:", + error, + ); + } + }, + }, +}; diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/App.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/App.vue new file mode 100644 index 000000000..e90d48fe1 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/App.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketFilterListComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketFilterListComponent.vue new file mode 100644 index 000000000..896c42cec --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketFilterListComponent.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryListComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketHistoryListComponent.vue similarity index 87% rename from src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryListComponent.vue rename to src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketHistoryListComponent.vue index 23065f9c3..b0b1b4f07 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryListComponent.vue +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketHistoryListComponent.vue @@ -80,12 +80,12 @@ import { Person } from "ChillPersonAssets/types"; import { Thirdparty } from "src/Bundle/ChillThirdPartyBundle/Resources/public/types"; // Components -import PersonComponent from "./Person/PersonComponent.vue"; -import MotiveComponent from "./Motive/MotiveComponent.vue"; -import CommentComponent from "./Comment/CommentComponent.vue"; -import AddresseeComponent from "./Addressee/AddresseeComponent.vue"; -import StateComponent from "./State/StateComponent.vue"; -import EmergencyComponent from "./Emergency/EmergencyComponent.vue"; +import PersonComponent from "../../TicketApp/components/Person/PersonComponent.vue"; +import MotiveComponent from "../../TicketApp/components/Motive/MotiveComponent.vue"; +import CommentComponent from "../../TicketApp/components/Comment/CommentComponent.vue"; +import AddresseeComponent from "../../TicketApp/components/Addressee/AddresseeComponent.vue"; +import StateComponent from "../../TicketApp/components/State/StateComponent.vue"; +import EmergencyComponent from "../../TicketApp/components/Emergency/EmergencyComponent.vue"; import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue"; @@ -104,8 +104,9 @@ import { CHILL_TICKET_TICKET_HISTORY_EMERGENCY_CHANGE, CHILL_TICKET_TICKET_HISTORY_SET_CALLER, } from "translator"; -defineProps<{ history: TicketHistoryLine[] }>(); +const props = defineProps<{ history?: TicketHistoryLine[] }>(); +const history = props.history ?? []; const store = useStore(); const actionIcons = ref(store.getters.getActionIcons); diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListComponent.vue new file mode 100644 index 000000000..3320bc457 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListComponent.vue @@ -0,0 +1,140 @@ + + + diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketListItemComponent.vue b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListItemComponent.vue similarity index 80% rename from src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketListItemComponent.vue rename to src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListItemComponent.vue index 902a4df66..21ca17dc1 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketListItemComponent.vue +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListItemComponent.vue @@ -1,11 +1,10 @@