diff --git a/README.md b/README.md index 2bb1878..fb68a0e 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ If you are having problem during build, use [`patch-package`](https://github.com @@ -1,6 +1,14 @@ { "name": "vue-echarts", - "version": "6.6.8", + "version": "6.7.3", + "type": "module", + "exports": { + ".": { diff --git a/package.json b/package.json index c9bfeda..ef93c93 100644 --- a/package.json +++ b/package.json @@ -13,62 +13,62 @@ "serve": "docker run --rm --name tickets-manager-web-nginx -v $(pwd)/dist:/usr/share/nginx/html:ro -p9999:80 nginx" }, "dependencies": { - "@headlessui/vue": "^1.7.22", + "@headlessui/vue": "^1.7.23", "@intlify/unplugin-vue-i18n": "^4.0.0", "@jamescoyle/vue-icon": "^0.1.2", "@johanaarstein/dotlottie-player-light": "^1.0.13", "@mdi/js": "^7.4.47", - "@tanstack/vue-query": "^5.37.1", - "@unhead/vue": "^1.9.10", + "@tanstack/vue-query": "^5.62.12", + "@unhead/vue": "^1.11.14", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", "@vueuse/core": "10.2.0", - "axios": "^1.7.0", - "axios-retry": "^4.2.0", - "dayjs": "^1.11.11", + "axios": "^1.7.9", + "axios-retry": "^4.5.0", + "dayjs": "^1.11.13", "echarts": "^5.5.0", "floating-vue": "^5.2.2", "lodash": "^4.17.21", - "pinia": "^2.1.7", + "pinia": "^2.3.0", "typeface-inter": "^3.18.1", "uuid": "^9.0.1", - "vue": "^3.4.27", - "vue-echarts": "6.6.8", + "vue": "^3.5.13", + "vue-echarts": "6.7.3", "vue-i18n": "^9.13.1", "vue-number-animation": "^2.0.2", - "vue-router": "^4.3.2", + "vue-router": "^4.5.0", "vue-tailwind-datepicker": "^1.7.3" }, "devDependencies": { "@intlify/eslint-plugin-vue-i18n": "^2.0.0", - "@tailwindcss/forms": "^0.5.7", - "@tailwindcss/typography": "^0.5.13", - "@types/lodash": "^4.17.4", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@types/lodash": "^4.17.14", "@types/node": "^20.12.12", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", - "@vitejs/plugin-vue": "^5.0.4", + "@vitejs/plugin-vue": "^5.2.1", "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^13.0.0", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.20", "eslint": "^8", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsonc": "^2.15.1", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-tailwindcss": "^3.15.2", - "eslint-plugin-vue": "^9.26.0", - "eslint-plugin-vuejs-accessibility": "^2.3.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsonc": "^2.18.2", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-tailwindcss": "^3.17.5", + "eslint-plugin-vue": "^9.32.0", + "eslint-plugin-vuejs-accessibility": "^2.4.1", "patch-package": "^8.0.0", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "tailwindcss": "^3.4.3", - "typescript": "^5.4.5", + "postcss": "^8.4.49", + "prettier": "^3.4.2", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", "vite": "^5.2.11", "vite-plugin-static-copy": "^1.0.5", - "vue-tsc": "^2.0.19" + "vue-tsc": "^2.2.0" } } diff --git a/patches/vue-echarts+6.6.8.patch b/patches/vue-echarts+6.7.3.patch similarity index 65% rename from patches/vue-echarts+6.6.8.patch rename to patches/vue-echarts+6.7.3.patch index 5b17fb2..6c7b7ad 100644 --- a/patches/vue-echarts+6.6.8.patch +++ b/patches/vue-echarts+6.7.3.patch @@ -1,22 +1,23 @@ diff --git a/node_modules/vue-echarts/dist/index.esm.js b/node_modules/vue-echarts/dist/index.esm.js -index 4ef9b7c..f05e046 100644 +index 3ec3ce0..c9e8af8 100644 --- a/node_modules/vue-echarts/dist/index.esm.js +++ b/node_modules/vue-echarts/dist/index.esm.js @@ -1,5 +1,5 @@ - import { watch, unref, inject, computed, watchEffect, Vue2, defineComponent, shallowRef, toRefs, getCurrentInstance, onMounted, onBeforeUnmount, h, nextTick } from 'vue-demi'; + import { watch, isRef, unref, inject, computed, watchEffect, Vue2, defineComponent, shallowRef, toRefs, getCurrentInstance, onMounted, onBeforeUnmount, h, nextTick } from 'vue-demi'; -import { throttle, init } from 'echarts/core'; +import { throttle, init } from 'echarts/core.js'; import { addListener, removeListener } from 'resize-detector'; - /****************************************************************************** + /****************************************************************************** diff --git a/node_modules/vue-echarts/package.json b/node_modules/vue-echarts/package.json -index 5fce2eb..5bafc62 100644 +index 7e46d29..dd07c39 100644 --- a/node_modules/vue-echarts/package.json +++ b/node_modules/vue-echarts/package.json -@@ -1,6 +1,14 @@ - { - "name": "vue-echarts", - "version": "6.6.8", +@@ -87,5 +87,13 @@ + "build:demo": "vue-cli-service build", + "docs": "node ./scripts/docs.js", + "postinstall": "node ./scripts/postinstall.js" ++ }, + "type": "module", + "exports": { + ".": { @@ -24,7 +25,5 @@ index 5fce2eb..5bafc62 100644 + "import": "./dist/index.esm.js", + "types": "./dist/index.d.ts" + } -+ }, - "description": "Vue.js component for Apache ECharts.", - "author": "GU Yiling ", - "scripts": { + } + } diff --git a/src/assets/animations/empty-office.lottie b/src/assets/animations/empty-office.lottie new file mode 100644 index 0000000..eeb1ebb Binary files /dev/null and b/src/assets/animations/empty-office.lottie differ diff --git a/src/assets/animations/select-calendar-date.lottie b/src/assets/animations/select-calendar-date.lottie new file mode 100644 index 0000000..18d164e Binary files /dev/null and b/src/assets/animations/select-calendar-date.lottie differ diff --git a/src/components/CircularProgress.vue b/src/components/CircularProgress.vue new file mode 100644 index 0000000..a7eaee8 --- /dev/null +++ b/src/components/CircularProgress.vue @@ -0,0 +1,37 @@ + + + + + + diff --git a/src/components/LoadingProgressBar.vue b/src/components/LoadingProgressBar.vue new file mode 100644 index 0000000..fcd3b2e --- /dev/null +++ b/src/components/LoadingProgressBar.vue @@ -0,0 +1,27 @@ + + + + diff --git a/src/components/layout/NavigationDrawer.vue b/src/components/layout/NavigationDrawer.vue index 6e161f3..7ccf7b7 100644 --- a/src/components/layout/NavigationDrawer.vue +++ b/src/components/layout/NavigationDrawer.vue @@ -48,7 +48,7 @@ import { doesRouteBelongsTo } from '@/router/helpers'; import { ROUTE_NAMES } from '@/router/names'; import { useAuthStore } from '@/store/auth'; import MembersThumbnail from '@/views/Private/Members/MembersThumbnail.vue'; -import { mdiAccountGroup, mdiFinance, mdiHistory } from '@mdi/js'; +import { mdiAccountGroup, mdiCalendarMultiselect, mdiFinance, mdiHistory } from '@mdi/js'; import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { RouteLocationRaw, useRoute } from 'vue-router'; @@ -88,5 +88,13 @@ const sidebarNavigation = computed(() => [ icon: mdiHistory, active: doesRouteBelongsTo(route, ROUTE_NAMES.HISTORY), }, + { + label: i18n.t('navigation.attendance'), + to: { + name: ROUTE_NAMES.ATTENDANCE, + }, + icon: mdiCalendarMultiselect, + active: doesRouteBelongsTo(route, ROUTE_NAMES.ATTENDANCE), + }, ]); diff --git a/src/i18n/index.ts b/src/i18n/index.ts index ac2961c..a2ceafe 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -2,11 +2,13 @@ import { LOCALE_STORAGE_KEY } from '@/store/settings'; import { createI18nMessage } from '@vuelidate/validators'; import dayjs from 'dayjs'; import calendar from 'dayjs/plugin/calendar.js'; +import customParseFormat from 'dayjs/plugin/customParseFormat.js'; import duration from 'dayjs/plugin/duration.js'; import isBetween from 'dayjs/plugin/isBetween.js'; import LocalizedFormat from 'dayjs/plugin/localizedFormat.js'; import relativeTime from 'dayjs/plugin/relativeTime.js'; import updateLocale from 'dayjs/plugin/updateLocale.js'; +import weekday from 'dayjs/plugin/weekday.js'; import { createI18n, IntlDateTimeFormats, IntlNumberFormats, PluralizationRule } from 'vue-i18n'; import 'dayjs/locale/fr.js'; import 'dayjs/locale/en-gb.js'; @@ -16,6 +18,8 @@ dayjs.extend(relativeTime); dayjs.extend(LocalizedFormat); dayjs.extend(duration); dayjs.extend(isBetween); +dayjs.extend(customParseFormat); +dayjs.extend(weekday); dayjs.updateLocale('fr', { calendar: { diff --git a/src/i18n/locales/en-GB/attendance.json b/src/i18n/locales/en-GB/attendance.json new file mode 100644 index 0000000..5425156 --- /dev/null +++ b/src/i18n/locales/en-GB/attendance.json @@ -0,0 +1,54 @@ +{ + "calendar": { + "tile": { + "attending": "No one | {count} attendee | {count} attendees", + "debt": "No debt | {count} overconsumed | {count} overconsumed" + } + }, + "description": "Who was there? Who did what? Who owes money?", + "detail": { + "activity": { + "value": { + "FULL": "1 full day", + "HALF": "1 half-day", + "NONE": "absent" + } + }, + "attending": "No attendees | 1 attendee | {count} attendees", + "empty": { + "description": "Apparently, no one showed up on this day.", + "title": "No one" + }, + "search": { + "empty": { + "title": "No results" + }, + "label": "Search for a member", + "placeholder": "@:attendance.detail.search.label" + }, + "select": { + "description": "To view attendance and other details for a specific day.", + "title": "Select a date" + }, + "sort": { + "label": "Sort {suffix}", + "value": { + "activity": "By activity", + "debt": "By debt", + "name": "By name" + } + } + }, + "head": { + "title": "@:attendance.title" + }, + "navigation": { + "nextMonth": "Next month", + "previousMonth": "Previous month", + "today": "Today" + }, + "onFetch": { + "fail": "Unable to retrieve attendance for the period from {start} to {end}" + }, + "title": "Attendance" +} diff --git a/src/i18n/locales/en-GB/index.ts b/src/i18n/locales/en-GB/index.ts index 6f05379..1ef0a8b 100644 --- a/src/i18n/locales/en-GB/index.ts +++ b/src/i18n/locales/en-GB/index.ts @@ -11,3 +11,4 @@ export { default as tickets } from './tickets.json'; export { default as activity } from './activity.json'; export { default as errors } from './errors.json'; export { default as audit } from './audit.json'; +export { default as attendance } from './attendance.json'; diff --git a/src/i18n/locales/en-GB/navigation.json b/src/i18n/locales/en-GB/navigation.json index a7a6f91..f2c8647 100644 --- a/src/i18n/locales/en-GB/navigation.json +++ b/src/i18n/locales/en-GB/navigation.json @@ -1,4 +1,5 @@ { + "attendance": "@:attendance.title", "close": "Close menu", "history": "@:audit.list.title", "members": "@:members.list.title", diff --git a/src/i18n/locales/fr-FR/attendance.json b/src/i18n/locales/fr-FR/attendance.json new file mode 100644 index 0000000..f8f89f2 --- /dev/null +++ b/src/i18n/locales/fr-FR/attendance.json @@ -0,0 +1,54 @@ +{ + "calendar": { + "tile": { + "attending": "Personne | {count} présent | {count} présents", + "debt": "Aucune dette | {count} surconsommé | {count} surconsommés" + } + }, + "description": "Qui était là ? Qui a fait quoi ? Qui doit de la moula ?", + "detail": { + "activity": { + "value": { + "FULL": "1 journée complète", + "HALF": "1 demi-journée", + "NONE": "absent" + } + }, + "attending": "Aucun présent | 1 seul présent | {count} présents", + "empty": { + "description": "Apparemment, personne ne s'est présenté ce jour-ci.", + "title": "Personne" + }, + "search": { + "empty": { + "title": "Aucun résultat" + }, + "label": "Rechercher un membre", + "placeholder": "@:attendance.detail.search.label" + }, + "select": { + "description": "Pour visualiser les présences et autres informations d'un jour précis.", + "title": "Sélectionner une date" + }, + "sort": { + "label": "Trier {suffix}", + "value": { + "activity": "Par activité", + "debt": "Par dette", + "name": "Par nom" + } + } + }, + "head": { + "title": "@:attendance.title" + }, + "navigation": { + "nextMonth": "Mois suivant", + "previousMonth": "Mois précédent", + "today": "Aujourd'hui" + }, + "onFetch": { + "fail": "Impossible de récupérer les présences pour la période du {start} au {end}" + }, + "title": "Présence" +} diff --git a/src/i18n/locales/fr-FR/index.ts b/src/i18n/locales/fr-FR/index.ts index 6f05379..1ef0a8b 100644 --- a/src/i18n/locales/fr-FR/index.ts +++ b/src/i18n/locales/fr-FR/index.ts @@ -11,3 +11,4 @@ export { default as tickets } from './tickets.json'; export { default as activity } from './activity.json'; export { default as errors } from './errors.json'; export { default as audit } from './audit.json'; +export { default as attendance } from './attendance.json'; diff --git a/src/i18n/locales/fr-FR/navigation.json b/src/i18n/locales/fr-FR/navigation.json index c67f5cf..ee2b9b2 100644 --- a/src/i18n/locales/fr-FR/navigation.json +++ b/src/i18n/locales/fr-FR/navigation.json @@ -1,4 +1,5 @@ { + "attendance": "@:attendance.title", "close": "Fermer le menu", "history": "@:audit.list.title", "members": "@:members.list.title", diff --git a/src/router/names.ts b/src/router/names.ts index 90eb7ad..28504d3 100644 --- a/src/router/names.ts +++ b/src/router/names.ts @@ -4,6 +4,7 @@ import { flatMapDeep, isEqual } from 'lodash'; const RAW_ROUTE_NAMES = { LOGIN: 'LOGIN', HISTORY: 'HISTORY', + ATTENDANCE: 'ATTENDANCE', STATS: { INDEX: 'STATS.INDEX', INCOMES: { diff --git a/src/router/routes.ts b/src/router/routes.ts index d9918b2..9e7d8b9 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -213,6 +213,17 @@ export const routes: RouteRecordRaw[] = [ to: route.query.to, }), }, + { + path: 'attendance/:date?', + name: ROUTE_NAMES.ATTENDANCE, + component: () => import('@/views/Private/Attendance/AttendancePage.vue'), + props: (route) => ({ + month: route.query.month, + date: route.params.date, + search: route.query.search, + sort: route.query.sort, + }), + }, { path: 'profile', name: ROUTE_NAMES.USER.PROFILE, diff --git a/src/services/api/activity.ts b/src/services/api/activity.ts index c34bb4b..8a0547b 100644 --- a/src/services/api/activity.ts +++ b/src/services/api/activity.ts @@ -1,6 +1,6 @@ import HTTP from '../http'; -export const MAX_ATTENDANCE = 28; +export const MAX_ATTENDANCE = 40; export type ActivityPeriod = { date: string; diff --git a/src/services/api/attendance.ts b/src/services/api/attendance.ts new file mode 100644 index 0000000..8bb2d40 --- /dev/null +++ b/src/services/api/attendance.ts @@ -0,0 +1,42 @@ +import { MemberListItem } from './members'; +import HTTP from '../http'; + +export const MAX_ATTENDANCE = 40; + +export type AttendingMember = MemberListItem & { + attendance: { + tickets: { + count: number; // tickets count consumed + amount: number; // amount in euro + debt: { + count: number; // tickets count consumed when not paid yet + amount: number; // debt in euro + }; + }; + subscriptions: { + count: number; // subscriptions count + amount: number; // amount in euro + }; + }; +}; + +export type AttendancePeriod = { + date: string; + type: PeriodType; + data: { + members: AttendingMember[]; + }; +}; + +export const getAttendancePerDay = ( + from?: string, + to?: string, +): Promise[]> => { + return HTTP.get('/stats/attendance/day', { + params: { + ...(from && { from }), + ...(to && { to }), + }, + timeout: 30_000, + }).then(({ data }) => data); +}; diff --git a/src/services/api/members.ts b/src/services/api/members.ts index 4f539fc..7fdd9f0 100644 --- a/src/services/api/members.ts +++ b/src/services/api/members.ts @@ -4,7 +4,7 @@ import HTTP from '../http'; import dayjs from 'dayjs'; export type AttendanceType = 'subscription' | 'ticket'; -export type MemberLocation = 'poulailler' | 'pti-poulailler' | 'racine'; +export type MemberLocation = 'poulailler' | 'pti-poulailler' | 'racine' | 'cantina'; export interface Attendance { date: string; diff --git a/src/views/Private/Attendance/AttendanceCalendarTile.vue b/src/views/Private/Attendance/AttendanceCalendarTile.vue new file mode 100644 index 0000000..2d2a059 --- /dev/null +++ b/src/views/Private/Attendance/AttendanceCalendarTile.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/views/Private/Attendance/AttendanceDetail.vue b/src/views/Private/Attendance/AttendanceDetail.vue new file mode 100644 index 0000000..5be17b4 --- /dev/null +++ b/src/views/Private/Attendance/AttendanceDetail.vue @@ -0,0 +1,258 @@ + + + diff --git a/src/views/Private/Attendance/AttendancePage.vue b/src/views/Private/Attendance/AttendancePage.vue new file mode 100644 index 0000000..3d81186 --- /dev/null +++ b/src/views/Private/Attendance/AttendancePage.vue @@ -0,0 +1,243 @@ + + + diff --git a/src/views/Private/Attendance/AttendingMemberCard.vue b/src/views/Private/Attendance/AttendingMemberCard.vue new file mode 100644 index 0000000..5abd74f --- /dev/null +++ b/src/views/Private/Attendance/AttendingMemberCard.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/views/Private/Members/MembersList.vue b/src/views/Private/Members/MembersList.vue index 39e5239..f71a9e0 100644 --- a/src/views/Private/Members/MembersList.vue +++ b/src/views/Private/Members/MembersList.vue @@ -124,29 +124,33 @@
-
    - - -
  • - - - -
  • -
+ +