Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
379a914
feat(metrics): Update metric type to match new api return
froozeify Oct 26, 2025
401b1b4
feat(chart): Migrate to component before chartjs to obfuscate it and …
froozeify Oct 26, 2025
a2674a6
feat(chart): Migrate to component before chartjs to obfuscate it and …
froozeify Oct 26, 2025
01e3bc3
feat(colors): Add colors util
froozeify Oct 27, 2025
d2f5f07
feat(chart): Migrate to component before chartjs
froozeify Oct 28, 2025
6a1958e
feat(chart): add doughnut chart
froozeify Oct 28, 2025
ef8f16a
feat(chart): add doughnut chart
froozeify Oct 28, 2025
0fd470e
feat(metrics): Add date range selector
froozeify Nov 9, 2025
b0a4c42
chore: Migrate to props instead of model for GenericDateRangePicker
froozeify Nov 9, 2025
78e4857
feat(metrics): Add possibility to remove previous season from selector
froozeify Nov 9, 2025
e2ef4c1
feat(metrics): Add possibility to compare with previous period
froozeify Nov 9, 2025
2106731
feat(metrics): Add possibility to remove previous season from selector
froozeify Nov 9, 2025
9dc74c0
feat(chart): add line chart
froozeify Nov 9, 2025
8fcef17
feat(metrics): Add details activites metrics page
froozeify Nov 9, 2025
0ea9573
Update app/stores/usePresenceStore.ts
froozeify Nov 9, 2025
c9d8eac
Update app/stores/useMetricStore.ts
froozeify Nov 9, 2025
7be3cac
Update app/stores/useMetricStore.ts
froozeify Nov 9, 2025
96829ea
Update app/stores/useMetricStore.ts
froozeify Nov 9, 2025
1c858b7
Update app/stores/usePresenceStore.ts
froozeify Nov 9, 2025
20e70f2
feat(metrics): Add details activites metrics page
froozeify Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
--color-blue-200: #CFD8F5;
--color-blue-100: #E7ECFA;
--color-blue-50: #FFFFFF;

/* We are using a color already set in tailwind, so we juste need to set the default name for ui-nuxt */
--ui-orange: var(--color-orange-500);
--ui-purple: var(--color-purple-500);
}

:root {
Expand Down
46 changes: 46 additions & 0 deletions app/components/Chart/ChartBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">

import {
BarController,
BarElement,
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
Title,
Tooltip
} from 'chart.js'

import {Bar} from 'vue-chartjs'
import type {PropType} from "vue";

import type {ChartBarData, setChartDefaultBackgroundColors} from "~/utils/chart";

ChartJS.register(Title, Tooltip, Legend, BarController, BarElement, CategoryScale, LinearScale)

const props = defineProps({
data: {
type: Object as PropType<ChartBarData>,
required: true
},
options: {
type: Object,
default: {
responsive: true,
maintainAspectRatio: false,
}
}
});

</script>

<template>
<Bar
:data="setChartDefaultBackgroundColors(props.data)"
:options="props.options"
/>
</template>

<style scoped lang="css">

</style>
47 changes: 47 additions & 0 deletions app/components/Chart/ChartDoughnut.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">

import {
ArcElement,
BarController,
BarElement,
CategoryScale,
Chart as ChartJS, Colors, DoughnutController,
Legend,
LinearScale,
Title,
Tooltip
} from 'chart.js'

import {Doughnut} from 'vue-chartjs'
import type {PropType} from "vue";

import type {ChartBarData, setChartDefaultBackgroundColors} from "~/utils/chart";

ChartJS.register(Title, Tooltip, Legend, DoughnutController, ArcElement, CategoryScale, LinearScale)

const props = defineProps({
data: {
type: Object as PropType<ChartDoughnutData>,
required: true
},
options: {
type: Object,
default: {
responsive: true,
maintainAspectRatio: false,
}
}
});

</script>

<template>
<Doughnut
:data="setChartDefaultBackgroundColors(props.data, false)"
:options="props.options"
/>
</template>

<style scoped lang="css">

</style>
45 changes: 45 additions & 0 deletions app/components/Chart/ChartLine.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script setup lang="ts">

import {
Chart as ChartJS,
CategoryScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
LinearScale
} from 'chart.js'
import { Line } from 'vue-chartjs'
import type {PropType} from "vue";

import type {ChartLineData, setChartDefaultBackgroundColors} from "~/utils/chart";

ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend)

const props = defineProps({
data: {
type: Object as PropType<ChartLineData>,
required: true
},
options: {
type: Object,
default: {
responsive: true,
maintainAspectRatio: false,
}
}
});

</script>

<template>
<Line
:data="setChartDefaultBackgroundColors(props.data, true, 'borderColor')"
:options="props.options"
/>
</template>

<style scoped lang="css">

</style>
47 changes: 29 additions & 18 deletions app/components/Generic/GenericDateRangePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,36 @@
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
import 'v-calendar/style.css'
import dayjs from "dayjs";
import type {DateRange, DateRangeFilter} from "~/types/date";
import {type DateRange, DateRangeFilter} from "~/types/date";

interface Range {
label: string,
duration: { type: string, value: string|number }
}

const date = defineModel<DateRange|DateRangeFilter|undefined>({default: undefined})
const dateRange = ref<DateRange|undefined>(undefined)

if (date.value && typeof date.value.label !== 'string') {
dateRange.value = date.value as DateRange
}

const emit = defineEmits<{ rangeUpdated: [DateRange | DateRangeFilter | undefined] }>()

const props = defineProps({
seasonSelectors: {
type: Boolean,
default: true
},
excludePreviousSeason: {
type: Boolean,
default: false
},
dateRange: {
type: Object as PropType<DateRange|DateRangeFilter|undefined>,
default: undefined
}
});

if (props.dateRange && typeof props.dateRange.label !== 'string') {
dateRange.value = props.dateRange as DateRange
}

const columns = computed(() => {
return isMobile().value ? 1 : 2
})
Expand Down Expand Up @@ -57,41 +64,45 @@ const ranges = [
] as Range[]

if (props.seasonSelectors) {
if (props.excludePreviousSeason) {
ranges.push({label: 'Dernière année', duration: {type: 'year', value: 1}})
}

ranges.push({ label: 'Saison actuelle', duration: { type: 'filter', value: 'current-season' } })
ranges.push({ label: 'Saison précédente', duration: { type: 'filter', value: 'previous-season' } })

if (!props.excludePreviousSeason) {
ranges.push({ label: 'Saison précédente', duration: { type: 'filter', value: 'previous-season' } })
}
} else {
ranges.push({ label: 'Dernière année', duration: { type: 'year', value: 1 } })
}

function isRangeSelected(range: Range) {
if (!date.value) return false
if (!props.dateRange) return false

const isFilter = typeof range.duration.value === 'string';
if (isFilter) {
return typeof date.value === 'object' && 'value' in date.value && date.value.value === range.duration.value;
return typeof props.dateRange === 'object' && 'value' in props.dateRange && props.dateRange.value === range.duration.value;
}

return dayjs(date.value.start).isSame(dayjs().subtract(range.duration.value, range.duration.type), 'day') && dayjs().isSame(date.value.end, 'day')
return dayjs(props.dateRange.start).isSame(dayjs().subtract(range.duration.value, range.duration.type), 'day') && dayjs().isSame(props.dateRange.end, 'day')
}

function selectRange(range: Range) {
const isFilter = typeof range.duration.value === 'string';
if (isFilter) {
date.value = { label: range.label, value: range.duration.value} as DateRangeFilter;
// props.dateRange = { label: range.label, value: range.duration.value} as DateRangeFilter;
dateRange.value = undefined;
notify()
notify(new DateRangeFilter(range.label, range.duration.value))
return;
}

dateRange.value = { start: dayjs().subtract(range.duration.value, range.duration.type).toDate(), end: new Date() }
notify({ start: dayjs().subtract(range.duration.value, range.duration.type).toDate(), end: new Date() } as DateRange)
}

function notify() {
if (dateRange.value) {
date.value = dateRange.value
}
emit('rangeUpdated', date.value)
function notify(range: DateRange|DateRangeFilter|undefined) {
emit('rangeUpdated', range)
}
</script>

Expand All @@ -109,7 +120,7 @@ function notify() {
@click="selectRange(range)"
/>
</div>
<VCalendarDatePicker v-model.range="dateRange" @update:model-value="notify" :columns="columns" v-bind="{ ...attrs, ...$attrs }" />
<VCalendarDatePicker v-model.range="dateRange" :columns="columns" v-bind="{ ...attrs, ...$attrs }" />
</div>

</template>
Expand Down
2 changes: 1 addition & 1 deletion app/components/Generic/GenericStatCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const emit = defineEmits([
])

interface topRight {
value?: string,
value?: string|number,
tooltip?: string|null,
icon?: string|null,
useDefaultIcon?: boolean
Expand Down
16 changes: 2 additions & 14 deletions app/components/Member/MemberDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ import ActivityQuery from "~/composables/api/query/clubDependent/plugin/presence
import type {Activity} from "~/types/api/item/clubDependent/plugin/presence/activity";
import type {MemberSeason, MemberSeasonWrite} from "~/types/api/item/clubDependent/memberSeason";
import {ClubRole, getAvailableClubRoles} from "~/types/api/item/club";

import { ArcElement, CategoryScale, Chart as ChartJS, Colors, DoughnutController, Legend, LinearScale, Title, Tooltip } from 'chart.js'
import {Doughnut} from 'vue-chartjs'
import ModalDeleteConfirmation from "~/components/Modal/ModalDeleteConfirmation.vue";
import MemberSeasonQuery from "~/composables/api/query/clubDependent/MemberSeasonQuery";
import MemberSeasonSelectModal from "~/components/MemberSeason/MemberSeasonSelectModal.vue";
Expand All @@ -27,8 +24,6 @@ import {createBrowserCsvDownload} from "~/utils/browser";
import type {ColumnSort} from "@tanstack/table-core";
import {getTableSortVal} from "~/utils/table";

ChartJS.register(Title, Tooltip, Legend, DoughnutController, ArcElement, CategoryScale, LinearScale, Colors)


const props = defineProps({
member: {
Expand Down Expand Up @@ -80,11 +75,7 @@ const itemModalOpen = ref(false)
const changeMemberRoleModalOpen = ref(false)


const chartData: Ref<object|undefined> = ref(undefined)
const chartOptions = ref({
responsive: true,
maintainAspectRatio: false,
})
const chartData: Ref<ChartDoughnutData|undefined> = ref(undefined)

// Season table
const seasonPage = ref(1)
Expand Down Expand Up @@ -720,10 +711,7 @@ async function deleteMember() {
<div class="lg:col-span-9">
<GenericCard v-if="totalMemberPresences > 0" :title="`${totalMemberPresences} présences ces 12 derniers mois`">
<div class="h-96 mt-2">
<Doughnut
:data="chartData"
:options="chartOptions"
/>
<ChartDoughnut :data="chartData"/>
</div>
</GenericCard>
<GenericCard v-else>
Expand Down
Loading