Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
- ADDED admin - track files sizes
- ADDED login/logout notifications
- FIXED system - customer deploy stack name
- ADDED calandar - planning view (default for mobile)
10 changes: 10 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@fullcalendar/core": "^6.1.20",
"@fullcalendar/daygrid": "^6.1.20",
"@fullcalendar/interaction": "^6.1.20",
"@fullcalendar/list": "^6.1.20",
"@fullcalendar/timegrid": "^6.1.20",
"@fullcalendar/vue3": "^6.1.20",
"axios": "^1.7.0",
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,30 @@
.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events {
min-height: 4.5em;
}

/* FullCalendar mobile responsive overrides */
@media (max-width: 640px) {
.fc .fc-toolbar {
flex-wrap: wrap;
gap: 0.5rem;
}
.fc .fc-toolbar-title {
font-size: 1rem;
width: 100%;
text-align: center;
order: -1;
}
.fc .fc-button {
font-size: 0.7rem;
padding: 0.3rem 0.5rem;
}
.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events {
min-height: 2.5em;
}
.fc .fc-daygrid-event-dot {
border-width: 4px;
}
.fc {
font-size: 0.75rem;
}
}
25 changes: 16 additions & 9 deletions frontend/src/views/CalendarView.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import FullCalendar from '@fullcalendar/vue3'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import listPlugin from '@fullcalendar/list'
import interactionPlugin from '@fullcalendar/interaction'
import frLocale from '@fullcalendar/core/locales/fr'
import type { EventClickArg, DatesSetArg } from '@fullcalendar/core'
Expand Down Expand Up @@ -38,15 +39,21 @@ const calendarEvents = computed(() =>
}))
)

const isMobile = ref(window.innerWidth < 640)

function onResize() {
isMobile.value = window.innerWidth < 640
}
window.addEventListener('resize', onResize)
onUnmounted(() => window.removeEventListener('resize', onResize))

const calendarOptions = computed(() => ({
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
initialView: 'dayGridMonth' as const,
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
initialView: isMobile.value ? 'listWeek' : 'dayGridMonth',
locale: frLocale,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay',
},
headerToolbar: isMobile.value
? { left: 'prev,next', center: 'title', right: 'listWeek,dayGridMonth' }
: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' },
eventTimeFormat: {
hour: '2-digit' as const,
minute: '2-digit' as const,
Expand Down Expand Up @@ -121,7 +128,7 @@ onMounted(() => {
<AppBreadcrumb :show-title="false">
<template v-if="auth.isMember" #after>
<RouterLink to="/calendar/new" class="btn-primary ml-auto">
<i class="fa-solid fa-plus mr-1"></i>Créer un événement
<i class="fa-solid fa-plus mr-1"></i><span class="hidden sm:inline">Créer un événement</span><span class="sm:hidden">Créer</span>
</RouterLink>
</template>
</AppBreadcrumb>
Expand Down
140 changes: 72 additions & 68 deletions frontend/src/views/EventDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ function formatShortDate(d: string) {

<!-- Bloc 1 : En-tête -->
<div class="card">
<div class="flex items-start justify-between">
<div class="flex flex-col sm:flex-row items-start justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">
<i class="fa-solid fa-circle text-sm mr-2" :style="{ color: event.type_color || '#999' }"></i>
Expand All @@ -219,7 +219,7 @@ function formatShortDate(d: string) {
Organisé par {{ event.owner_nickname }}
</div>
</div>
<div v-if="canEdit" class="flex space-x-2 flex-shrink-0">
<div v-if="canEdit" class="flex flex-wrap gap-2 flex-shrink-0">
<button @click="handleCopy" class="btn-secondary text-sm"><i class="fa-solid fa-copy mr-1"></i>Copier</button>
<RouterLink :to="`/calendar/${event.id}/edit`" class="btn-secondary text-sm"><i class="fa-solid fa-edit mr-1"></i>Modifier</RouterLink>
<button @click="handleDelete" class="btn-danger text-sm"><i class="fa-solid fa-trash mr-1"></i>Supprimer</button>
Expand Down Expand Up @@ -307,31 +307,33 @@ function formatShortDate(d: string) {
<!-- Bloc 4 : ATO -->
<div v-if="event.ato && event.flights.length" class="card">
<h2 class="text-lg font-semibold mb-4">ATO</h2>
<table class="w-full text-sm">
<thead>
<tr class="border-b text-left">
<th class="py-2">Flight</th>
<th class="py-2">Appareil</th>
<th class="py-2">Mission</th>
<th class="py-2">Joueurs</th>
</tr>
</thead>
<tbody>
<tr v-for="f in event.flights" :key="f.id" class="border-b">
<td class="py-2">{{ f.name }}</td>
<td class="py-2">
<span v-if="f.aircraft_name">{{ f.aircraft_name }} x{{ f.nb_slots }}</span>
</td>
<td class="py-2">{{ f.mission }}</td>
<td class="py-2">
<template v-for="(s, idx) in f.slots" :key="s.id">
<span v-if="idx > 0">, </span>
<span>{{ s.user_nickname || s.username || '= vide =' }}</span>
</template>
</td>
</tr>
</tbody>
</table>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b text-left">
<th class="py-2">Flight</th>
<th class="py-2">Appareil</th>
<th class="py-2">Mission</th>
<th class="py-2">Joueurs</th>
</tr>
</thead>
<tbody>
<tr v-for="f in event.flights" :key="f.id" class="border-b">
<td class="py-2">{{ f.name }}</td>
<td class="py-2">
<span v-if="f.aircraft_name">{{ f.aircraft_name }} x{{ f.nb_slots }}</span>
</td>
<td class="py-2">{{ f.mission }}</td>
<td class="py-2">
<template v-for="(s, idx) in f.slots" :key="s.id">
<span v-if="idx > 0">, </span>
<span>{{ s.user_nickname || s.username || '= vide =' }}</span>
</template>
</td>
</tr>
</tbody>
</table>
</div>
</div>

<!-- Bloc 5 : Participation (Vote + Choix) -->
Expand Down Expand Up @@ -458,48 +460,50 @@ function formatShortDate(d: string) {
>
{{ showAllChoices ? 'masquer' : 'voir plus ...' }}
</button>
<table v-if="showAllChoices" class="w-full text-sm mt-3">
<thead>
<tr class="border-b">
<th rowspan="2" class="text-left py-2 align-top">Joueurs</th>
<th colspan="2" class="text-left py-1">Choix 1</th>
<th colspan="2" class="text-left py-1">Choix 2</th>
<th colspan="2" class="text-left py-1">Choix 3</th>
</tr>
<tr class="border-b text-xs text-gray-500">
<th class="text-left py-1">Module</th>
<th class="text-left py-1">Tâche</th>
<th class="text-left py-1">Module</th>
<th class="text-left py-1">Tâche</th>
<th class="text-left py-1">Module</th>
<th class="text-left py-1">Tâche</th>
</tr>
</thead>
<tbody>
<tr v-for="v in [...votesYes, ...votesMaybe]" :key="v.id" class="border-b">
<td class="py-2">
{{ v.user_nickname }}
<i v-if="v.vote === null" class="fa-solid fa-exclamation-circle text-yellow-500 ml-1" title="peut-être absent"></i>
<i v-if="v.comment" class="fa-solid fa-comment text-gray-400 ml-1" :title="v.comment"></i>
<span v-if="v.created_at" class="text-xs text-gray-400 ml-1" :title="formatDate(v.created_at)">{{ formatShortDate(v.created_at) }}</span>
</td>
<template v-for="n in [1, 2, 3]" :key="n">
<template v-if="usersChoicesMap.get(v.user_id)?.[n]">
<td class="py-2">{{ usersChoicesMap.get(v.user_id)![n]!.module_name }}</td>
<td class="py-2">
{{ usersChoicesMap.get(v.user_id)![n]!.task_as_string }}
<i
v-if="usersChoicesMap.get(v.user_id)![n]!.comment"
class="fa-solid fa-circle-question text-gray-400 ml-1"
:title="usersChoicesMap.get(v.user_id)![n]!.comment ?? ''"
></i>
</td>
<div v-if="showAllChoices" class="overflow-x-auto mt-3">
<table class="w-full text-sm">
<thead>
<tr class="border-b">
<th rowspan="2" class="text-left py-2 align-top">Joueurs</th>
<th colspan="2" class="text-left py-1">Choix 1</th>
<th colspan="2" class="text-left py-1">Choix 2</th>
<th colspan="2" class="text-left py-1">Choix 3</th>
</tr>
<tr class="border-b text-xs text-gray-500">
<th class="text-left py-1">Module</th>
<th class="text-left py-1">Tâche</th>
<th class="text-left py-1">Module</th>
<th class="text-left py-1">Tâche</th>
<th class="text-left py-1">Module</th>
<th class="text-left py-1">Tâche</th>
</tr>
</thead>
<tbody>
<tr v-for="v in [...votesYes, ...votesMaybe]" :key="v.id" class="border-b">
<td class="py-2">
{{ v.user_nickname }}
<i v-if="v.vote === null" class="fa-solid fa-exclamation-circle text-yellow-500 ml-1" title="peut-être absent"></i>
<i v-if="v.comment" class="fa-solid fa-comment text-gray-400 ml-1" :title="v.comment"></i>
<span v-if="v.created_at" class="text-xs text-gray-400 ml-1" :title="formatDate(v.created_at)">{{ formatShortDate(v.created_at) }}</span>
</td>
<template v-for="n in [1, 2, 3]" :key="n">
<template v-if="usersChoicesMap.get(v.user_id)?.[n]">
<td class="py-2">{{ usersChoicesMap.get(v.user_id)![n]!.module_name }}</td>
<td class="py-2">
{{ usersChoicesMap.get(v.user_id)![n]!.task_as_string }}
<i
v-if="usersChoicesMap.get(v.user_id)![n]!.comment"
class="fa-solid fa-circle-question text-gray-400 ml-1"
:title="usersChoicesMap.get(v.user_id)![n]!.comment ?? ''"
></i>
</td>
</template>
<td v-else colspan="2" class="py-2">&nbsp;</td>
</template>
<td v-else colspan="2" class="py-2">&nbsp;</td>
</template>
</tr>
</tbody>
</table>
</tr>
</tbody>
</table>
</div>
</div>
</div>

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/views/EventEditView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ async function handleSubmit() {

<form @submit.prevent="handleSubmit" class="card space-y-4">
<!-- Dates -->
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="label">Début <span class="text-xs text-gray-400 font-normal">(heure locale)</span></label>
<input v-model="form.start_date" type="datetime-local" class="input" required />
Expand Down Expand Up @@ -233,7 +233,7 @@ async function handleSubmit() {
</div>

<!-- Sim / ATO checkboxes -->
<div class="flex space-x-6">
<div class="flex flex-wrap gap-x-6 gap-y-2">
<label class="flex items-center space-x-2">
<input v-model="form.sim_dcs" type="checkbox" class="rounded" />
<span class="text-sm">Simulateur DCS</span>
Expand Down Expand Up @@ -264,7 +264,7 @@ async function handleSubmit() {
<div>
<label class="label">Réservé aux</label>
<p class="text-xs text-gray-500 mb-1">Ne rien cocher si ouvert à tout le monde</p>
<div class="flex space-x-6">
<div class="flex flex-wrap gap-x-6 gap-y-2">
<label class="flex items-center space-x-2">
<input type="checkbox" :value="1" v-model="form.restrictions" class="rounded" />
<span class="text-sm">Cadets</span>
Expand Down
Loading