add calendar in dashboard
This commit is contained in:
parent
f180f5a531
commit
46a555a038
|
@ -1,111 +1,9 @@
|
|||
import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js'
|
||||
|
||||
const Sortable = require('sortablejs').Sortable
|
||||
const Routing = require('../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js')
|
||||
const routes = require('../../public/js/fos_js_routes.json')
|
||||
|
||||
Routing.setRoutingData(routes)
|
||||
|
||||
class AddressAutocomplete {
|
||||
constructor () {
|
||||
this.fields = {
|
||||
address: document.querySelector('#establishment_address'),
|
||||
zipCode: document.querySelector('#establishment_zipCode'),
|
||||
city: document.querySelector('#establishment_city')
|
||||
}
|
||||
|
||||
this.results = []
|
||||
this.timer = null
|
||||
|
||||
if (!this.fields.address) {
|
||||
return
|
||||
}
|
||||
|
||||
const that = this
|
||||
|
||||
this.fields.address.addEventListener('keyup', () => {
|
||||
if (that.timer) {
|
||||
clearTimeout(that.timer)
|
||||
}
|
||||
|
||||
that.timer = setTimeout(() => {
|
||||
const query = that.fields.address.value
|
||||
|
||||
fetch(Routing.generate('admin_establishment_address', {
|
||||
q: query
|
||||
}))
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
that.results = data.features
|
||||
that.renderResults()
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
renderResults () {
|
||||
const address = this.fields.address
|
||||
const that = this
|
||||
|
||||
let wrapper = address.nextSibling
|
||||
|
||||
if (wrapper) {
|
||||
wrapper.remove()
|
||||
}
|
||||
|
||||
if (this.results.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
wrapper = document.createElement('div')
|
||||
wrapper.classList.add('list-group', 'mt-1', 'mb-1')
|
||||
|
||||
for (const address of this.results) {
|
||||
const item = document.createElement('div')
|
||||
item.classList.add('list-group-item', 'btn', 'text-left')
|
||||
item.textContent = address.properties.label
|
||||
|
||||
item.setAttribute('data-name', address.properties.name)
|
||||
item.setAttribute('data-zipCode', address.properties.postcode)
|
||||
item.setAttribute('data-city', address.properties.city)
|
||||
|
||||
item.addEventListener('click', (e) => {
|
||||
that.fields.address.value = e.target.getAttribute('data-name')
|
||||
that.fields.zipCode.value = e.target.getAttribute('data-zipCode')
|
||||
that.fields.city.value = e.target.getAttribute('data-city')
|
||||
|
||||
wrapper.remove()
|
||||
})
|
||||
|
||||
wrapper.appendChild(item)
|
||||
}
|
||||
|
||||
address.parentNode.appendChild(wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
class FilesCollectionSorter {
|
||||
constructor () {
|
||||
const collections = document.querySelectorAll('div[data-collection^="collection-files"]')
|
||||
|
||||
for (const collection of collections) {
|
||||
return new Sortable(collection, {
|
||||
handle: '*[data-collection-item]',
|
||||
sort: true,
|
||||
animation: 150,
|
||||
fallbackTolerance: 3,
|
||||
onEnd: (e) => {
|
||||
const positions = collection.querySelectorAll('*[data-collection-item] input[type=hidden]')
|
||||
console.log(positions);
|
||||
|
||||
for (let u = 0; u < positions.length; u++) {
|
||||
positions[u].value = u
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const AddressAutocomplete = require('./modules/address.js')
|
||||
const FilesCollectionSorter = require('./modules/collection-sorter.js')
|
||||
const Calendar = require('./modules/calendar.js')
|
||||
|
||||
new AddressAutocomplete()
|
||||
new FilesCollectionSorter()
|
||||
new Calendar()
|
||||
|
|
150
assets/js/components/Calendar.vue
Normal file
150
assets/js/components/Calendar.vue
Normal file
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="pr-4">
|
||||
<label for="projects">Période</label>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<select class="form-control mr-2" v-model="month">
|
||||
<option v-for="value, key in months" v-bind:value="key">{{ value }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<select class="form-control ml-2" v-model="year">
|
||||
<option v-for="value, key in years" v-bind:value="key">{{ value }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="pr-2">
|
||||
<label for="projects">Intervenants</label>
|
||||
<select class="form-control" v-model="selectedSpeakers" id="speakers" multiple>
|
||||
<option v-for="item in speakers" v-bind:value="item.id">{{ item.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="projects">Projets</label>
|
||||
<select class="form-control" v-model="selectedProjects" id="projects" multiple>
|
||||
<option v-for="item in projects" v-bind:value="item.id">{{ item.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="weeks.length > 0">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-bordered mt-3">
|
||||
<Week v-for="week, key in weeks" v-bind:key="key" v-bind:week="week" />
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Routing from '../../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js'
|
||||
|
||||
const Week = require('./Week').default
|
||||
const Choices = require('choices.js')
|
||||
const chunk = require('chunk')
|
||||
const axios = require('axios').default
|
||||
const routes = require('../../../public/js/fos_js_routes.json')
|
||||
|
||||
Routing.setRoutingData(routes)
|
||||
|
||||
export default {
|
||||
name: 'Calendar',
|
||||
components: {
|
||||
Week
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
projects: [],
|
||||
speakers: [],
|
||||
month: new Date().getMonth() + 1,
|
||||
year: new Date().getFullYear(),
|
||||
years: {},
|
||||
weeks: [],
|
||||
months: {
|
||||
1: 'Janvier',
|
||||
2: 'Février',
|
||||
3: 'Mars',
|
||||
4: 'Avril',
|
||||
5: 'Mai',
|
||||
6: 'Juin',
|
||||
7: 'Juillet',
|
||||
8: 'Août',
|
||||
9: 'Septembre',
|
||||
10: 'Octobre',
|
||||
11: 'Novembre',
|
||||
12: 'Décembre'
|
||||
},
|
||||
selectedProjects: [],
|
||||
selectedSpeakers: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh () {
|
||||
const that = this
|
||||
|
||||
axios.get(Routing.generate('admin_calendar_month', {
|
||||
month: this.month,
|
||||
year: this.year,
|
||||
projects: this.selectedProjects,
|
||||
speakers: this.selectedSpeakers
|
||||
}))
|
||||
.then((response) => {
|
||||
that.weeks = chunk(response.data, 7)
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const that = this
|
||||
|
||||
for (let year = 2020; year <= (new Date().getFullYear() + 3); year++) {
|
||||
this.years[year] = year
|
||||
}
|
||||
|
||||
axios.get(Routing.generate('admin_calendar_projects'))
|
||||
.then((response) => {
|
||||
that.projects = response.data
|
||||
that.selectedProjects = response.data.map(item => item.id)
|
||||
|
||||
window.setTimeout(e => new Choices(document.getElementById('projects')), 500)
|
||||
})
|
||||
|
||||
axios.get(Routing.generate('admin_calendar_speakers'))
|
||||
.then((response) => {
|
||||
that.speakers = response.data
|
||||
that.selectedSpeakers = response.data.map(item => item.id)
|
||||
|
||||
window.setTimeout(e => new Choices(document.getElementById('speakers')), 500)
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
year (year) {
|
||||
this.refresh()
|
||||
},
|
||||
month (month) {
|
||||
this.refresh()
|
||||
},
|
||||
selectedProjects (selectedProjects) {
|
||||
this.refresh()
|
||||
},
|
||||
selectedSpeakers (selectedSpeakers) {
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
95
assets/js/components/Week.vue
Normal file
95
assets/js/components/Week.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th v-for="item in week" v-bind:class="{'text-right': true, 'text-muted': !item.currentMonth}">
|
||||
{{ item.day }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td v-for="item in week" v-bind:class="{'bg-light': !item.currentMonth}">
|
||||
<div v-for="event in item.events">
|
||||
<details v-if="event.description">
|
||||
<summary>
|
||||
<span class="font-weight-bold">
|
||||
<span class="text-muted">
|
||||
[{{ event.startAt }}]
|
||||
</span>
|
||||
{{ event.summary }}
|
||||
</span>
|
||||
</summary>
|
||||
|
||||
<p v-html="event.description.replace(/\n/g, '<br>')" class="mt-3"></p>
|
||||
|
||||
<div class="mt-3">
|
||||
<a v-bind:href="route('admin_speaker_show', {entity: speaker.id})" v-for="speaker in event.speakers" v-bind:class="['d-block mr-1 mt-1 btn btn-xs', 'btn-' + speaker.color].join(' ')">
|
||||
{{ speaker.name }}
|
||||
</a>
|
||||
|
||||
<a v-bind:href="route('admin_project_show', {entity: project.id})" v-for="project in event.projects" class="d-block mr-1 mt-1 btn btn-xs border btn-light">
|
||||
{{ project.label }}
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
<div v-else>
|
||||
<span class="font-weight-bold">{{ event.summary }}</span>
|
||||
|
||||
<div class="mt-3">
|
||||
<a v-bind:href="route('admin_speaker_show', {entity: speaker.id})" v-for="speaker in event.speakers" v-bind:class="['d-block mr-1 mt-1 btn btn-xd', 'btn-' + speaker.color].join(' ')">
|
||||
{{ speaker.name }}
|
||||
</a>
|
||||
|
||||
<a v-bind:href="route('admin_project_show', {entity: project.id})" v-for="project in event.projects" class="d-block mr-1 mt-1 btn btn-xs border btn-light">
|
||||
{{ project.label }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
height: calc((100vh - 190px) / 8);
|
||||
width: calc(100% / 7);
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
font-size: 12px;
|
||||
padding: 3px 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Routing from '../../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js'
|
||||
import { Fragment } from 'vue-fragment'
|
||||
|
||||
const routes = require('../../../public/js/fos_js_routes.json')
|
||||
|
||||
Routing.setRoutingData(routes)
|
||||
|
||||
export default {
|
||||
name: 'Week',
|
||||
components: {
|
||||
Fragment
|
||||
},
|
||||
props: {
|
||||
week: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
route(route, params) {
|
||||
return Routing.generate(route, params)
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
84
assets/js/modules/address.js
Normal file
84
assets/js/modules/address.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
const Routing = require('../../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js')
|
||||
const routes = require('../../../public/js/fos_js_routes.json')
|
||||
|
||||
Routing.setRoutingData(routes)
|
||||
|
||||
class AddressAutocomplete {
|
||||
constructor () {
|
||||
this.fields = {
|
||||
address: document.querySelector('#establishment_address'),
|
||||
zipCode: document.querySelector('#establishment_zipCode'),
|
||||
city: document.querySelector('#establishment_city')
|
||||
}
|
||||
|
||||
this.results = []
|
||||
this.timer = null
|
||||
|
||||
if (!this.fields.address) {
|
||||
return
|
||||
}
|
||||
|
||||
const that = this
|
||||
|
||||
this.fields.address.addEventListener('keyup', () => {
|
||||
if (that.timer) {
|
||||
clearTimeout(that.timer)
|
||||
}
|
||||
|
||||
that.timer = setTimeout(() => {
|
||||
const query = that.fields.address.value
|
||||
|
||||
fetch(Routing.generate('admin_establishment_address', {
|
||||
q: query
|
||||
}))
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
that.results = data.features
|
||||
that.renderResults()
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
renderResults () {
|
||||
const address = this.fields.address
|
||||
const that = this
|
||||
|
||||
let wrapper = address.nextSibling
|
||||
|
||||
if (wrapper) {
|
||||
wrapper.remove()
|
||||
}
|
||||
|
||||
if (this.results.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
wrapper = document.createElement('div')
|
||||
wrapper.classList.add('list-group', 'mt-1', 'mb-1')
|
||||
|
||||
for (const address of this.results) {
|
||||
const item = document.createElement('div')
|
||||
item.classList.add('list-group-item', 'btn', 'text-left')
|
||||
item.textContent = address.properties.label
|
||||
|
||||
item.setAttribute('data-name', address.properties.name)
|
||||
item.setAttribute('data-zipCode', address.properties.postcode)
|
||||
item.setAttribute('data-city', address.properties.city)
|
||||
|
||||
item.addEventListener('click', (e) => {
|
||||
that.fields.address.value = e.target.getAttribute('data-name')
|
||||
that.fields.zipCode.value = e.target.getAttribute('data-zipCode')
|
||||
that.fields.city.value = e.target.getAttribute('data-city')
|
||||
|
||||
wrapper.remove()
|
||||
})
|
||||
|
||||
wrapper.appendChild(item)
|
||||
}
|
||||
|
||||
address.parentNode.appendChild(wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AddressAutocomplete
|
17
assets/js/modules/calendar.js
Normal file
17
assets/js/modules/calendar.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const Vue = require('vue').default
|
||||
|
||||
const Calendar = require('../components/Calendar').default
|
||||
|
||||
module.exports = () => {
|
||||
if (!document.getElementById('calendar')) {
|
||||
return
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el: '#calendar',
|
||||
template: '<Calendar />',
|
||||
components: {
|
||||
Calendar
|
||||
}
|
||||
})
|
||||
}
|
26
assets/js/modules/collection-sorter.js
Normal file
26
assets/js/modules/collection-sorter.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const Sortable = require('sortablejs').Sortable
|
||||
|
||||
class FilesCollectionSorter {
|
||||
constructor () {
|
||||
const collections = document.querySelectorAll('div[data-collection^="collection-files"]')
|
||||
|
||||
for (const collection of collections) {
|
||||
return new Sortable(collection, {
|
||||
handle: '*[data-collection-item]',
|
||||
sort: true,
|
||||
animation: 150,
|
||||
fallbackTolerance: 3,
|
||||
onEnd: (e) => {
|
||||
const positions = collection.querySelectorAll('*[data-collection-item] input[type=hidden]')
|
||||
console.log(positions)
|
||||
|
||||
for (let u = 0; u < positions.length; u++) {
|
||||
positions[u].value = u
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FilesCollectionSorter
|
|
@ -8,6 +8,8 @@
|
|||
"build": "encore production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"murph-project": "^1"
|
||||
"chunk": "^0.0.3",
|
||||
"murph-project": "^1",
|
||||
"vue-fragment": "^1.5.2"
|
||||
}
|
||||
}
|
||||
|
|
123
src/Controller/CalendarAdminController.php
Normal file
123
src/Controller/CalendarAdminController.php
Normal file
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\EventRepositoryQuery;
|
||||
use App\Repository\ProjectRepositoryQuery;
|
||||
use App\Repository\SpeakerRepositoryQuery;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class CalendarAdminController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/calendar/month', name: 'admin_calendar_month', options: ['expose' => true])]
|
||||
public function month(
|
||||
Request $request,
|
||||
SpeakerRepositoryQuery $speakerQuery,
|
||||
ProjectRepositoryQuery $projectQuery,
|
||||
EventRepositoryQuery $eventQuery
|
||||
): Response {
|
||||
$projects = $projectQuery->create()->withIds($request->query->get('projects'))->find();
|
||||
$speakers = $speakerQuery->create()->withIds($request->query->get('speakers'))->find();
|
||||
|
||||
$month = $request->query->get('month', date('m'));
|
||||
$year = $request->query->get('year', date('Y'));
|
||||
|
||||
$dateFrom = new \DateTime(sprintf('%d-%d-1', $year, $month));
|
||||
|
||||
$currentMonthFirstDayOfWeek = $dateFrom->format('N');
|
||||
$nextMonthFirstDayOfWeek = date('N', $dateFrom->getTimestamp() + 3600 * 24 * $dateFrom->format('t'));
|
||||
$currentMonthDays = $dateFrom->format('t');
|
||||
$prevMonthDays = date('t', $dateFrom->getTimestamp() - 3600);
|
||||
|
||||
$days = [];
|
||||
|
||||
if ($currentMonthFirstDayOfWeek > 1) {
|
||||
for ($day = $prevMonthDays - $currentMonthFirstDayOfWeek + 2; $day <= $prevMonthDays; ++$day) {
|
||||
$days[] = [
|
||||
'day' => $day,
|
||||
'currentMonth' => false,
|
||||
'events' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
for ($day = 1; $day <= $currentMonthDays; ++$day) {
|
||||
$events = $eventQuery->create()
|
||||
->ofProjects($projects)
|
||||
->ofSpeakers($speakers)
|
||||
->ofMonth($month)
|
||||
->ofYear($year)
|
||||
->ofDay($day)
|
||||
->find()
|
||||
;
|
||||
|
||||
$events = array_map(function ($event) {
|
||||
return [
|
||||
'uid' => $event->getUid(),
|
||||
'summary' => $event->getSummary(),
|
||||
'description' => $event->getCleanedDescription(),
|
||||
'startAt' => $event->getStartAt() ? $event->getStartAt()->format('H:i') : null,
|
||||
'finishAt' => $event->getFinishAt() ? $event->getFinishAt()->format('H:i') : null,
|
||||
'projects' => array_map(fn ($i) => [
|
||||
'id' => $i->getId(),
|
||||
'label' => $i->getLabel()
|
||||
], $event->getProjects()->toArray()),
|
||||
'speakers' => array_map(fn ($i) => [
|
||||
'id' => $i->getId(),
|
||||
'name' => $i->getName(),
|
||||
'color' => $i->getColor(),
|
||||
], $event->getSpeakers()->toArray()),
|
||||
];
|
||||
}, $events);
|
||||
|
||||
$days[] = [
|
||||
'day' => $day,
|
||||
'currentMonth' => true,
|
||||
'events' => $events,
|
||||
];
|
||||
}
|
||||
|
||||
if ($nextMonthFirstDayOfWeek > 1) {
|
||||
$day = 0;
|
||||
|
||||
for ($u = $nextMonthFirstDayOfWeek; $u <= 7; ++$u) {
|
||||
$days[] = [
|
||||
'day' => ++$day,
|
||||
'currentMonth' => false,
|
||||
'events' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json($days);
|
||||
}
|
||||
|
||||
#[Route('/admin/calendar/projects', name: 'admin_calendar_projects', options: ['expose' => true])]
|
||||
public function projects(ProjectRepositoryQuery $query): Response
|
||||
{
|
||||
$entities = array_map(function ($entity) {
|
||||
return [
|
||||
'id' => $entity->getId(),
|
||||
'label' => $entity->getLabel(),
|
||||
];
|
||||
}, $query->create()->orderBy('.label')->find());
|
||||
|
||||
return $this->json($entities);
|
||||
}
|
||||
|
||||
#[Route('/admin/calendar/speakers', name: 'admin_calendar_speakers', options: ['expose' => true])]
|
||||
public function speakers(SpeakerRepositoryQuery $query): Response
|
||||
{
|
||||
$entities = array_map(function ($entity) {
|
||||
return [
|
||||
'id' => $entity->getId(),
|
||||
'name' => $entity->getName(),
|
||||
];
|
||||
}, $query->create()->orderBy('.name')->find());
|
||||
|
||||
return $this->json($entities);
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ class ProjectAdminController extends CrudController
|
|||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/show/{entity}", name="admin_project_show", methods={"GET"})
|
||||
* @Route("/admin/project/show/{entity}", name="admin_project_show", methods={"GET"}, options={"expose":true})
|
||||
*/
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@ class SpeakerAdminController extends CrudController
|
|||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/speaker/show/{entity}", name="admin_speaker_show", methods={"GET"})
|
||||
* @Route("/admin/speaker/show/{entity}", name="admin_speaker_show", methods={"GET"}, options={"expose":true})
|
||||
*/
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
|
|
|
@ -45,20 +45,20 @@ class Event implements EntityInterface
|
|||
*/
|
||||
protected $finishAt;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Project::class, inversedBy="events", cascade={"persist", "remove"})
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
protected $projects;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=Speaker::class, inversedBy="events")
|
||||
*/
|
||||
private $speakers;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=Project::class, inversedBy="events")
|
||||
*/
|
||||
private $projects;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->speakers = new ArrayCollection();
|
||||
$this->projects = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
|
@ -131,18 +131,6 @@ class Event implements EntityInterface
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getProjects(): ?Project
|
||||
{
|
||||
return $this->projects;
|
||||
}
|
||||
|
||||
public function setProjects(?Project $projects): self
|
||||
{
|
||||
$this->projects = $projects;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Speaker>
|
||||
*/
|
||||
|
@ -166,4 +154,28 @@ class Event implements EntityInterface
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Project>
|
||||
*/
|
||||
public function getProjects(): Collection
|
||||
{
|
||||
return $this->projects;
|
||||
}
|
||||
|
||||
public function addProject(Project $project): self
|
||||
{
|
||||
if (!$this->projects->contains($project)) {
|
||||
$this->projects[] = $project;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeProject(Project $project): self
|
||||
{
|
||||
$this->projects->removeElement($project);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,9 +51,9 @@ class Project implements EntityInterface
|
|||
protected $debriefings;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=Event::class, mappedBy="projects", cascade={"persist", "remove"})
|
||||
* @ORM\ManyToMany(targetEntity=Event::class, mappedBy="projects")
|
||||
*/
|
||||
protected $events;
|
||||
private $events;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -185,20 +185,14 @@ class Project implements EntityInterface
|
|||
*/
|
||||
public function getEvents(): Collection
|
||||
{
|
||||
$events = $this->events->toArray();
|
||||
|
||||
usort($events, function($a, $b) {
|
||||
return $a->getStartAt() <=> $a->getFinishAt();
|
||||
});
|
||||
|
||||
return new ArrayCollection($events);
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
public function addEvent(Event $event): self
|
||||
{
|
||||
if (!$this->events->contains($event)) {
|
||||
$this->events[] = $event;
|
||||
$event->setProjects($this);
|
||||
$event->addProject($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -207,10 +201,7 @@ class Project implements EntityInterface
|
|||
public function removeEvent(Event $event): self
|
||||
{
|
||||
if ($this->events->removeElement($event)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($event->getProjects() === $this) {
|
||||
$event->setProjects(null);
|
||||
}
|
||||
$event->removeProject($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
|
@ -56,6 +56,11 @@ class Speaker implements EntityInterface, EncryptedEntityInterface
|
|||
*/
|
||||
private $events;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=30, nullable=true)
|
||||
*/
|
||||
private $color;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->interventions = new ArrayCollection();
|
||||
|
@ -192,4 +197,16 @@ class Speaker implements EntityInterface, EncryptedEntityInterface
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColor(): ?string
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
public function setColor(?string $color): self
|
||||
{
|
||||
$this->color = $color;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Symfony\Component\Form\AbstractType;
|
|||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
|
||||
class SpeakerType extends AbstractType
|
||||
{
|
||||
|
@ -14,6 +15,17 @@ class SpeakerType extends AbstractType
|
|||
{
|
||||
$builder
|
||||
->add('name')
|
||||
->add('color', ChoiceType::class, [
|
||||
'required' => true,
|
||||
'choices' => [
|
||||
'Bleu' => 'primary',
|
||||
'Gris' => 'secondary',
|
||||
'Vert' => 'success',
|
||||
'Rouge' => 'danger',
|
||||
'Jaune' => 'warning',
|
||||
'Turquoise' => 'info',
|
||||
],
|
||||
])
|
||||
;
|
||||
|
||||
if ($options['edit_caldav']) {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace App\Repository;
|
||||
|
||||
use App\Core\Repository\RepositoryQuery;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use App\Repository\EventRepository as Repository;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
|
||||
class EventRepositoryQuery extends RepositoryQuery
|
||||
{
|
||||
|
@ -12,4 +12,54 @@ class EventRepositoryQuery extends RepositoryQuery
|
|||
{
|
||||
parent::__construct($repository, 'e', $paginator);
|
||||
}
|
||||
|
||||
public function ofProjects(array $projects): self
|
||||
{
|
||||
if (empty($projects)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this
|
||||
->innerJoin('.projects', 'p')
|
||||
->andWhere('p.id IN (:projectIds)')
|
||||
->setParameter('projectIds', array_map(fn ($i) => $i->getId(), $projects))
|
||||
;
|
||||
}
|
||||
|
||||
public function ofSpeakers(array $speakers): self
|
||||
{
|
||||
if (empty($speakers)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this
|
||||
->innerJoin('.speakers', 's')
|
||||
->andWhere('s.id IN (:speakerIds)')
|
||||
->setParameter('speakerIds', array_map(fn ($i) => $i->getId(), $speakers))
|
||||
;
|
||||
}
|
||||
|
||||
public function ofMonth(int $month): self
|
||||
{
|
||||
return $this
|
||||
->andWhere('.startAt LIKE :month')
|
||||
->setParameter('month', '%-'.sprintf('%02d', $month).'-% %')
|
||||
;
|
||||
}
|
||||
|
||||
public function ofYear(int $year): self
|
||||
{
|
||||
return $this
|
||||
->andWhere('.startAt LIKE :year')
|
||||
->setParameter('year', $year.'-%-% %')
|
||||
;
|
||||
}
|
||||
|
||||
public function ofDay(int $day): self
|
||||
{
|
||||
return $this
|
||||
->andWhere('.startAt LIKE :day')
|
||||
->setParameter('day', '%-%-'.sprintf('%02d', $day).' %')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,16 @@ class ProjectRepositoryQuery extends RepositoryQuery
|
|||
{
|
||||
parent::__construct($repository, 'p', $paginator);
|
||||
}
|
||||
|
||||
public function withIds(?array $ids)
|
||||
{
|
||||
if (null === $ids) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this
|
||||
->andWhere('.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,18 @@ class SpeakerRepositoryQuery extends RepositoryQuery
|
|||
parent::__construct($repository, 's', $paginator);
|
||||
}
|
||||
|
||||
public function withIds(?array $ids)
|
||||
{
|
||||
if (null === $ids) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this
|
||||
->andWhere('.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
;
|
||||
}
|
||||
|
||||
public function withCalendar()
|
||||
{
|
||||
return $this
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
{% extends '@Core/admin/layout.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="bg-light pl-5 pr-4 pt-5 pb-5">
|
||||
<div class="d-flex">
|
||||
<div class="mr-auto w-50">
|
||||
<h1 class="display-5">
|
||||
Tableau de bord
|
||||
</h1>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="p-3">
|
||||
<div id="calendar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
46
yarn.lock
46
yarn.lock
|
@ -2246,6 +2246,11 @@ chrome-trace-event@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
||||
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
||||
|
||||
chunk@^0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/chunk/-/chunk-0.0.3.tgz#d4f7473e35439c543d1d209464c27839e2b51d7a"
|
||||
integrity sha512-oGfwvhjGRW3Ks4GTdGoJhZWKEO1eomjOC26001R+5H0TIlP7vBCO+/XcNcPCA6ayYC7RQSq1/NsN4679Odcm5A==
|
||||
|
||||
ci-info@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
|
||||
|
@ -2800,7 +2805,7 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
|||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debuglog@*, debuglog@^1.0.1:
|
||||
debuglog@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
|
||||
|
@ -4250,7 +4255,7 @@ import-local@^3.0.2:
|
|||
pkg-dir "^4.2.0"
|
||||
resolve-cwd "^3.0.0"
|
||||
|
||||
imurmurhash@*, imurmurhash@^0.1.4:
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
@ -4966,11 +4971,6 @@ lockfile@^1.0.4:
|
|||
dependencies:
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
lodash._baseindexof@*:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
|
||||
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
|
||||
|
||||
lodash._baseuniq@~4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
|
||||
|
@ -4979,33 +4979,11 @@ lodash._baseuniq@~4.6.0:
|
|||
lodash._createset "~4.0.0"
|
||||
lodash._root "~3.0.0"
|
||||
|
||||
lodash._bindcallback@*:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
|
||||
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
|
||||
|
||||
lodash._cacheindexof@*:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
|
||||
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
|
||||
|
||||
lodash._createcache@*:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
|
||||
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
|
||||
dependencies:
|
||||
lodash._getnative "^3.0.0"
|
||||
|
||||
lodash._createset@~4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
|
||||
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
|
||||
|
||||
lodash._getnative@*, lodash._getnative@^3.0.0:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
||||
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
|
||||
|
||||
lodash._root@~3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
|
||||
|
@ -5036,11 +5014,6 @@ lodash.merge@^4.6.2:
|
|||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.restparam@*:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
|
||||
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
|
||||
|
||||
lodash.truncate@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||
|
@ -8024,6 +7997,11 @@ vue-eslint-parser@^7.10.0:
|
|||
lodash "^4.17.21"
|
||||
semver "^6.3.0"
|
||||
|
||||
vue-fragment@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-fragment/-/vue-fragment-1.5.2.tgz#310017170c564c4aad95da14c185c92c6784fd3c"
|
||||
integrity sha512-KEW0gkeNOLJjtXN4jqJhTazez5jtrwimHkE5Few/VxblH4F9EcvJiEsahrV5kg5uKd5U8du4ORKS6QjGE0piYA==
|
||||
|
||||
vue-hot-reload-api@^2.3.0:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
|
|
Loading…
Reference in a new issue