Introduce group basic federation, event new page and notifications
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -1,133 +1,155 @@
|
||||
<template>
|
||||
<div class="address-autocomplete">
|
||||
<b-field expanded>
|
||||
<template slot="label">
|
||||
{{ $t('Find an address') }}
|
||||
<b-button v-if="!gettingLocation" size="is-small" icon-right="map-marker" @click="locateMe" />
|
||||
<span v-else>{{ $t('Getting location') }}</span>
|
||||
</template>
|
||||
<b-autocomplete
|
||||
:data="addressData"
|
||||
v-model="queryText"
|
||||
:placeholder="$t('e.g. 10 Rue Jangot')"
|
||||
field="fullName"
|
||||
:loading="isFetching"
|
||||
@typing="fetchAsyncData"
|
||||
icon="map-marker"
|
||||
expanded
|
||||
@select="updateSelected">
|
||||
|
||||
<template slot-scope="{option}">
|
||||
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
||||
<b>{{ option.poiInfos.name }}</b><br />
|
||||
<small>{{ option.poiInfos.alternativeName }}</small>
|
||||
</template>
|
||||
<template slot="empty">
|
||||
<span v-if="isFetching">{{ $t('Searching…') }}</span>
|
||||
<div v-else-if="queryText.length >= 3" class="is-enabled">
|
||||
<span>{{ $t('No results for "{queryText}"') }}</span>
|
||||
<span>{{ $t('You can try another search term or drag and drop the marker on the map', { queryText }) }}</span>
|
||||
<!-- <p class="control" @click="openNewAddressModal">-->
|
||||
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
|
||||
<!-- </p>-->
|
||||
</div>
|
||||
</template>
|
||||
</b-autocomplete>
|
||||
</b-field>
|
||||
<div class="map" v-if="selected && selected.geom">
|
||||
<map-leaflet
|
||||
:coords="selected.geom"
|
||||
:marker="{ text: [selected.poiInfos.name, selected.poiInfos.alternativeName], icon: selected.poiInfos.poiIcon.icon}"
|
||||
:updateDraggableMarkerCallback="reverseGeoCode"
|
||||
:options="{ zoom: mapDefaultZoom }"
|
||||
:readOnly="false"
|
||||
/>
|
||||
</div>
|
||||
<!-- <b-modal v-if="selected" :active.sync="addressModalActive" :width="640" has-modal-card scroll="keep">-->
|
||||
<!-- <div class="modal-card" style="width: auto">-->
|
||||
<!-- <header class="modal-card-head">-->
|
||||
<!-- <p class="modal-card-title">{{ $t('Add an address') }}</p>-->
|
||||
<!-- </header>-->
|
||||
<!-- <section class="modal-card-body">-->
|
||||
<!-- <form>-->
|
||||
<!-- <b-field :label="$t('Name')">-->
|
||||
<!-- <b-input aria-required="true" required v-model="selected.description" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field :label="$t('Street')">-->
|
||||
<!-- <b-input v-model="selected.street" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field grouped>-->
|
||||
<!-- <b-field :label="$t('Postal Code')">-->
|
||||
<!-- <b-input v-model="selected.postalCode" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field :label="$t('Locality')">-->
|
||||
<!-- <b-input v-model="selected.locality" />-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field grouped>-->
|
||||
<!-- <b-field :label="$t('Region')">-->
|
||||
<!-- <b-input v-model="selected.region" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field :label="$t('Country')">-->
|
||||
<!-- <b-input v-model="selected.country" />-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </form>-->
|
||||
<!-- </section>-->
|
||||
<!-- <footer class="modal-card-foot">-->
|
||||
<!-- <button class="button" type="button" @click="resetPopup()">{{ $t('Clear') }}</button>-->
|
||||
<!-- </footer>-->
|
||||
<!-- </div>-->
|
||||
<!-- </b-modal>-->
|
||||
<div class="address-autocomplete">
|
||||
<b-field expanded>
|
||||
<template slot="label">
|
||||
{{ $t("Find an address") }}
|
||||
<b-button
|
||||
v-if="!gettingLocation"
|
||||
size="is-small"
|
||||
icon-right="map-marker"
|
||||
@click="locateMe"
|
||||
/>
|
||||
<span v-else>{{ $t("Getting location") }}</span>
|
||||
</template>
|
||||
<b-autocomplete
|
||||
:data="addressData"
|
||||
v-model="queryText"
|
||||
:placeholder="$t('e.g. 10 Rue Jangot')"
|
||||
field="fullName"
|
||||
:loading="isFetching"
|
||||
@typing="fetchAsyncData"
|
||||
icon="map-marker"
|
||||
expanded
|
||||
@select="updateSelected"
|
||||
>
|
||||
<template slot-scope="{ option }">
|
||||
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
||||
<b>{{ option.poiInfos.name }}</b
|
||||
><br />
|
||||
<small>{{ option.poiInfos.alternativeName }}</small>
|
||||
</template>
|
||||
<template slot="empty">
|
||||
<span v-if="isFetching">{{ $t("Searching…") }}</span>
|
||||
<div v-else-if="queryText.length >= 3" class="is-enabled">
|
||||
<span>{{ $t('No results for "{queryText}"') }}</span>
|
||||
<span>{{
|
||||
$t("You can try another search term or drag and drop the marker on the map", {
|
||||
queryText,
|
||||
})
|
||||
}}</span>
|
||||
<!-- <p class="control" @click="openNewAddressModal">-->
|
||||
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
|
||||
<!-- </p>-->
|
||||
</div>
|
||||
</template>
|
||||
</b-autocomplete>
|
||||
</b-field>
|
||||
<div class="map" v-if="selected && selected.geom">
|
||||
<map-leaflet
|
||||
:coords="selected.geom"
|
||||
:marker="{
|
||||
text: [selected.poiInfos.name, selected.poiInfos.alternativeName],
|
||||
icon: selected.poiInfos.poiIcon.icon,
|
||||
}"
|
||||
:updateDraggableMarkerCallback="reverseGeoCode"
|
||||
:options="{ zoom: mapDefaultZoom }"
|
||||
:readOnly="false"
|
||||
/>
|
||||
</div>
|
||||
<!-- <b-modal v-if="selected" :active.sync="addressModalActive" :width="640" has-modal-card scroll="keep">-->
|
||||
<!-- <div class="modal-card" style="width: auto">-->
|
||||
<!-- <header class="modal-card-head">-->
|
||||
<!-- <p class="modal-card-title">{{ $t('Add an address') }}</p>-->
|
||||
<!-- </header>-->
|
||||
<!-- <section class="modal-card-body">-->
|
||||
<!-- <form>-->
|
||||
<!-- <b-field :label="$t('Name')">-->
|
||||
<!-- <b-input aria-required="true" required v-model="selected.description" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field :label="$t('Street')">-->
|
||||
<!-- <b-input v-model="selected.street" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field grouped>-->
|
||||
<!-- <b-field :label="$t('Postal Code')">-->
|
||||
<!-- <b-input v-model="selected.postalCode" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field :label="$t('Locality')">-->
|
||||
<!-- <b-input v-model="selected.locality" />-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field grouped>-->
|
||||
<!-- <b-field :label="$t('Region')">-->
|
||||
<!-- <b-input v-model="selected.region" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<!-- <b-field :label="$t('Country')">-->
|
||||
<!-- <b-input v-model="selected.country" />-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </form>-->
|
||||
<!-- </section>-->
|
||||
<!-- <footer class="modal-card-foot">-->
|
||||
<!-- <button class="button" type="button" @click="resetPopup()">{{ $t('Clear') }}</button>-->
|
||||
<!-- </footer>-->
|
||||
<!-- </div>-->
|
||||
<!-- </b-modal>-->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { Address, IAddress } from '@/types/address.model';
|
||||
import { ADDRESS, REVERSE_GEOCODE } from '@/graphql/address';
|
||||
import { Modal } from 'buefy/dist/components/dialog';
|
||||
import { LatLng } from 'leaflet';
|
||||
import { debounce } from 'lodash';
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { LatLng } from "leaflet";
|
||||
import { debounce } from "lodash";
|
||||
import { Address, IAddress } from "../../types/address.model";
|
||||
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
import { IConfig } from "../../types/config.model";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
'map-leaflet': () => import(/* webpackChunkName: "map" */ '@/components/Map.vue'),
|
||||
Modal,
|
||||
"map-leaflet": () => import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
||||
},
|
||||
apollo: {
|
||||
config: CONFIG,
|
||||
},
|
||||
})
|
||||
export default class AddressAutoComplete extends Vue {
|
||||
|
||||
@Prop({ required: true }) value!: IAddress;
|
||||
|
||||
addressData: IAddress[] = [];
|
||||
|
||||
selected: IAddress = new Address();
|
||||
isFetching: boolean = false;
|
||||
queryText: string = (this.value && (new Address(this.value)).fullName) || '';
|
||||
addressModalActive: boolean = false;
|
||||
private gettingLocation: boolean = false;
|
||||
|
||||
isFetching = false;
|
||||
|
||||
queryText: string = (this.value && new Address(this.value).fullName) || "";
|
||||
|
||||
addressModalActive = false;
|
||||
|
||||
private gettingLocation = false;
|
||||
|
||||
private location!: Position;
|
||||
|
||||
private gettingLocationError: any;
|
||||
private mapDefaultZoom: number = 15;
|
||||
|
||||
private mapDefaultZoom = 15;
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
// We put this in data because of issues like https://github.com/vuejs/vue-class-component/issues/263
|
||||
fetchAsyncData!: Function;
|
||||
|
||||
// We put this in data because of issues like
|
||||
// https://github.com/vuejs/vue-class-component/issues/263
|
||||
data() {
|
||||
return {
|
||||
fetchAsyncData: debounce(this.asyncData, 200),
|
||||
};
|
||||
}
|
||||
|
||||
async asyncData(query: String) {
|
||||
async asyncData(query: string) {
|
||||
if (!query.length) {
|
||||
this.addressData = [];
|
||||
this.selected = new Address();
|
||||
@@ -142,27 +164,27 @@ export default class AddressAutoComplete extends Vue {
|
||||
this.isFetching = true;
|
||||
const result = await this.$apollo.query({
|
||||
query: ADDRESS,
|
||||
fetchPolicy: 'network-only',
|
||||
fetchPolicy: "network-only",
|
||||
variables: {
|
||||
query,
|
||||
locale: this.$i18n.locale,
|
||||
},
|
||||
});
|
||||
|
||||
this.addressData = result.data.searchAddress.map(address => new Address(address));
|
||||
this.addressData = result.data.searchAddress.map((address: IAddress) => new Address(address));
|
||||
this.isFetching = false;
|
||||
}
|
||||
|
||||
@Watch('config')
|
||||
watchConfig(config: IConfig) {
|
||||
@Watch("config")
|
||||
watchConfig(config: IConfig) {
|
||||
if (!config.geocoding.autocomplete) {
|
||||
// If autocomplete is disabled, we put a larger debounce value so that we don't request with incomplete address
|
||||
// @ts-ignore
|
||||
// If autocomplete is disabled, we put a larger debounce value
|
||||
// so that we don't request with incomplete address
|
||||
this.fetchAsyncData = debounce(this.asyncData, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('value')
|
||||
@Watch("value")
|
||||
updateEditing() {
|
||||
if (!(this.value && this.value.id)) return;
|
||||
this.selected = this.value;
|
||||
@@ -170,10 +192,10 @@ export default class AddressAutoComplete extends Vue {
|
||||
this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`;
|
||||
}
|
||||
|
||||
updateSelected(option) {
|
||||
updateSelected(option: IAddress) {
|
||||
if (option == null) return;
|
||||
this.selected = option;
|
||||
this.$emit('input', this.selected);
|
||||
this.$emit("input", this.selected);
|
||||
}
|
||||
|
||||
resetPopup() {
|
||||
@@ -185,8 +207,8 @@ export default class AddressAutoComplete extends Vue {
|
||||
this.addressModalActive = true;
|
||||
}
|
||||
|
||||
async reverseGeoCode(e: LatLng, zoom: Number) {
|
||||
// If the position has been updated through autocomplete selection, no need to geocode it !
|
||||
async reverseGeoCode(e: LatLng, zoom: number) {
|
||||
// If the position has been updated through autocomplete selection, no need to geocode it!
|
||||
if (this.checkCurrentPosition(e)) return;
|
||||
const result = await this.$apollo.query({
|
||||
query: REVERSE_GEOCODE,
|
||||
@@ -198,74 +220,77 @@ export default class AddressAutoComplete extends Vue {
|
||||
},
|
||||
});
|
||||
|
||||
this.addressData = result.data.reverseGeocode.map(address => new Address(address));
|
||||
this.addressData = result.data.reverseGeocode.map((address: IAddress) => new Address(address));
|
||||
const defaultAddress = new Address(this.addressData[0]);
|
||||
this.selected = defaultAddress;
|
||||
this.$emit('input', this.selected);
|
||||
this.$emit("input", this.selected);
|
||||
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
|
||||
}
|
||||
|
||||
checkCurrentPosition(e: LatLng) {
|
||||
if (!this.selected || !this.selected.geom) return false;
|
||||
const lat = parseFloat(this.selected.geom.split(';')[1]);
|
||||
const lon = parseFloat(this.selected.geom.split(';')[0]);
|
||||
const lat = parseFloat(this.selected.geom.split(";")[1]);
|
||||
const lon = parseFloat(this.selected.geom.split(";")[0]);
|
||||
|
||||
return e.lat === lat && e.lng === lon;
|
||||
}
|
||||
|
||||
async locateMe(): Promise<void> {
|
||||
|
||||
this.gettingLocation = true;
|
||||
try {
|
||||
this.gettingLocation = false;
|
||||
this.location = await this.getLocation();
|
||||
this.location = await AddressAutoComplete.getLocation();
|
||||
this.mapDefaultZoom = 12;
|
||||
this.reverseGeoCode(new LatLng(this.location.coords.latitude, this.location.coords.longitude), 12);
|
||||
this.reverseGeoCode(
|
||||
new LatLng(this.location.coords.latitude, this.location.coords.longitude),
|
||||
12
|
||||
);
|
||||
} catch (e) {
|
||||
this.gettingLocation = false;
|
||||
this.gettingLocationError = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async getLocation(): Promise<Position> {
|
||||
static async getLocation(): Promise<Position> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!('geolocation' in navigator)) {
|
||||
reject(new Error('Geolocation is not available.'));
|
||||
if (!("geolocation" in navigator)) {
|
||||
reject(new Error("Geolocation is not available."));
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(pos => {
|
||||
resolve(pos);
|
||||
}, err => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
resolve(pos);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.address-autocomplete {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.address-autocomplete {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
.dropdown-menu {
|
||||
z-index: 2000;
|
||||
}
|
||||
.autocomplete {
|
||||
.dropdown-menu {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.dropdown-item.is-disabled {
|
||||
opacity: 1 !important;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
.dropdown-item.is-disabled {
|
||||
opacity: 1 !important;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.read-only {
|
||||
cursor: pointer;
|
||||
}
|
||||
.read-only {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
.map {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<time class="datetime-container" :datetime="dateObj.getUTCSeconds()">
|
||||
<span class="month">{{ month }}</span>
|
||||
<span class="day">{{ day }}</span>
|
||||
</time>
|
||||
<time class="datetime-container" :datetime="dateObj.getUTCSeconds()">
|
||||
<span class="month">{{ month }}</span>
|
||||
<span class="day">{{ day }}</span>
|
||||
</time>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class DateCalendarIcon extends Vue {
|
||||
@@ -32,44 +32,44 @@ export default class DateCalendarIcon extends Vue {
|
||||
}
|
||||
|
||||
get month() {
|
||||
return this.dateObj.toLocaleString(undefined, { month: 'short' });
|
||||
return this.dateObj.toLocaleString(undefined, { month: "short" });
|
||||
}
|
||||
|
||||
get day() {
|
||||
return this.dateObj.toLocaleString(undefined, { day: 'numeric' });
|
||||
return this.dateObj.toLocaleString(undefined, { day: "numeric" });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
time.datetime-container {
|
||||
background: #f6f7f8;
|
||||
border: 1px solid rgba(46,62,72,.12);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
/*height: 50px;*/
|
||||
width: 50px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
time.datetime-container {
|
||||
background: #f6f7f8;
|
||||
border: 1px solid rgba(46, 62, 72, 0.12);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
/*height: 50px;*/
|
||||
width: 50px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
span {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
|
||||
&.month {
|
||||
color: #fa3e3e;
|
||||
padding: 2px 0;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
&.month {
|
||||
color: #fa3e3e;
|
||||
padding: 2px 0;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.day {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
&.day {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,40 +12,41 @@
|
||||
```
|
||||
</docs>
|
||||
<template>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">{{ label }}</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow is-grouped calendar-picker">
|
||||
<b-datepicker
|
||||
:day-names="localeShortWeekDayNamesProxy"
|
||||
:month-names="localeMonthNamesProxy"
|
||||
:first-day-of-week="parseInt($t('firstDayOfWeek'), 10)"
|
||||
:min-date="minDatetime"
|
||||
:max-date="maxDatetime"
|
||||
v-model="dateWithTime"
|
||||
:placeholder="$t('Click to select')"
|
||||
:years-range="[-2,10]"
|
||||
icon="calendar"
|
||||
class="is-narrow"
|
||||
/>
|
||||
<b-timepicker
|
||||
placeholder="Type or select a time..."
|
||||
icon="clock"
|
||||
v-model="dateWithTime"
|
||||
:min-time="minDatetime"
|
||||
:max-time="maxDatetime"
|
||||
size="is-small"
|
||||
inline>
|
||||
</b-timepicker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">{{ label }}</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow is-grouped calendar-picker">
|
||||
<b-datepicker
|
||||
:day-names="localeShortWeekDayNamesProxy"
|
||||
:month-names="localeMonthNamesProxy"
|
||||
:first-day-of-week="parseInt($t('firstDayOfWeek'), 10)"
|
||||
:min-date="minDatetime"
|
||||
:max-date="maxDatetime"
|
||||
v-model="dateWithTime"
|
||||
:placeholder="$t('Click to select')"
|
||||
:years-range="[-2, 10]"
|
||||
icon="calendar"
|
||||
class="is-narrow"
|
||||
/>
|
||||
<b-timepicker
|
||||
placeholder="Type or select a time..."
|
||||
icon="clock"
|
||||
v-model="dateWithTime"
|
||||
:min-time="minDatetime"
|
||||
:max-time="maxDatetime"
|
||||
size="is-small"
|
||||
inline
|
||||
>
|
||||
</b-timepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { localeMonthNames, localeShortWeekDayNames } from '@/utils/datetime';
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import { localeMonthNames, localeShortWeekDayNames } from "@/utils/datetime";
|
||||
|
||||
@Component
|
||||
export default class DateTimePicker extends Vue {
|
||||
@@ -58,34 +59,35 @@ export default class DateTimePicker extends Vue {
|
||||
/**
|
||||
* What's shown besides the picker
|
||||
*/
|
||||
@Prop({ required: false, type: String, default: 'Datetime' }) label!: string;
|
||||
@Prop({ required: false, type: String, default: "Datetime" }) label!: string;
|
||||
|
||||
/**
|
||||
* The step for the time input
|
||||
*/
|
||||
@Prop({ required: false, type: Number, default: 1 }) step!: number;
|
||||
|
||||
/**
|
||||
* Earliest date available for selection
|
||||
*/
|
||||
/**
|
||||
* Earliest date available for selection
|
||||
*/
|
||||
@Prop({ required: false, type: Date, default: null }) minDatetime!: Date;
|
||||
|
||||
/**
|
||||
* Latest date available for selection
|
||||
*/
|
||||
/**
|
||||
* Latest date available for selection
|
||||
*/
|
||||
@Prop({ required: false, type: Date, default: null }) maxDatetime!: Date;
|
||||
|
||||
dateWithTime: Date = this.value;
|
||||
|
||||
localeShortWeekDayNamesProxy = localeShortWeekDayNames();
|
||||
|
||||
localeMonthNamesProxy = localeMonthNames();
|
||||
|
||||
@Watch('value')
|
||||
@Watch("value")
|
||||
updateValue() {
|
||||
this.dateWithTime = this.value;
|
||||
}
|
||||
|
||||
@Watch('dateWithTime')
|
||||
@Watch("dateWithTime")
|
||||
updateDateWithTimeWatcher() {
|
||||
this.updateDateTime();
|
||||
}
|
||||
@@ -96,21 +98,21 @@ export default class DateTimePicker extends Vue {
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
this.$emit('input', this.dateWithTime);
|
||||
this.$emit("input", this.dateWithTime);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.timepicker {
|
||||
/deep/ .dropdown-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.timepicker {
|
||||
/deep/ .dropdown-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-picker {
|
||||
/deep/ .dropdown-menu {
|
||||
z-index: 200;
|
||||
}
|
||||
}
|
||||
.calendar-picker {
|
||||
/deep/ .dropdown-menu {
|
||||
z-index: 200;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@ A simple card for an event
|
||||
|
||||
```vue
|
||||
<EventCard
|
||||
:event="{
|
||||
:event="{
|
||||
title: 'Vue Styleguidist first meetup: learn the basics!',
|
||||
beginsOn: new Date(),
|
||||
tags: [
|
||||
@@ -29,9 +29,16 @@ A simple card for an event
|
||||
<template>
|
||||
<router-link class="card" :to="{ name: 'Event', params: { uuid: event.uuid } }">
|
||||
<div class="card-image">
|
||||
<figure class="image is-16by9" :style="`background-image: url('${event.picture ? event.picture.url : '/img/mobilizon_default_card.png'}')`">
|
||||
<figure
|
||||
class="image is-16by9"
|
||||
:style="`background-image: url('${
|
||||
event.picture ? event.picture.url : '/img/mobilizon_default_card.png'
|
||||
}')`"
|
||||
>
|
||||
<div class="tag-container" v-if="event.tags">
|
||||
<b-tag v-for="tag in event.tags.slice(0, 3)" :key="tag.slug" type="is-light">{{ tag.title }}</b-tag>
|
||||
<b-tag v-for="tag in event.tags.slice(0, 3)" :key="tag.slug" type="is-light">{{
|
||||
tag.title
|
||||
}}</b-tag>
|
||||
</div>
|
||||
</figure>
|
||||
</div>
|
||||
@@ -43,57 +50,57 @@ A simple card for an event
|
||||
<div class="media-content">
|
||||
<p class="event-title">{{ event.title }}</p>
|
||||
<div class="event-subtitle" v-if="event.physicalAddress">
|
||||
<!-- <p>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</p>-->
|
||||
<!-- <p>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</p>-->
|
||||
<span>
|
||||
{{ event.physicalAddress.description }}, {{ event.physicalAddress.locality }}
|
||||
{{ event.physicalAddress.description }}, {{ event.physicalAddress.locality }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="date-and-title">-->
|
||||
<!-- <div class="date-component">-->
|
||||
<!-- <date-calendar-icon v-if="!mergedOptions.hideDate" :date="event.beginsOn" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="title-wrapper">-->
|
||||
<!-- <h4>{{ event.title }}</h4>-->
|
||||
<!-- <div class="organizer-place-wrapper has-text-grey">-->
|
||||
<!-- <span>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</span>-->
|
||||
<!-- ·-->
|
||||
<!-- <span v-if="event.physicalAddress">-->
|
||||
<!-- {{ event.physicalAddress.description }}, {{ event.physicalAddress.locality }}-->
|
||||
<!-- </span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-if="!mergedOptions.hideDetails" class="details">-->
|
||||
<!-- <div v-if="event.participants.length > 0 &&-->
|
||||
<!-- mergedOptions.loggedPerson &&-->
|
||||
<!-- event.participants[0].actor.id === mergedOptions.loggedPerson.id">-->
|
||||
<!-- <b-tag type="is-info"><translate>Organizer</translate></b-tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else-if="event.participants.length === 1">-->
|
||||
<!-- <translate-->
|
||||
<!-- :translate-params="{name: event.participants[0].actor.preferredUsername}"-->
|
||||
<!-- >{name} organizes this event</translate>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else>-->
|
||||
<!-- <span v-for="participant in event.participants" :key="participant.actor.uuid">-->
|
||||
<!-- {{ participant.actor.preferredUsername }}-->
|
||||
<!-- <span v-if="participant.role === ParticipantRole.CREATOR">(organizer)</span>,-->
|
||||
<!-- <!– <translate-->
|
||||
<!-- :translate-params="{name: participant.actor.preferredUsername}"-->
|
||||
<!-- > {name} is in,</translate>–>-->
|
||||
<!-- </span>-->
|
||||
<!-- </div>-->
|
||||
</router-link>
|
||||
<!-- <div class="date-and-title">-->
|
||||
<!-- <div class="date-component">-->
|
||||
<!-- <date-calendar-icon v-if="!mergedOptions.hideDate" :date="event.beginsOn" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="title-wrapper">-->
|
||||
<!-- <h4>{{ event.title }}</h4>-->
|
||||
<!-- <div class="organizer-place-wrapper has-text-grey">-->
|
||||
<!-- <span>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</span>-->
|
||||
<!-- ·-->
|
||||
<!-- <span v-if="event.physicalAddress">-->
|
||||
<!-- {{ event.physicalAddress.description }}, {{ event.physicalAddress.locality }}-->
|
||||
<!-- </span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-if="!mergedOptions.hideDetails" class="details">-->
|
||||
<!-- <div v-if="event.participants.length > 0 &&-->
|
||||
<!-- mergedOptions.loggedPerson &&-->
|
||||
<!-- event.participants[0].actor.id === mergedOptions.loggedPerson.id">-->
|
||||
<!-- <b-tag type="is-info"><translate>Organizer</translate></b-tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else-if="event.participants.length === 1">-->
|
||||
<!-- <translate-->
|
||||
<!-- :translate-params="{name: event.participants[0].actor.preferredUsername}"-->
|
||||
<!-- >{name} organizes this event</translate>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else>-->
|
||||
<!-- <span v-for="participant in event.participants" :key="participant.actor.uuid">-->
|
||||
<!-- {{ participant.actor.preferredUsername }}-->
|
||||
<!-- <span v-if="participant.role === ParticipantRole.CREATOR">(organizer)</span>,-->
|
||||
<!-- <!– <translate-->
|
||||
<!-- :translate-params="{name: participant.actor.preferredUsername}"-->
|
||||
<!-- > {name} is in,</translate>–>-->
|
||||
<!-- </span>-->
|
||||
<!-- </div>-->
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IEvent, IEventCardOptions, ParticipantRole } from '@/types/event.model';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
|
||||
import { Actor, Person } from '@/types/actor';
|
||||
import { IEvent, IEventCardOptions, ParticipantRole } from "@/types/event.model";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import { Actor, Person } from "@/types/actor";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@@ -102,6 +109,7 @@ import { Actor, Person } from '@/types/actor';
|
||||
})
|
||||
export default class EventCard extends Vue {
|
||||
@Prop({ required: true }) event!: IEvent;
|
||||
|
||||
@Prop({ required: false }) options!: IEventCardOptions;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
@@ -118,102 +126,103 @@ export default class EventCard extends Vue {
|
||||
}
|
||||
|
||||
get actor(): Actor {
|
||||
return Object.assign(new Person(), this.event.organizerActor || this.mergedOptions.organizerActor);
|
||||
return Object.assign(
|
||||
new Person(),
|
||||
this.event.organizerActor || this.mergedOptions.organizerActor
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../variables";
|
||||
@import "../../variables";
|
||||
|
||||
a.card {
|
||||
display: block;
|
||||
background: $secondary;
|
||||
|
||||
&:hover {
|
||||
// box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
|
||||
transform: scale(1.01, 1.01);
|
||||
&:after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
a.card {
|
||||
display: block;
|
||||
background: $secondary;
|
||||
|
||||
&:hover {
|
||||
// box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
|
||||
transform: scale(1.01, 1.01);
|
||||
&:after {
|
||||
content: "";
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
div.tag-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0;
|
||||
margin-right: -3px;
|
||||
z-index: 10;
|
||||
max-width: 40%;
|
||||
|
||||
span.tag {
|
||||
margin: 5px auto;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
div.card-image {
|
||||
background: $secondary;
|
||||
|
||||
figure.image {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 0.5rem;
|
||||
|
||||
.event-title {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
min-height: 2.4rem;
|
||||
}
|
||||
|
||||
.event-subtitle {
|
||||
font-size: 0.85rem;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
span {
|
||||
width: 15rem;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
div.tag-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0;
|
||||
margin-right: -3px;
|
||||
z-index: 10;
|
||||
max-width: 40%;
|
||||
|
||||
span.tag {
|
||||
margin: 5px auto;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
div.card-image {
|
||||
background: $secondary;
|
||||
|
||||
figure.image {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 0.5rem;
|
||||
|
||||
.event-title {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
min-height: 2.4rem;
|
||||
}
|
||||
|
||||
.event-subtitle {
|
||||
font-size: 0.85rem;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
span {
|
||||
width: 15rem;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,55 +18,87 @@
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<span v-if="!endsOn">{{ beginsOn | formatDateTimeString(showStartTime) }}</span>
|
||||
<span v-else-if="isSameDay() && showStartTime && showEndTime">
|
||||
{{ $t('On {date} from {startTime} to {endTime}', {date: formatDate(beginsOn), startTime: formatTime(beginsOn), endTime: formatTime(endsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="isSameDay() && !showStartTime && showEndTime">
|
||||
{{ $t('On {date} ending at {endTime}', {date: formatDate(beginsOn), endTime: formatTime(endsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="isSameDay() && showStartTime && !showEndTime">
|
||||
{{ $t('On {date} starting at {startTime}', {date: formatDate(beginsOn), startTime: formatTime(beginsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="isSameDay()">
|
||||
{{ $t('On {date}', {date: formatDate(beginsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="endsOn && showStartTime && showEndTime">
|
||||
{{ $t('From the {startDate} at {startTime} to the {endDate} at {endTime}',
|
||||
{startDate: formatDate(beginsOn), startTime: formatTime(beginsOn), endDate: formatDate(endsOn), endTime: formatTime(endsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="endsOn && showStartTime">
|
||||
{{ $t('From the {startDate} at {startTime} to the {endDate}',
|
||||
{startDate: formatDate(beginsOn), startTime: formatTime(beginsOn), endDate: formatDate(endsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="endsOn">
|
||||
{{ $t('From the {startDate} to the {endDate}',
|
||||
{startDate: formatDate(beginsOn), endDate: formatDate(endsOn)}) }}
|
||||
</span>
|
||||
<span v-if="!endsOn">{{ beginsOn | formatDateTimeString(showStartTime) }}</span>
|
||||
<span v-else-if="isSameDay() && showStartTime && showEndTime">
|
||||
{{
|
||||
$t("On {date} from {startTime} to {endTime}", {
|
||||
date: formatDate(beginsOn),
|
||||
startTime: formatTime(beginsOn),
|
||||
endTime: formatTime(endsOn),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="isSameDay() && !showStartTime && showEndTime">
|
||||
{{
|
||||
$t("On {date} ending at {endTime}", {
|
||||
date: formatDate(beginsOn),
|
||||
endTime: formatTime(endsOn),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="isSameDay() && showStartTime && !showEndTime">
|
||||
{{
|
||||
$t("On {date} starting at {startTime}", {
|
||||
date: formatDate(beginsOn),
|
||||
startTime: formatTime(beginsOn),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="isSameDay()">{{ $t("On {date}", { date: formatDate(beginsOn) }) }}</span>
|
||||
<span v-else-if="endsOn && showStartTime && showEndTime">
|
||||
{{
|
||||
$t("From the {startDate} at {startTime} to the {endDate} at {endTime}", {
|
||||
startDate: formatDate(beginsOn),
|
||||
startTime: formatTime(beginsOn),
|
||||
endDate: formatDate(endsOn),
|
||||
endTime: formatTime(endsOn),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="endsOn && showStartTime">
|
||||
{{
|
||||
$t("From the {startDate} at {startTime} to the {endDate}", {
|
||||
startDate: formatDate(beginsOn),
|
||||
startTime: formatTime(beginsOn),
|
||||
endDate: formatDate(endsOn),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="endsOn">
|
||||
{{
|
||||
$t("From the {startDate} to the {endDate}", {
|
||||
startDate: formatDate(beginsOn),
|
||||
endDate: formatDate(endsOn),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class EventFullDate extends Vue {
|
||||
@Prop({ required: true }) beginsOn!: string;
|
||||
|
||||
@Prop({ required: false }) endsOn!: string;
|
||||
|
||||
@Prop({ required: false, default: true }) showStartTime!: boolean;
|
||||
|
||||
@Prop({ required: false, default: true }) showEndTime!: boolean;
|
||||
|
||||
formatDate(value) {
|
||||
if (!this.$options.filters) return;
|
||||
formatDate(value: Date): string | undefined {
|
||||
if (!this.$options.filters) return undefined;
|
||||
return this.$options.filters.formatDateString(value);
|
||||
}
|
||||
|
||||
formatTime(value) {
|
||||
if (!this.$options.filters) return;
|
||||
formatTime(value: Date): string | undefined {
|
||||
if (!this.$options.filters) return undefined;
|
||||
return this.$options.filters.formatTimeString(value);
|
||||
}
|
||||
|
||||
isSameDay() {
|
||||
const sameDay = ((new Date(this.beginsOn)).toDateString()) === ((new Date(this.endsOn)).toDateString());
|
||||
return this.endsOn && sameDay;
|
||||
isSameDay(): boolean {
|
||||
const sameDay = new Date(this.beginsOn).toDateString() === new Date(this.endsOn).toDateString();
|
||||
return this.endsOn !== undefined && sameDay;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,55 +1,3 @@
|
||||
<docs>
|
||||
A simple card for a participation (we should rename it)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<EventListCard
|
||||
:participation="participation"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
participation: {
|
||||
event: {
|
||||
title: 'Vue Styleguidist first meetup: learn the basics!',
|
||||
id: 5,
|
||||
uuid: 'some uuid',
|
||||
beginsOn: new Date(),
|
||||
organizerActor: {
|
||||
preferredUsername: 'tcit',
|
||||
name: 'Some Random Dude',
|
||||
domain: null,
|
||||
id: 4,
|
||||
displayName() { return 'Some random dude' }
|
||||
},
|
||||
options: {
|
||||
maximumAttendeeCapacity: 4
|
||||
},
|
||||
participantStats: {
|
||||
approved: 1,
|
||||
notApproved: 2
|
||||
}
|
||||
},
|
||||
actor: {
|
||||
preferredUsername: 'tcit',
|
||||
name: 'Some Random Dude',
|
||||
domain: null,
|
||||
id: 4,
|
||||
displayName() { return 'Some random dude' }
|
||||
},
|
||||
role: 'CREATOR',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<article class="box">
|
||||
<div class="columns">
|
||||
@@ -58,37 +6,72 @@ export default {
|
||||
<div class="date-component">
|
||||
<date-calendar-icon :date="participation.event.beginsOn" />
|
||||
</div>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }"><h2 class="title">{{participation.event.title }}</h2></router-link>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }">
|
||||
<h2 class="title">{{ participation.event.title }}</h2>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="participation-actor has-text-grey">
|
||||
<span v-if="participation.event.physicalAddress && participation.event.physicalAddress.locality">{{ participation.event.physicalAddress.locality }} - </span>
|
||||
<span
|
||||
v-if="
|
||||
participation.event.physicalAddress && participation.event.physicalAddress.locality
|
||||
"
|
||||
>{{ participation.event.physicalAddress.locality }} -</span
|
||||
>
|
||||
<span>
|
||||
<span>{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span>
|
||||
<span v-if="participation.role === ParticipantRole.PARTICIPANT">{{ $t('Going as {name}', { name: participation.actor.displayName() }) }}</span>
|
||||
<span>
|
||||
{{
|
||||
$t("Organized by {name}", {
|
||||
name: participation.event.organizerActor.displayName(),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-if="participation.role === ParticipantRole.PARTICIPANT">
|
||||
{{ $t("Going as {name}", { name: participation.actor.displayName() }) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<span class="column is-narrow">
|
||||
<b-icon icon="earth" v-if="participation.event.visibility === EventVisibility.PUBLIC" />
|
||||
<b-icon icon="lock-open" v-if="participation.event.visibility === EventVisibility.UNLISTED" />
|
||||
<b-icon
|
||||
icon="lock-open"
|
||||
v-if="participation.event.visibility === EventVisibility.UNLISTED"
|
||||
/>
|
||||
<b-icon icon="lock" v-if="participation.event.visibility === EventVisibility.PRIVATE" />
|
||||
</span>
|
||||
<span class="column is-narrow participant-stats">
|
||||
<span v-if="participation.event.options.maximumAttendeeCapacity !== 0">
|
||||
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.participant, total: participation.event.options.maximumAttendeeCapacity }) }}
|
||||
<!-- <b-progress-->
|
||||
<!-- v-if="participation.event.options.maximumAttendeeCapacity > 0"-->
|
||||
<!-- size="is-medium"-->
|
||||
<!-- :value="participation.event.participantStats.participant * 100 / participation.event.options.maximumAttendeeCapacity">-->
|
||||
<!-- </b-progress>-->
|
||||
{{
|
||||
$t("{approved} / {total} seats", {
|
||||
approved: participation.event.participantStats.participant,
|
||||
total: participation.event.options.maximumAttendeeCapacity,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $tc('{count} participants', participation.event.participantStats.participant, { count: participation.event.participantStats.participant })}}
|
||||
{{
|
||||
$tc("{count} participants", participation.event.participantStats.participant, {
|
||||
count: participation.event.participantStats.participant,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="participation.event.participantStats.notApproved > 0">
|
||||
<b-button type="is-text" @click="gotToWithCheck(participation, { name: RouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } })">
|
||||
{{ $tc('{count} requests waiting', participation.event.participantStats.notApproved, { count: participation.event.participantStats.notApproved })}}
|
||||
<span v-if="participation.event.participantStats.notApproved > 0">
|
||||
<b-button
|
||||
type="is-text"
|
||||
@click="
|
||||
gotToWithCheck(participation, {
|
||||
name: RouteName.PARTICIPATIONS,
|
||||
params: { eventId: participation.event.uuid },
|
||||
})
|
||||
"
|
||||
>
|
||||
{{
|
||||
$tc(
|
||||
"{count} requests waiting",
|
||||
participation.event.participantStats.notApproved,
|
||||
{ count: participation.event.participantStats.notApproved }
|
||||
)
|
||||
}}
|
||||
</b-button>
|
||||
</span>
|
||||
</span>
|
||||
@@ -96,56 +79,86 @@ export default {
|
||||
</div>
|
||||
<div class="actions column is-narrow">
|
||||
<ul>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<li
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
"
|
||||
>
|
||||
<b-button
|
||||
type="is-text"
|
||||
@click="gotToWithCheck(participation, { name: RouteName.EDIT_EVENT, params: { eventId: participation.event.uuid } })"
|
||||
icon-left="pencil"
|
||||
type="is-text"
|
||||
@click="
|
||||
gotToWithCheck(participation, {
|
||||
name: RouteName.EDIT_EVENT,
|
||||
params: { eventId: participation.event.uuid },
|
||||
})
|
||||
"
|
||||
icon-left="pencil"
|
||||
>{{ $t("Edit") }}</b-button
|
||||
>
|
||||
{{ $t('Edit') }}
|
||||
</b-button>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))" @click="openDeleteEventModalWrapper">
|
||||
<b-button type="is-text" icon-left="delete">
|
||||
{{ $t('Delete') }}
|
||||
</b-button>
|
||||
<li
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
"
|
||||
@click="openDeleteEventModalWrapper"
|
||||
>
|
||||
<b-button type="is-text" icon-left="delete">{{ $t("Delete") }}</b-button>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<li
|
||||
v-if="
|
||||
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
|
||||
participation.role
|
||||
)
|
||||
"
|
||||
>
|
||||
<b-button
|
||||
type="is-text"
|
||||
@click="gotToWithCheck(participation, { name: RouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } })"
|
||||
icon-left="account-multiple-plus"
|
||||
type="is-text"
|
||||
@click="
|
||||
gotToWithCheck(participation, {
|
||||
name: RouteName.PARTICIPATIONS,
|
||||
params: { eventId: participation.event.uuid },
|
||||
})
|
||||
"
|
||||
icon-left="account-multiple-plus"
|
||||
>{{ $t("Manage participations") }}</b-button
|
||||
>
|
||||
{{ $t('Manage participations') }}
|
||||
</b-button>
|
||||
</li>
|
||||
<li>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
icon-left="view-compact"
|
||||
type="is-text"
|
||||
:to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }">
|
||||
{{ $t('View event page') }}
|
||||
</b-button>
|
||||
:to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }"
|
||||
>{{ $t("View event page") }}</b-button
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IParticipant, ParticipantRole, EventVisibility, IEventCardOptions } from '@/types/event.model';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { mixins } from 'vue-class-component';
|
||||
import ActorMixin from '@/mixins/actor';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import EventMixin from '@/mixins/event';
|
||||
import { RouteName } from '@/router';
|
||||
import { changeIdentity } from '@/utils/auth';
|
||||
import { Route } from 'vue-router';
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { RawLocation } from "vue-router";
|
||||
import {
|
||||
IParticipant,
|
||||
ParticipantRole,
|
||||
EventVisibility,
|
||||
IEventCardOptions,
|
||||
} from "../../types/event.model";
|
||||
import { IPerson } from "../../types/actor";
|
||||
import ActorMixin from "../../mixins/actor";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||
import EventMixin from "../../mixins/event";
|
||||
import RouteName from "../../router/name";
|
||||
import { changeIdentity } from "../../utils/auth";
|
||||
|
||||
const defaultOptions: IEventCardOptions = {
|
||||
hideDate: true,
|
||||
@@ -169,15 +182,19 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
|
||||
* The participation associated
|
||||
*/
|
||||
@Prop({ required: true }) participation!: IParticipant;
|
||||
|
||||
/**
|
||||
* Options are merged with default options
|
||||
*/
|
||||
@Prop({ required: false, default: () => defaultOptions }) options!: IEventCardOptions;
|
||||
@Prop({ required: false, default: () => defaultOptions })
|
||||
options!: IEventCardOptions;
|
||||
|
||||
currentActor!: IPerson;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
EventVisibility = EventVisibility;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
get mergedOptions(): IEventCardOptions {
|
||||
@@ -191,114 +208,116 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
|
||||
await this.openDeleteEventModal(this.participation.event, this.currentActor);
|
||||
}
|
||||
|
||||
async gotToWithCheck(participation: IParticipant, route: Route) {
|
||||
async gotToWithCheck(participation: IParticipant, route: RawLocation) {
|
||||
if (participation.actor.id !== this.currentActor.id && participation.event.organizerActor) {
|
||||
const organizer = participation.event.organizerActor as IPerson;
|
||||
await changeIdentity(this.$apollo.provider.defaultClient, organizer);
|
||||
this.$buefy.notification.open({
|
||||
message: this.$t('Current identity has been changed to {identityName} in order to manage this event.', {
|
||||
identityName: organizer.preferredUsername,
|
||||
}) as string,
|
||||
type: 'is-info',
|
||||
position: 'is-bottom-right',
|
||||
message: this.$t(
|
||||
"Current identity has been changed to {identityName} in order to manage this event.",
|
||||
{
|
||||
identityName: organizer.preferredUsername,
|
||||
}
|
||||
) as string,
|
||||
type: "is-info",
|
||||
position: "is-bottom-right",
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
return await this.$router.push(route);
|
||||
return this.$router.push(route);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../variables";
|
||||
@import "../../variables";
|
||||
|
||||
article.box {
|
||||
div.tag-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0;
|
||||
margin-right: -5px;
|
||||
z-index: 10;
|
||||
max-width: 40%;
|
||||
article.box {
|
||||
div.tag-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0;
|
||||
margin-right: -5px;
|
||||
z-index: 10;
|
||||
max-width: 40%;
|
||||
|
||||
span.tag {
|
||||
margin: 5px auto;
|
||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
|
||||
/*word-break: break-all;*/
|
||||
text-overflow: ellipsis;
|
||||
span.tag {
|
||||
margin: 5px auto;
|
||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 1);
|
||||
/*word-break: break-all;*/
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
/*text-align: right;*/
|
||||
font-size: 1em;
|
||||
/*padding: 0 1px;*/
|
||||
line-height: 1.75em;
|
||||
}
|
||||
}
|
||||
div.content {
|
||||
padding: 5px;
|
||||
|
||||
.participation-actor span,
|
||||
.participant-stats span {
|
||||
padding: 0 5px;
|
||||
|
||||
button {
|
||||
height: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div.date-component {
|
||||
flex: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
/*text-align: right;*/
|
||||
font-size: 1em;
|
||||
/*padding: 0 1px;*/
|
||||
line-height: 1.75em;
|
||||
}
|
||||
}
|
||||
div.content {
|
||||
padding: 5px;
|
||||
|
||||
.participation-actor span, .participant-stats span {
|
||||
padding: 0 5px;
|
||||
|
||||
button {
|
||||
height: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div.date-component {
|
||||
flex: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
font-size: 1.6em;
|
||||
padding-bottom: 5px;
|
||||
margin: auto 0;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ progress + .progress-value {
|
||||
color: lighten($primary, 20%) !important;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
font-size: 1.6em;
|
||||
padding-bottom: 5px;
|
||||
margin: auto 0;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
ul li {
|
||||
margin: 0 auto;
|
||||
.is-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button.is-text {
|
||||
text-decoration: none;
|
||||
|
||||
/deep/ span:first-child i.mdi::before {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
/deep/ span:last-child {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
font-size: 0.8rem;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
/deep/ progress + .progress-value {
|
||||
color: lighten($primary, 20%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
ul li {
|
||||
margin: 0 auto;
|
||||
.is-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button.is-text {
|
||||
text-decoration: none;
|
||||
|
||||
/deep/ span:first-child i.mdi::before {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
/deep/ span:last-child {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
font-size: 0.8rem;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,48 +1,3 @@
|
||||
<docs>
|
||||
A simple card for an event
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<EventListViewCard
|
||||
:event="event"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
event: {
|
||||
title: 'Vue Styleguidist first meetup: learn the basics!',
|
||||
id: 5,
|
||||
uuid: 'some uuid',
|
||||
beginsOn: new Date(),
|
||||
organizerActor: {
|
||||
preferredUsername: 'tcit',
|
||||
name: 'Some Random Dude',
|
||||
domain: null,
|
||||
id: 4,
|
||||
displayName() {
|
||||
return 'Some random dude'
|
||||
}
|
||||
},
|
||||
options: {
|
||||
maximumAttendeeCapacity: 4
|
||||
},
|
||||
participantStats: {
|
||||
approved: 1,
|
||||
notApproved: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<article class="box">
|
||||
<div class="columns">
|
||||
@@ -51,12 +6,18 @@ export default {
|
||||
<div class="date-component">
|
||||
<date-calendar-icon :date="event.beginsOn" />
|
||||
</div>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"><h2 class="title">{{ event.title }}</h2></router-link>
|
||||
<router-link :to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }">
|
||||
<h2 class="title">{{ event.title }}</h2>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="participation-actor has-text-grey">
|
||||
<span v-if="event.physicalAddress && event.physicalAddress.locality">{{ event.physicalAddress.locality }}</span>
|
||||
<span v-if="event.physicalAddress && event.physicalAddress.locality">
|
||||
{{ event.physicalAddress.locality }}
|
||||
</span>
|
||||
<span>
|
||||
<span>{{ $t('Organized by {name}', { name: event.organizerActor.displayName() } ) }}</span>
|
||||
<span>
|
||||
{{ $t("Organized by {name}", { name: event.organizerActor.displayName() }) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@@ -67,30 +28,44 @@ export default {
|
||||
</span>
|
||||
<span class="column is-narrow participant-stats">
|
||||
<span v-if="event.options.maximumAttendeeCapacity !== 0">
|
||||
{{ $t('{approved} / {total} seats', {approved: event.participantStats.participant, total: event.options.maximumAttendeeCapacity }) }}
|
||||
{{
|
||||
$t("{approved} / {total} seats", {
|
||||
approved: event.participantStats.participant,
|
||||
total: event.options.maximumAttendeeCapacity,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $tc('{count} participants', event.participantStats.participant, { count: event.participantStats.participant })}}
|
||||
{{
|
||||
$tc("{count} participants", event.participantStats.participant, {
|
||||
count: event.participantStats.participant,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IParticipant, ParticipantRole, EventVisibility, IEventCardOptions } from '@/types/event.model';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { mixins } from 'vue-class-component';
|
||||
import ActorMixin from '@/mixins/actor';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import EventMixin from '@/mixins/event';
|
||||
import { RouteName } from '@/router';
|
||||
import { changeIdentity } from '@/utils/auth';
|
||||
import { Route } from 'vue-router';
|
||||
import {
|
||||
IParticipant,
|
||||
ParticipantRole,
|
||||
EventVisibility,
|
||||
IEventCardOptions,
|
||||
} from "@/types/event.model";
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import { IPerson } from "@/types/actor";
|
||||
import { mixins } from "vue-class-component";
|
||||
import ActorMixin from "@/mixins/actor";
|
||||
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import EventMixin from "@/mixins/event";
|
||||
import { changeIdentity } from "@/utils/auth";
|
||||
import { Route } from "vue-router";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
const defaultOptions: IEventCardOptions = {
|
||||
hideDate: true,
|
||||
@@ -114,58 +89,61 @@ export default class EventListViewCard extends mixins(ActorMixin, EventMixin) {
|
||||
* The participation associated
|
||||
*/
|
||||
@Prop({ required: true }) event!: IParticipant;
|
||||
|
||||
/**
|
||||
* Options are merged with default options
|
||||
*/
|
||||
@Prop({ required: false, default: () => defaultOptions }) options!: IEventCardOptions;
|
||||
@Prop({ required: false, default: () => defaultOptions })
|
||||
options!: IEventCardOptions;
|
||||
|
||||
currentActor!: IPerson;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
EventVisibility = EventVisibility;
|
||||
RouteName = RouteName;
|
||||
|
||||
EventVisibility = EventVisibility;
|
||||
|
||||
RouteName = RouteName;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../variables";
|
||||
@import "../../variables";
|
||||
|
||||
article.box {
|
||||
div.content {
|
||||
padding: 5px;
|
||||
article.box {
|
||||
div.content {
|
||||
padding: 5px;
|
||||
|
||||
.participation-actor span, .participant-stats span {
|
||||
padding: 0 5px;
|
||||
.participation-actor span,
|
||||
.participant-stats span {
|
||||
padding: 0 5px;
|
||||
|
||||
button {
|
||||
height: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
button {
|
||||
height: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div.date-component {
|
||||
flex: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
div.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div.date-component {
|
||||
flex: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
font-size: 1.6em;
|
||||
padding-bottom: 5px;
|
||||
margin: auto 0;
|
||||
}
|
||||
.title {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
font-size: 1.6em;
|
||||
padding-bottom: 5px;
|
||||
margin: auto 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
42
js/src/components/Event/EventMetadataBlock.vue
Normal file
42
js/src/components/Event/EventMetadataBlock.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>{{ title }}</h2>
|
||||
<div class="eventMetadataBlock">
|
||||
<b-icon v-if="icon" :icon="icon" size="is-medium" />
|
||||
<p :class="{ 'padding-left': icon }">
|
||||
<slot></slot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class EventMetadataBlock extends Vue {
|
||||
@Prop({ required: false, type: String }) icon!: string;
|
||||
|
||||
@Prop({ required: true, type: String }) title!: string;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 500;
|
||||
color: #f7ba30;
|
||||
}
|
||||
|
||||
div.eventMetadataBlock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.75rem;
|
||||
|
||||
p {
|
||||
flex: 1;
|
||||
|
||||
&.padding-left {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
55
js/src/components/Event/EventMinimalistCard.vue
Normal file
55
js/src/components/Event/EventMinimalistCard.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<router-link
|
||||
class="event-minimalist-card-wrapper"
|
||||
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
|
||||
>
|
||||
<date-calendar-icon class="calendar-icon" :date="event.beginsOn" />
|
||||
<div class="title-info-wrapper">
|
||||
<p class="event-minimalist-title">{{ event.title }}</p>
|
||||
<p v-if="event.physicalAddress" class="has-text-grey">
|
||||
{{ event.physicalAddress.description }}
|
||||
</p>
|
||||
<p v-else>3 demandes de participation à traiter</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IEvent } from "@/types/event.model";
|
||||
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
DateCalendarIcon,
|
||||
},
|
||||
})
|
||||
export default class EventMinimalistCard extends Vue {
|
||||
@Prop({ required: true, type: Object }) event!: IEvent;
|
||||
|
||||
RouteName = RouteName;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.event-minimalist-card-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
color: initial;
|
||||
align-items: flex-start;
|
||||
|
||||
.calendar-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.title-info-wrapper {
|
||||
flex: 2;
|
||||
|
||||
.event-minimalist-title {
|
||||
color: #3c376e;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial, serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -24,86 +24,131 @@ A button to set your participation
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<div class="participation-button">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" v-if="participation && participation.role === ParticipantRole.PARTICIPANT">
|
||||
<button class="button is-success is-large" type="button" slot="trigger">
|
||||
<b-icon icon="check" />
|
||||
<template>
|
||||
<span>{{ $t('I participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
<div class="participation-button">
|
||||
<b-dropdown
|
||||
aria-role="list"
|
||||
position="is-bottom-left"
|
||||
v-if="participation && participation.role === ParticipantRole.PARTICIPANT"
|
||||
>
|
||||
<button class="button is-success is-large" type="button" slot="trigger">
|
||||
<b-icon icon="check" />
|
||||
<template>
|
||||
<span>{{ $t("I participate") }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave" class="has-text-danger">
|
||||
{{ $t('Cancel my participation…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<b-dropdown-item
|
||||
:value="false"
|
||||
aria-role="listitem"
|
||||
@click="confirmLeave"
|
||||
class="has-text-danger"
|
||||
>{{ $t("Cancel my participation…") }}</b-dropdown-item
|
||||
>
|
||||
</b-dropdown>
|
||||
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.NOT_APPROVED">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" class="dropdown-disabled">
|
||||
<button class="button is-success is-large" type="button" slot="trigger">
|
||||
<b-icon icon="timer-sand-empty" />
|
||||
<template>
|
||||
<span>{{ $t('I participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.NOT_APPROVED">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" class="dropdown-disabled">
|
||||
<button class="button is-success is-large" type="button" slot="trigger">
|
||||
<b-icon icon="timer-sand-empty" />
|
||||
<template>
|
||||
<span>{{ $t("I participate") }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<!-- <b-dropdown-item :value="false" aria-role="listitem">-->
|
||||
<!-- {{ $t('Change my identity…')}}-->
|
||||
<!-- </b-dropdown-item>-->
|
||||
<!-- <b-dropdown-item :value="false" aria-role="listitem">-->
|
||||
<!-- {{ $t('Change my identity…')}}-->
|
||||
<!-- </b-dropdown-item>-->
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave" class="has-text-danger">
|
||||
{{ $t('Cancel my participation request…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<small>{{ $t('Participation requested!')}}</small><br />
|
||||
<small>{{ $t('Waiting for organization team approval.')}}</small>
|
||||
</div>
|
||||
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.REJECTED">
|
||||
<span>{{ $t('Unfortunately, your participation request was rejected by the organizers.')}}</span>
|
||||
</div>
|
||||
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" v-else-if="!participation && currentActor.id">
|
||||
<button class="button is-primary is-large" type="button" slot="trigger">
|
||||
<template>
|
||||
<span>{{ $t('Participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<b-dropdown-item :value="true" aria-role="listitem" @click="joinEvent(currentActor)">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32" v-if="currentActor.avatar">
|
||||
<img class="is-rounded" :src="currentActor.avatar.url" alt="" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span>{{ $t('as {identity}', {identity: currentActor.name || `@${currentActor.preferredUsername}` }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="joinModal" v-if="identities.length > 1">
|
||||
{{ $t('with another identity…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<b-button tag="router-link" :to="{ name: RouteName.EVENT_PARTICIPATE_LOGGED_OUT, params: { uuid: event.uuid } }" v-else-if="!participation && hasAnonymousParticipationMethods" type="is-primary" size="is-large" native-type="button">{{ $t('Participate') }}</b-button>
|
||||
<b-button tag="router-link" :to="{ name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT, params: { uuid: event.uuid } }" v-else-if="!currentActor.id" type="is-primary" size="is-large" native-type="button">{{ $t('Participate') }}</b-button>
|
||||
<b-dropdown-item
|
||||
:value="false"
|
||||
aria-role="listitem"
|
||||
@click="confirmLeave"
|
||||
class="has-text-danger"
|
||||
>{{ $t("Cancel my participation request…") }}</b-dropdown-item
|
||||
>
|
||||
</b-dropdown>
|
||||
<small>{{ $t("Participation requested!") }}</small>
|
||||
<br />
|
||||
<small>{{ $t("Waiting for organization team approval.") }}</small>
|
||||
</div>
|
||||
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.REJECTED">
|
||||
<span>
|
||||
{{ $t("Unfortunately, your participation request was rejected by the organizers.") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<b-dropdown
|
||||
aria-role="list"
|
||||
position="is-bottom-left"
|
||||
v-else-if="!participation && currentActor.id"
|
||||
>
|
||||
<button class="button is-primary is-large" type="button" slot="trigger">
|
||||
<template>
|
||||
<span>{{ $t("Participate") }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down" />
|
||||
</button>
|
||||
|
||||
<b-dropdown-item :value="true" aria-role="listitem" @click="joinEvent(currentActor)">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32" v-if="currentActor.avatar">
|
||||
<img class="is-rounded" :src="currentActor.avatar.url" alt />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span>
|
||||
{{
|
||||
$t("as {identity}", {
|
||||
identity: currentActor.name || `@${currentActor.preferredUsername}`,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
:value="false"
|
||||
aria-role="listitem"
|
||||
@click="joinModal"
|
||||
v-if="identities.length > 1"
|
||||
>{{ $t("with another identity…") }}</b-dropdown-item
|
||||
>
|
||||
</b-dropdown>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.EVENT_PARTICIPATE_LOGGED_OUT, params: { uuid: event.uuid } }"
|
||||
v-else-if="!participation && hasAnonymousParticipationMethods"
|
||||
type="is-primary"
|
||||
size="is-large"
|
||||
native-type="button"
|
||||
>{{ $t("Participate") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT, params: { uuid: event.uuid } }"
|
||||
v-else-if="!currentActor.id"
|
||||
type="is-primary"
|
||||
size="is-large"
|
||||
native-type="button"
|
||||
>{{ $t("Participate") }}</b-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { EventJoinOptions, IEvent, IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
import { CURRENT_ACTOR_CLIENT, IDENTITIES } from '@/graphql/actor';
|
||||
import { CURRENT_USER_CLIENT } from '@/graphql/user';
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
import { RouteName } from '@/router';
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { EventJoinOptions, IEvent, IParticipant, ParticipantRole } from "../../types/event.model";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
import { CURRENT_ACTOR_CLIENT, IDENTITIES } from "../../graphql/actor";
|
||||
import { CURRENT_USER_CLIENT } from "../../graphql/user";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
import { IConfig } from "../../types/config.model";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@@ -114,7 +159,8 @@ import { RouteName } from '@/router';
|
||||
config: CONFIG,
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
update: ({ identities }) => identities ? identities.map(identity => new Person(identity)) : [],
|
||||
update: ({ identities }) =>
|
||||
identities ? identities.map((identity: IPerson) => new Person(identity)) : [],
|
||||
skip() {
|
||||
return this.currentUser.isLoggedIn === false;
|
||||
},
|
||||
@@ -123,28 +169,33 @@ import { RouteName } from '@/router';
|
||||
})
|
||||
export default class ParticipationButton extends Vue {
|
||||
@Prop({ required: true }) participation!: IParticipant;
|
||||
|
||||
@Prop({ required: true }) event!: IEvent;
|
||||
|
||||
@Prop({ required: true }) currentActor!: IPerson;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
identities: IPerson[] = [];
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
joinEvent(actor: IPerson) {
|
||||
if (this.event.joinOptions === EventJoinOptions.RESTRICTED) {
|
||||
this.$emit('joinEventWithConfirmation', actor);
|
||||
this.$emit("joinEventWithConfirmation", actor);
|
||||
} else {
|
||||
this.$emit('joinEvent', actor);
|
||||
this.$emit("joinEvent", actor);
|
||||
}
|
||||
}
|
||||
|
||||
joinModal() {
|
||||
this.$emit('joinModal');
|
||||
this.$emit("joinModal");
|
||||
}
|
||||
|
||||
confirmLeave() {
|
||||
this.$emit('confirmLeave');
|
||||
this.$emit("confirmLeave");
|
||||
}
|
||||
|
||||
get hasAnonymousParticipationMethods(): boolean {
|
||||
@@ -154,20 +205,20 @@ export default class ParticipationButton extends Vue {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.participation-button {
|
||||
.dropdown {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.participation-button {
|
||||
.dropdown {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
&.dropdown-disabled button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
&.dropdown-disabled button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.anonymousParticipationModal {
|
||||
/deep/ .animation-content {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
.anonymousParticipationModal {
|
||||
/deep/ .animation-content {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,123 +1,164 @@
|
||||
<template>
|
||||
<b-table
|
||||
:data="data"
|
||||
ref="queueTable"
|
||||
detailed
|
||||
detail-key="id"
|
||||
:checked-rows.sync="checkedRows"
|
||||
checkable
|
||||
:is-row-checkable="row => row.role !== ParticipantRole.CREATOR"
|
||||
checkbox-position="left"
|
||||
default-sort="insertedAt"
|
||||
default-sort-direction="asc"
|
||||
:show-detail-icon="false"
|
||||
:loading="this.$apollo.loading"
|
||||
paginated
|
||||
backend-pagination
|
||||
:aria-next-label="$t('Next page')"
|
||||
:aria-previous-label="$t('Previous page')"
|
||||
:aria-page-label="$t('Page')"
|
||||
:aria-current-label="$t('Current page')"
|
||||
:total="total"
|
||||
:per-page="perPage"
|
||||
backend-sorting
|
||||
:default-sort-direction="'desc'"
|
||||
:default-sort="['insertedAt', 'desc']"
|
||||
@page-change="page => $emit('page-change', page)"
|
||||
@sort="(field, order) => $emit('sort', field, order)"
|
||||
>
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="insertedAt" :label="$t('Date')" sortable>
|
||||
<b-tag type="is-success" class="has-text-centered">{{ props.row.insertedAt | formatDateString }}<br>{{ props.row.insertedAt | formatTimeString }}</b-tag>
|
||||
</b-table-column>
|
||||
<b-table-column field="role" :label="$t('Role')" sortable v-if="showRole">
|
||||
<span v-if="props.row.role === ParticipantRole.CREATOR">
|
||||
{{ $t('Organizer') }}
|
||||
</span>
|
||||
<span v-else-if="props.row.role === ParticipantRole.PARTICIPANT">
|
||||
{{ $t('Participant') }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
<b-table-column field="actor.preferredUsername" :label="$t('Participant')" sortable>
|
||||
<article class="media">
|
||||
<figure class="media-left" v-if="props.row.actor.avatar">
|
||||
<p class="image is-48x48">
|
||||
<img :src="props.row.actor.avatar.url" alt="">
|
||||
</p>
|
||||
</figure>
|
||||
<b-icon class="media-left" v-else-if="props.row.actor.preferredUsername === 'anonymous'" size="is-large" icon="incognito" />
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<span v-if="props.row.actor.preferredUsername !== 'anonymous'">
|
||||
<span v-if="props.row.actor.name">{{ props.row.actor.name }}</span><br />
|
||||
<span class="is-size-7 has-text-grey">@{{ props.row.actor.preferredUsername }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t('Anonymous participant') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</b-table-column>
|
||||
<b-table-column field="metadata.message" :label="$t('Message')">
|
||||
<span @click="toggleQueueDetails(props.row)" :class="{ 'ellipsed-message': props.row.metadata.message.length > MESSAGE_ELLIPSIS_LENGTH }" v-if="props.row.metadata && props.row.metadata.message">
|
||||
{{ props.row.metadata.message | ellipsize }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey">
|
||||
{{ $t('No message') }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
</template>
|
||||
<template slot="detail" slot-scope="props">
|
||||
<article v-html="nl2br(props.row.metadata.message)" />
|
||||
</template>
|
||||
<template slot="bottom-left" v-if="checkedRows.length > 0">
|
||||
<div class="buttons">
|
||||
<b-button @click="acceptParticipants(checkedRows)" type="is-success" v-if="canAcceptParticipants">
|
||||
{{ $tc('No participant to approve|Approve participant|Approve {number} participants', checkedRows.length, { number: checkedRows.length }) }}
|
||||
</b-button>
|
||||
<b-button @click="refuseParticipants(checkedRows)" type="is-danger" v-if="canRefuseParticipants">
|
||||
{{ $tc('No participant to reject|Reject participant|Reject {number} participants', checkedRows.length, { number: checkedRows.length }) }}
|
||||
</b-button>
|
||||
<b-table
|
||||
:data="data"
|
||||
ref="queueTable"
|
||||
detailed
|
||||
detail-key="id"
|
||||
:checked-rows.sync="checkedRows"
|
||||
checkable
|
||||
:is-row-checkable="(row) => row.role !== ParticipantRole.CREATOR"
|
||||
checkbox-position="left"
|
||||
:show-detail-icon="false"
|
||||
:loading="this.$apollo.loading"
|
||||
paginated
|
||||
backend-pagination
|
||||
:aria-next-label="$t('Next page')"
|
||||
:aria-previous-label="$t('Previous page')"
|
||||
:aria-page-label="$t('Page')"
|
||||
:aria-current-label="$t('Current page')"
|
||||
:total="total"
|
||||
:per-page="perPage"
|
||||
backend-sorting
|
||||
:default-sort-direction="'desc'"
|
||||
:default-sort="['insertedAt', 'desc']"
|
||||
@page-change="(page) => $emit('page-change', page)"
|
||||
@sort="(field, order) => $emit('sort', field, order)"
|
||||
>
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="insertedAt" :label="$t('Date')" sortable>
|
||||
<b-tag type="is-success" class="has-text-centered"
|
||||
>{{ props.row.insertedAt | formatDateString }}<br />{{
|
||||
props.row.insertedAt | formatTimeString
|
||||
}}</b-tag
|
||||
>
|
||||
</b-table-column>
|
||||
<b-table-column field="role" :label="$t('Role')" sortable v-if="showRole">
|
||||
<span v-if="props.row.role === ParticipantRole.CREATOR">
|
||||
{{ $t("Organizer") }}
|
||||
</span>
|
||||
<span v-else-if="props.row.role === ParticipantRole.PARTICIPANT">
|
||||
{{ $t("Participant") }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
<b-table-column field="actor.preferredUsername" :label="$t('Participant')" sortable>
|
||||
<article class="media">
|
||||
<figure class="media-left" v-if="props.row.actor.avatar">
|
||||
<p class="image is-48x48">
|
||||
<img :src="props.row.actor.avatar.url" alt="" />
|
||||
</p>
|
||||
</figure>
|
||||
<b-icon
|
||||
class="media-left"
|
||||
v-else-if="props.row.actor.preferredUsername === 'anonymous'"
|
||||
size="is-large"
|
||||
icon="incognito"
|
||||
/>
|
||||
<b-icon class="media-left" v-else size="is-large" icon="account-circle" />
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<span v-if="props.row.actor.preferredUsername !== 'anonymous'">
|
||||
<span v-if="props.row.actor.name">{{ props.row.actor.name }}</span
|
||||
><br />
|
||||
<span class="is-size-7 has-text-grey"
|
||||
>@{{ props.row.actor.preferredUsername }}</span
|
||||
>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t("Anonymous participant") }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</b-table>
|
||||
</div>
|
||||
</article>
|
||||
</b-table-column>
|
||||
<b-table-column field="metadata.message" :label="$t('Message')">
|
||||
<span
|
||||
@click="toggleQueueDetails(props.row)"
|
||||
:class="{
|
||||
'ellipsed-message': props.row.metadata.message.length > MESSAGE_ELLIPSIS_LENGTH,
|
||||
}"
|
||||
v-if="props.row.metadata && props.row.metadata.message"
|
||||
>
|
||||
{{ props.row.metadata.message | ellipsize }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey">
|
||||
{{ $t("No message") }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
</template>
|
||||
<template slot="detail" slot-scope="props">
|
||||
<article v-html="nl2br(props.row.metadata.message)" />
|
||||
</template>
|
||||
<template slot="bottom-left" v-if="checkedRows.length > 0">
|
||||
<div class="buttons">
|
||||
<b-button
|
||||
@click="acceptParticipants(checkedRows)"
|
||||
type="is-success"
|
||||
v-if="canAcceptParticipants"
|
||||
>
|
||||
{{
|
||||
$tc(
|
||||
"No participant to approve|Approve participant|Approve {number} participants",
|
||||
checkedRows.length,
|
||||
{ number: checkedRows.length }
|
||||
)
|
||||
}}
|
||||
</b-button>
|
||||
<b-button
|
||||
@click="refuseParticipants(checkedRows)"
|
||||
type="is-danger"
|
||||
v-if="canRefuseParticipants"
|
||||
>
|
||||
{{
|
||||
$tc(
|
||||
"No participant to reject|Reject participant|Reject {number} participants",
|
||||
checkedRows.length,
|
||||
{ number: checkedRows.length }
|
||||
)
|
||||
}}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
</b-table>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { Refs } from '@/shims-vue';
|
||||
import { nl2br } from '@/utils/html';
|
||||
import { asyncForEach } from '@/utils/asyncForEach';
|
||||
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
|
||||
import { IParticipant, ParticipantRole } from "../../types/event.model";
|
||||
import { nl2br } from "../../utils/html";
|
||||
import { asyncForEach } from "../../utils/asyncForEach";
|
||||
|
||||
const MESSAGE_ELLIPSIS_LENGTH = 130;
|
||||
|
||||
@Component({
|
||||
filters: {
|
||||
ellipsize: (text?: string) => text && text.substr(0, MESSAGE_ELLIPSIS_LENGTH).concat('…'),
|
||||
ellipsize: (text?: string) => text && text.substr(0, MESSAGE_ELLIPSIS_LENGTH).concat("…"),
|
||||
},
|
||||
})
|
||||
export default class ParticipationTable extends Vue {
|
||||
@Prop({ required: true, type: Array }) data!: IParticipant[];
|
||||
|
||||
@Prop({ required: true, type: Number }) total!: number;
|
||||
@Prop({ required: true, type: Function }) acceptParticipant;
|
||||
@Prop({ required: true, type: Function }) refuseParticipant;
|
||||
@Prop({ required: false, type: Boolean, default: false }) showRole;
|
||||
@Prop({ required: false, type: Number, default: 20 }) perPage;
|
||||
|
||||
@Prop({ required: true, type: Function }) acceptParticipant!: Function;
|
||||
|
||||
@Prop({ required: true, type: Function }) refuseParticipant!: Function;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) showRole!: boolean;
|
||||
|
||||
@Prop({ required: false, type: Number, default: 20 }) perPage!: number;
|
||||
|
||||
@Ref("queueTable") readonly queueTable!: any;
|
||||
|
||||
checkedRows: IParticipant[] = [];
|
||||
|
||||
MESSAGE_ELLIPSIS_LENGTH = MESSAGE_ELLIPSIS_LENGTH;
|
||||
nl2br = nl2br;
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
$refs!: Refs<{
|
||||
queueTable: any,
|
||||
}>;
|
||||
nl2br = nl2br;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
toggleQueueDetails(row: IParticipant) {
|
||||
if (row.metadata.message && row.metadata.message.length < MESSAGE_ELLIPSIS_LENGTH) return;
|
||||
this.$refs.queueTable.toggleDetails(row);
|
||||
this.queueTable.toggleDetails(row);
|
||||
}
|
||||
|
||||
async acceptParticipants(participants: IParticipant[]) {
|
||||
@@ -134,31 +175,33 @@ export default class ParticipationTable extends Vue {
|
||||
this.checkedRows = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* We can accept participants if at least one of them is not approved
|
||||
*/
|
||||
/**
|
||||
* We can accept participants if at least one of them is not approved
|
||||
*/
|
||||
get canAcceptParticipants(): boolean {
|
||||
return this.checkedRows.some(
|
||||
(participant: IParticipant) => [ParticipantRole.NOT_APPROVED, ParticipantRole.REJECTED].includes(participant.role),
|
||||
return this.checkedRows.some((participant: IParticipant) =>
|
||||
[ParticipantRole.NOT_APPROVED, ParticipantRole.REJECTED].includes(participant.role)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We can refuse participants if at least one of them is something different than not approved
|
||||
*/
|
||||
/**
|
||||
* We can refuse participants if at least one of them is something different than not approved
|
||||
*/
|
||||
get canRefuseParticipants(): boolean {
|
||||
return this.checkedRows.some((participant: IParticipant) => participant.role !== ParticipantRole.REJECTED);
|
||||
return this.checkedRows.some(
|
||||
(participant: IParticipant) => participant.role !== ParticipantRole.REJECTED
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ellipsed-message {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ellipsed-message {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table {
|
||||
span.tag {
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
.table {
|
||||
span.tag {
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,56 +1,33 @@
|
||||
<docs>
|
||||
### Tag input
|
||||
A special input to manage event tags
|
||||
|
||||
```vue
|
||||
<tag-input :value="[{ title: 'toto' }]" path="title" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<tag-input v-model="tags" :data="sourceTags" path="title" />
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sourceTags: [{ title: 'my tag'}, { title: 'my second tag' }, { title: 'another example'}],
|
||||
tags: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<b-field>
|
||||
<template slot="label">
|
||||
{{ $t('Add some tags') }}
|
||||
<b-tooltip type="is-dark" :label="$t('You can add tags by hitting the Enter key or by adding a comma')">
|
||||
<b-icon size="is-small" icon="help-circle-outline"></b-icon>
|
||||
</b-tooltip>
|
||||
</template>
|
||||
<b-taginput
|
||||
v-model="tagsStrings"
|
||||
:data="filteredTags"
|
||||
autocomplete
|
||||
:allow-new="true"
|
||||
:field="path"
|
||||
icon="label"
|
||||
maxlength="20"
|
||||
maxtags="10"
|
||||
:placeholder="$t('Eg: Stockholm, Dance, Chess…')"
|
||||
@typing="getFilteredTags"
|
||||
>
|
||||
</b-taginput>
|
||||
</b-field>
|
||||
<b-field>
|
||||
<template slot="label">
|
||||
{{ $t("Add some tags") }}
|
||||
<b-tooltip
|
||||
type="is-dark"
|
||||
:label="$t('You can add tags by hitting the Enter key or by adding a comma')"
|
||||
>
|
||||
<b-icon size="is-small" icon="help-circle-outline"></b-icon>
|
||||
</b-tooltip>
|
||||
</template>
|
||||
<b-taginput
|
||||
v-model="tagsStrings"
|
||||
:data="filteredTags"
|
||||
autocomplete
|
||||
:allow-new="true"
|
||||
:field="path"
|
||||
icon="label"
|
||||
maxlength="20"
|
||||
maxtags="10"
|
||||
:placeholder="$t('Eg: Stockholm, Dance, Chess…')"
|
||||
@typing="getFilteredTags"
|
||||
>
|
||||
</b-taginput>
|
||||
</b-field>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { get, differenceBy } from 'lodash';
|
||||
import { ITag } from '@/types/tag.model';
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { get, differenceBy } from "lodash";
|
||||
import { ITag } from "../../types/tag.model";
|
||||
|
||||
@Component({
|
||||
computed: {
|
||||
@@ -59,32 +36,30 @@ import { ITag } from '@/types/tag.model';
|
||||
return this.$props.value.map((tag: ITag) => tag.title);
|
||||
},
|
||||
set(tagStrings) {
|
||||
const tagEntities = tagStrings.map((tag) => {
|
||||
if (TagInput.isTag(tag)) {
|
||||
const tagEntities = tagStrings.map((tag: string | ITag) => {
|
||||
if (!(tag instanceof String)) {
|
||||
return tag;
|
||||
}
|
||||
return { title: tag, slug: tag } as ITag;
|
||||
});
|
||||
this.$emit('input', tagEntities);
|
||||
this.$emit("input", tagEntities);
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class TagInput extends Vue {
|
||||
|
||||
@Prop({ required: false, default: () => [] }) data!: ITag[];
|
||||
@Prop({ required: true, default: 'value' }) path!: string;
|
||||
|
||||
@Prop({ required: true, default: "value" }) path!: string;
|
||||
|
||||
@Prop({ required: true }) value!: ITag[];
|
||||
|
||||
filteredTags: ITag[] = [];
|
||||
|
||||
getFilteredTags(text) {
|
||||
this.filteredTags = differenceBy(this.data, this.value, 'id').filter((option) => {
|
||||
return get(option, this.path)
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(text.toLowerCase()) >= 0;
|
||||
});
|
||||
getFilteredTags(text: string) {
|
||||
this.filteredTags = differenceBy(this.data, this.value, "id").filter(
|
||||
(option) => get(option, this.path).toString().toLowerCase().indexOf(text.toLowerCase()) >= 0
|
||||
);
|
||||
}
|
||||
|
||||
static isTag(x: any): x is ITag {
|
||||
|
||||
Reference in New Issue
Block a user