diff --git a/package.json b/package.json
index 590976cfa..8ef78d1c7 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node14": "^1.0.1",
"bindings": "^1.5.0",
- "bootstrap": "^5.0.1",
+ "bootstrap": "^5.3.0",
"chokidar": "^3.5.1",
"fork-awesome": "^1.1.7",
"jquery": "^3.6.0",
@@ -31,7 +31,8 @@
"typescript": "^4.7.2",
"vue-loader": "^17.0.0",
"webpack": "^5.75.0",
- "webpack-cli": "^5.0.1"
+ "webpack-cli": "^5.0.1",
+ "@types/dompurify": "^3.0.5"
},
"dependencies": {
"@fullcalendar/core": "^6.1.4",
@@ -53,7 +54,9 @@
"vue-i18n": "^9.1.6",
"vue-multiselect": "3.0.0-alpha.2",
"vue-toast-notification": "^2.0",
- "vuex": "^4.0.0"
+ "vuex": "^4.0.0",
+ "dompurify": "^3.0.6",
+ "marked": "^9.1.5"
},
"browserslist": [
"Firefox ESR"
diff --git a/src/Bundle/ChillMainBundle/Controller/AdminController.php b/src/Bundle/ChillMainBundle/Controller/AdminController.php
index 7d3826823..46fbfb351 100644
--- a/src/Bundle/ChillMainBundle/Controller/AdminController.php
+++ b/src/Bundle/ChillMainBundle/Controller/AdminController.php
@@ -47,4 +47,12 @@ class AdminController extends AbstractController
{
return $this->render('@ChillMain/Admin/indexUser.html.twig');
}
+
+ /**
+ * @Route("/{_locale}/admin/dashboard", name="chill_main_dashboard_admin")
+ */
+ public function indexDashboardAction()
+ {
+ return $this->render('@ChillMain/Admin/indexDashboard.html.twig');
+ }
}
diff --git a/src/Bundle/ChillMainBundle/Controller/DashboardApiController.php b/src/Bundle/ChillMainBundle/Controller/DashboardApiController.php
new file mode 100644
index 000000000..e091d2f5a
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Controller/DashboardApiController.php
@@ -0,0 +1,41 @@
+ 'top-left',
+ 'id' => 1,
+ 'type' => 'news',
+ 'metadata' => [
+ // arbitrary data that will be store "some time"
+ 'only_unread' => false,
+ ],
+ ],
+ ];
+
+ return new JsonResponse($data, JsonResponse::HTTP_OK, []);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Controller/NewsItemApiController.php b/src/Bundle/ChillMainBundle/Controller/NewsItemApiController.php
new file mode 100644
index 000000000..5224228a8
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Controller/NewsItemApiController.php
@@ -0,0 +1,52 @@
+newsItemRepository->countWithDateFilter();
+ $paginator = $this->paginatorFactory->create($total);
+ $newsItems = $this->newsItemRepository->findWithDateFilter(
+ $limit = $paginator->getItemsPerPage(),
+ $offset = $paginator->getCurrentPage()->getFirstItemNumber()
+ );
+
+ return new JsonResponse($this->serializer->serialize(
+ new Collection(array_values($newsItems), $paginator),
+ 'json',
+ [
+ AbstractNormalizer::GROUPS => ['read'],
+ ]
+ ), JsonResponse::HTTP_OK, [], true);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Controller/NewsItemController.php b/src/Bundle/ChillMainBundle/Controller/NewsItemController.php
new file mode 100644
index 000000000..a94dd55b9
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Controller/NewsItemController.php
@@ -0,0 +1,27 @@
+addOrderBy('e.startDate', 'DESC');
+ $query->addOrderBy('e.id', 'DESC');
+
+ return parent::orderQuery($action, $query, $request, $paginator);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Controller/NewsItemHistoryController.php b/src/Bundle/ChillMainBundle/Controller/NewsItemHistoryController.php
new file mode 100644
index 000000000..8c645828b
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Controller/NewsItemHistoryController.php
@@ -0,0 +1,72 @@
+buildFilterOrder(false);
+ $total = $this->newsItemRepository->countAllFilteredByUser($filter->getQueryString());
+ $newsItems = $this->newsItemRepository->findAllFilteredByUser($filter->getQueryString());
+
+ $pagination = $this->paginatorFactory->create($total);
+
+ return $this->render('@ChillMain/NewsItem/news_items_history.html.twig', [
+ 'entities' => $newsItems,
+ 'paginator' => $pagination,
+ 'filter_order' => $filter,
+ ]);
+ }
+
+ /**
+ * @Route("/{_locale}/news-items/{id}", name="chill_main_single_news_item")
+ */
+ public function showSingleItem(int $id, Request $request): Response
+ {
+ $newsItem = $this->newsItemRepository->findOneBy(['id' => $id]);
+
+ return $this->render(
+ '@ChillMain/NewsItem/show.html.twig',
+ [
+ 'entity' => $newsItem,
+ ]
+ );
+ }
+
+ private function buildFilterOrder($includeFilterByUser = true, $includeMissionType = false): FilterOrderHelper
+ {
+ $filterBuilder = $this->filterOrderHelperFactory
+ ->create(self::class)
+ ->addSearchBox();
+
+ return $filterBuilder->build();
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
index 5c33ef1b3..63cfb94c4 100644
--- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
+++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
@@ -19,6 +19,7 @@ use Chill\MainBundle\Controller\CountryController;
use Chill\MainBundle\Controller\LanguageController;
use Chill\MainBundle\Controller\LocationController;
use Chill\MainBundle\Controller\LocationTypeController;
+use Chill\MainBundle\Controller\NewsItemController;
use Chill\MainBundle\Controller\RegroupmentController;
use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Controller\UserJobApiController;
@@ -53,6 +54,7 @@ use Chill\MainBundle\Entity\GeographicalUnitLayer;
use Chill\MainBundle\Entity\Language;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType;
+use Chill\MainBundle\Entity\NewsItem;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
@@ -62,6 +64,7 @@ use Chill\MainBundle\Form\CountryType;
use Chill\MainBundle\Form\LanguageType;
use Chill\MainBundle\Form\LocationFormType;
use Chill\MainBundle\Form\LocationTypeType;
+use Chill\MainBundle\Form\NewsItemType;
use Chill\MainBundle\Form\RegroupmentType;
use Chill\MainBundle\Form\UserJobType;
use Chill\MainBundle\Form\UserType;
@@ -544,6 +547,27 @@ class ChillMainExtension extends Extension implements
],
],
],
+ [
+ 'class' => NewsItem::class,
+ 'name' => 'news_item',
+ 'base_path' => '/admin/news_item',
+ 'form_class' => NewsItemType::class,
+ 'controller' => NewsItemController::class,
+ 'actions' => [
+ 'index' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillMain/NewsItem/index.html.twig',
+ ],
+ 'new' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillMain/NewsItem/new.html.twig',
+ ],
+ 'edit' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillMain/NewsItem/edit.html.twig',
+ ],
+ ],
+ ],
],
'apis' => [
[
diff --git a/src/Bundle/ChillMainBundle/Entity/DashboardConfigItem.php b/src/Bundle/ChillMainBundle/Entity/DashboardConfigItem.php
new file mode 100644
index 000000000..5ad1107c5
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Entity/DashboardConfigItem.php
@@ -0,0 +1,112 @@
+id;
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function setType(string $type): self
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ public function getPosition(): string
+ {
+ return $this->position;
+ }
+
+ public function setPosition(string $position): void
+ {
+ $this->position = $position;
+ }
+
+ public function getUser(): User
+ {
+ return $this->user;
+ }
+
+ public function setUser(User $user): void
+ {
+ $this->user = $user;
+ }
+
+ public function getMetadata(): array
+ {
+ return $this->metadata;
+ }
+
+ public function setMetadata(array $metadata): void
+ {
+ $this->metadata = $metadata;
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Entity/NewsItem.php b/src/Bundle/ChillMainBundle/Entity/NewsItem.php
new file mode 100644
index 000000000..604c58c5f
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Entity/NewsItem.php
@@ -0,0 +1,128 @@
+title;
+ }
+
+ public function setTitle(string $title): void
+ {
+ $this->title = $title;
+ }
+
+ public function getContent(): string
+ {
+ return $this->content;
+ }
+
+ public function setContent(string $content): void
+ {
+ $this->content = $content;
+ }
+
+ public function getStartDate(): ?\DateTimeImmutable
+ {
+ return $this->startDate;
+ }
+
+ public function setStartDate(?\DateTimeImmutable $startDate): void
+ {
+ $this->startDate = $startDate;
+ }
+
+ public function getEndDate(): ?\DateTimeImmutable
+ {
+ return $this->endDate;
+ }
+
+ public function setEndDate(?\DateTimeImmutable $endDate): void
+ {
+ $this->endDate = $endDate;
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Form/NewsItemType.php b/src/Bundle/ChillMainBundle/Form/NewsItemType.php
new file mode 100644
index 000000000..01ae63882
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Form/NewsItemType.php
@@ -0,0 +1,54 @@
+add('title', TextType::class, [
+ 'required' => true,
+ ])
+ ->add('content', ChillTextareaType::class, [
+ 'required' => false,
+ ])
+ ->add(
+ 'startDate',
+ ChillDateType::class,
+ [
+ 'required' => true,
+ 'input' => 'datetime_immutable',
+ ]
+ )
+ ->add('endDate', ChillDateType::class, [
+ 'required' => false,
+ 'input' => 'datetime_immutable',
+ ]);
+ }
+
+ /**
+ * @return void
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefault('data_class', NewsItem::class);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Repository/NewsItemRepository.php b/src/Bundle/ChillMainBundle/Repository/NewsItemRepository.php
new file mode 100644
index 000000000..88b597647
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Repository/NewsItemRepository.php
@@ -0,0 +1,137 @@
+repository = $entityManager->getRepository(NewsItem::class);
+ }
+
+ public function createQueryBuilder(string $alias, string $indexBy = null): QueryBuilder
+ {
+ return $this->repository->createQueryBuilder($alias, $indexBy);
+ }
+
+ public function find($id)
+ {
+ return $this->repository->find($id);
+ }
+
+ public function findAll()
+ {
+ return $this->repository->findAll();
+ }
+
+ public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null)
+ {
+ return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
+ }
+
+ public function findOneBy(array $criteria)
+ {
+ return $this->repository->findOneBy($criteria);
+ }
+
+ public function getClassName()
+ {
+ return NewsItem::class;
+ }
+
+ public function buildBaseQuery(
+ string $pattern = null
+ ): QueryBuilder {
+ $qb = $this->createQueryBuilder('n');
+
+ if (null !== $pattern && '' !== $pattern) {
+ $qb->andWhere($qb->expr()->like('LOWER(UNACCENT(n.title))', 'LOWER(UNACCENT(:pattern))'))
+ ->orWhere($qb->expr()->like('LOWER(UNACCENT(n.content))', 'LOWER(UNACCENT(:pattern))'))
+ ->setParameter('pattern', '%'.$pattern.'%');
+ }
+
+ return $qb;
+ }
+
+ public function findAllFilteredByUser(string $pattern = null)
+ {
+ $qb = $this->buildBaseQuery($pattern);
+ $qb->addOrderBy('n.startDate', 'DESC')
+ ->addOrderBy('n.id', 'DESC');
+
+ return $qb->getQuery()->getResult();
+ }
+
+ public function findWithDateFilter($limit = null, $offset = null)
+ {
+ $qb = $this->buildQueryWithDateFilter();
+
+ if ($limit) {
+ $qb->setMaxResults($limit);
+ }
+
+ if ($offset) {
+ $qb->setFirstResult($offset);
+ }
+
+ return $qb
+ ->getQuery()
+ ->getResult();
+ }
+
+ public function countAllFilteredByUser(string $pattern = null)
+ {
+ $qb = $this->buildBaseQuery($pattern);
+
+ return $qb
+ ->select('COUNT(n)')
+ ->getQuery()
+ ->getSingleScalarResult();
+ }
+
+ public function countWithDateFilter()
+ {
+ return $this->buildQueryWithDateFilter()
+ ->select('COUNT(n)')
+ ->getQuery()
+ ->getSingleScalarResult();
+ }
+
+ public function buildQueryWithDateFilter(): QueryBuilder
+ {
+ $now = $this->clock->now();
+
+ $qb = $this->createQueryBuilder('n');
+ $qb
+ ->where(
+ $qb->expr()->andX(
+ $qb->expr()->lte('n.startDate', ':now'),
+ $qb->expr()->orX(
+ $qb->expr()->gt('n.endDate', ':now'),
+ $qb->expr()->isNull('n.endDate')
+ )
+ )
+ )
+ ->setParameter('now', $now);
+
+ return $qb;
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss
index 96da20779..feca44382 100644
--- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss
+++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss
@@ -11,6 +11,7 @@
// 3. Include remainder of required Bootstrap stylesheets
@import "bootstrap/scss/variables";
+@import "bootstrap/scss/variables-dark";
// 4. Include any default map overrides here
@import "custom/_maps";
diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts
index b31b70897..79dae2695 100644
--- a/src/Bundle/ChillMainBundle/Resources/public/types.ts
+++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts
@@ -160,3 +160,11 @@ export interface LocationType {
contactData: "optional" | "required";
title: TranslatableString;
}
+
+export interface NewsItemType {
+ id: number;
+ title: string;
+ content: string;
+ startdate: { date: DateTime };
+ enddate: { date: DateTime | null}
+}
diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue
index 315fd863f..02763221a 100644
--- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue
+++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue
@@ -97,6 +97,8 @@ import MyNotifications from './MyNotifications';
import MyWorkflows from './MyWorkflows.vue';
import TabCounter from './TabCounter';
import { mapState } from "vuex";
+import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
+
export default {
name: "App",
@@ -112,7 +114,7 @@ export default {
},
data() {
return {
- activeTab: 'MyCustoms'
+ activeTab: 'MyCustoms',
}
},
computed: {
@@ -126,8 +128,11 @@ export default {
},
methods: {
selectTab(tab) {
- this.$store.dispatch('getByTab', { tab: tab });
+ if (tab !== 'MyCustoms') {
+ this.$store.dispatch('getByTab', { tab: tab });
+ }
this.activeTab = tab;
+ console.log(this.activeTab)
}
},
mounted() {
diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue
new file mode 100644
index 000000000..a33bebf5c
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue
@@ -0,0 +1,47 @@
+
+ {{ item.title }}
+ {{ $t('widget.news.date') }}: {{ formatDate(item.startdate.date) }}
+ {{ $t('widget.news.title') }}
+
+
+ {{ props.item.title }}
+
@@ -38,24 +38,12 @@
+ {{ "news.no_data"|trans }} +
+ {% else %} + +#} + {{ entity.content|u.truncate(350, '…', false)|chill_markdown_to_html }} +{# {% if entity.content|length > 350 %}#} +{# {{ 'news.read_more'|trans }}#} +{# {% endif %}#} +{##} + +