Skip to content

Commit a83df19

Browse files
Merge pull request #3150 from carlobeltrame/dashboard
New dashboard
2 parents e79e835 + 8cb5b01 commit a83df19

File tree

15 files changed

+732
-170
lines changed

15 files changed

+732
-170
lines changed

api/src/Entity/Camp.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototy
9292
* All the programme that will be carried out during the camp. An activity may be carried out
9393
* multiple times in the same camp.
9494
*/
95-
#[ApiProperty(writable: false, example: '["/activities/1a2b3c4d"]')]
95+
#[ApiProperty(writable: false, example: '/activities?camp=%2Fcamps%2F1a2b3c4d')]
9696
#[Groups(['read'])]
9797
#[ORM\OneToMany(targetEntity: Activity::class, mappedBy: 'camp', orphanRemoval: true)]
9898
public Collection $activities;

common/helpers/dateHelperUTCFormatted.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@ function hourShort(dateTimeString) {
1313
return dayjs.utc(dateTimeString).format(i18n.tc('global.datetime.hourShort'))
1414
}
1515

16+
function timeDurationShort(start, end) {
17+
const startTime = dayjs.utc(start)
18+
const endTime = dayjs.utc(end)
19+
const duration = dayjs(endTime.diff(startTime))
20+
const durationInMinutes = duration.valueOf() / 1000 / 60
21+
if (durationInMinutes < 60) {
22+
return i18n.tc('global.datetime.duration.minutesOnly', 0, {
23+
minutes: durationInMinutes,
24+
})
25+
}
26+
if (durationInMinutes % 60 === 0) {
27+
return i18n.tc('global.datetime.duration.hoursOnly', 0, {
28+
hours: durationInMinutes / 60,
29+
})
30+
}
31+
return i18n.tc('global.datetime.duration.hoursAndMinutes', 0, {
32+
hours: Math.floor(durationInMinutes / 60.0),
33+
minutes: durationInMinutes % 60,
34+
})
35+
}
36+
1637
// short format of dateTime range
1738
// doesn't repeat end date if on the same day
1839
function rangeShort(start, end) {
@@ -42,4 +63,4 @@ function dateRange(start, end) {
4263
return `${dateShort(start)} - ${dateLong(end)}`
4364
}
4465

45-
export { dateShort, dateLong, hourShort, dateRange, rangeShort }
66+
export { dateShort, dateLong, timeDurationShort, hourShort, dateRange, rangeShort }

common/locales/de.json

+5
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@
165165
"dateLong": "dd L",
166166
"dateShort": "dd D.M.",
167167
"dateTimeLong": "dd L HH:mm",
168+
"duration": {
169+
"hoursAndMinutes": "{hours}h {minutes}min",
170+
"hoursOnly": "{hours}h",
171+
"minutesOnly": "{minutes}min"
172+
},
168173
"hourLong": "HH:mm",
169174
"hourShort": "H:mm"
170175
}

common/locales/en.json

+5
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@
172172
"dateLong": "dd L",
173173
"dateShort": "dd M/D",
174174
"dateTimeLong": "dd L HH:mm",
175+
"duration": {
176+
"hoursAndMinutes": "{hours}h {minutes}m",
177+
"hoursOnly": "{hours}h",
178+
"minutesOnly": "{minutes}m"
179+
},
175180
"hourLong": "HH:mm",
176181
"hourShort": "h:mm A"
177182
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<template>
2+
<tr class="row">
3+
<th style="text-align: left" class="tabular-nums" scope="row">
4+
<TextAlignBaseline
5+
><span class="smaller">{{ scheduleEntry.number }}</span></TextAlignBaseline
6+
>
7+
<br />
8+
<CategoryChip small dense :category="category" class="d-sm-none" />
9+
</th>
10+
<td class="d-none d-sm-table-cell">
11+
<CategoryChip small dense :category="category" />
12+
</td>
13+
<td class="nowrap">
14+
{{ start }}<br />
15+
<span class="e-subtitle">{{ duration }}</span>
16+
</td>
17+
<td style="width: 100%" class="contentrow">
18+
<router-link
19+
:to="routerLink"
20+
class="text-decoration-none text-decoration-hover-underline black--text"
21+
>
22+
{{ title }}<br />
23+
</router-link>
24+
<span class="e-subtitle">{{ location }}</span>
25+
</td>
26+
<td class="contentrow avatarrow overflow-visible">
27+
<AvatarRow :camp-collaborations="collaborators" size="28" class="ml-auto" />
28+
</td>
29+
</tr>
30+
</template>
31+
32+
<script>
33+
import AvatarRow from './AvatarRow.vue'
34+
import CategoryChip from '@/components/generic/CategoryChip.vue'
35+
import {
36+
hourShort,
37+
timeDurationShort,
38+
} from '../../common/helpers/dateHelperUTCFormatted.js'
39+
import TextAlignBaseline from '@/components/layout/TextAlignBaseline.vue'
40+
41+
export default {
42+
name: 'ActivityRow',
43+
components: { CategoryChip, AvatarRow, TextAlignBaseline },
44+
props: {
45+
scheduleEntry: { type: Object, required: true },
46+
},
47+
computed: {
48+
collaborators() {
49+
return this.scheduleEntry
50+
.activity()
51+
.activityResponsibles()
52+
.items.map((responsible) => responsible.campCollaboration())
53+
},
54+
category() {
55+
return this.scheduleEntry.activity().category()
56+
},
57+
title() {
58+
return this.scheduleEntry.activity().title
59+
},
60+
location() {
61+
return this.scheduleEntry.activity().location
62+
},
63+
start() {
64+
return hourShort(this.scheduleEntry.start)
65+
},
66+
duration() {
67+
return timeDurationShort(this.scheduleEntry.start, this.scheduleEntry.end)
68+
},
69+
routerLink() {
70+
return {
71+
name: 'activity',
72+
params: {
73+
campId: this.scheduleEntry.period().camp().id,
74+
scheduleEntryId: this.scheduleEntry.id,
75+
},
76+
}
77+
},
78+
},
79+
}
80+
</script>
81+
82+
<style scoped>
83+
.row {
84+
display: table-row;
85+
vertical-align: baseline;
86+
}
87+
88+
tr + tr :is(td, th) {
89+
border-top: 1px solid #ddd;
90+
}
91+
92+
:is(td, th) {
93+
padding-top: 0.25rem;
94+
padding-bottom: 0.25rem;
95+
}
96+
97+
:is(td, th) + :is(td, th) {
98+
padding-left: 0.5rem;
99+
}
100+
101+
.contentrow {
102+
max-width: 100px;
103+
overflow: hidden;
104+
text-overflow: ellipsis;
105+
white-space: nowrap;
106+
}
107+
108+
.avatarrow {
109+
vertical-align: middle;
110+
}
111+
112+
.e-subtitle {
113+
font-size: 0.9em;
114+
color: #666;
115+
}
116+
117+
.nowrap {
118+
white-space: nowrap;
119+
}
120+
121+
.smaller {
122+
font-size: 0.75em;
123+
}
124+
125+
.text-decoration-hover-underline:hover {
126+
text-decoration: underline !important;
127+
}
128+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<template>
2+
<div class="avatarrow" :style="avatarrow">
3+
<div
4+
v-for="campCollaboration in campCollaborations"
5+
:key="campCollaboration && campCollaboration._meta.self"
6+
class="avataritem"
7+
>
8+
<UserAvatar :size="Number(size)" :camp-collaboration="campCollaboration" />
9+
</div>
10+
</div>
11+
</template>
12+
13+
<script>
14+
import UserAvatar from '@/components/user/UserAvatar.vue'
15+
16+
export default {
17+
name: 'AvatarRow',
18+
components: { UserAvatar },
19+
props: {
20+
campCollaborations: { type: Array, default: () => [] },
21+
size: { type: [Number, String], default: 20 },
22+
},
23+
computed: {
24+
maxWidth() {
25+
return (
26+
(this.campCollaborations?.length - 1) * (Number(this.size) * 0.25) +
27+
Number(this.size)
28+
)
29+
},
30+
avatarrow() {
31+
return {
32+
'max-width': `${this.maxWidth}px`,
33+
'font-size': `${this.size}px`,
34+
}
35+
},
36+
},
37+
}
38+
</script>
39+
40+
<style scoped lang="scss">
41+
.avatarrow {
42+
display: flex;
43+
flex-direction: row-reverse;
44+
gap: 0.75em;
45+
padding-left: 0.5em;
46+
padding-right: 0.5em;
47+
transition: gap 0.25s ease;
48+
}
49+
50+
@media #{map-get($display-breakpoints, 'md-and-up')} {
51+
.avatarrow {
52+
gap: 1.1em;
53+
}
54+
}
55+
56+
.avatarrow:hover {
57+
gap: 1.1em;
58+
}
59+
60+
.avataritem {
61+
display: grid;
62+
width: 0;
63+
place-content: center;
64+
text-decoration: none;
65+
}
66+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<template>
2+
<v-chip
3+
label
4+
outlined
5+
:color="value ? 'primary' : null"
6+
@click="$emit('input', !value)"
7+
>{{ label }}</v-chip
8+
>
9+
</template>
10+
11+
<script>
12+
export default {
13+
name: 'BooleanFilter',
14+
props: {
15+
value: Boolean,
16+
label: { type: String, required: true },
17+
},
18+
}
19+
</script>
20+
21+
<style scoped></style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<span>|</span>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'FilterDivider',
8+
}
9+
</script>
10+
11+
<style scoped>
12+
span {
13+
color: rgba(0, 0, 0, 0.12);
14+
align-self: center;
15+
}
16+
</style>

0 commit comments

Comments
 (0)