mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-05 15:29:50 +00:00
Merge branch 'issue159_page_acceuil' into testing-202401
This commit is contained in:
commit
dd62581226
@ -16,7 +16,6 @@
|
||||
"@tsconfig/node14": "^1.0.1",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"bindings": "^1.5.0",
|
||||
"bootstrap": "^5.3.0",
|
||||
"chokidar": "^3.5.1",
|
||||
"fork-awesome": "^1.1.7",
|
||||
"jquery": "^3.6.0",
|
||||
@ -35,6 +34,7 @@
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "~5.2.0",
|
||||
"@fullcalendar/core": "^6.1.4",
|
||||
"@fullcalendar/daygrid": "^6.1.4",
|
||||
"@fullcalendar/interaction": "^6.1.4",
|
||||
|
@ -10,7 +10,7 @@
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[direct]=0&max[indirect]=999999" />
|
||||
<env name="SYMFONY_DEPRECATIONS_HELPER" value="logFile=var/log/deprecations.log" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
<env name="KERNEL_CLASS" value="\App\Kernel" />
|
||||
</php>
|
||||
|
@ -22,23 +22,24 @@ use Symfony\Component\Serializer\SerializerInterface;
|
||||
class NewsItemApiController
|
||||
{
|
||||
public function __construct(
|
||||
private NewsItemRepository $newsItemRepository,
|
||||
private SerializerInterface $serializer,
|
||||
private PaginatorFactory $paginatorFactory
|
||||
) {}
|
||||
private readonly NewsItemRepository $newsItemRepository,
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly PaginatorFactory $paginatorFactory
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of news items filtered on start and end date.
|
||||
*
|
||||
* @Route("/api/1.0/main/news.json", methods={"get"})
|
||||
* @Route("/api/1.0/main/news/current.json", methods={"get"})
|
||||
*/
|
||||
public function listCurrentNewsItems(): JsonResponse
|
||||
{
|
||||
$total = $this->newsItemRepository->countWithDateFilter();
|
||||
$total = $this->newsItemRepository->countCurrentNews();
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
$newsItems = $this->newsItemRepository->findWithDateFilter(
|
||||
$limit = $paginator->getItemsPerPage(),
|
||||
$offset = $paginator->getCurrentPage()->getFirstItemNumber()
|
||||
$newsItems = $this->newsItemRepository->findCurrentNews(
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPage()->getFirstItemNumber()
|
||||
);
|
||||
|
||||
return new JsonResponse($this->serializer->serialize(
|
||||
|
@ -11,57 +11,58 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\NewsItem;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\NewsItemRepository;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
class NewsItemHistoryController extends AbstractController
|
||||
final readonly class NewsItemHistoryController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NewsItemRepository $newsItemRepository,
|
||||
private readonly PaginatorFactory $paginatorFactory,
|
||||
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
|
||||
) {}
|
||||
private readonly Environment $environment,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/news-items/history", name="chill_main_news_items_history")
|
||||
*/
|
||||
public function listAction(Request $request): Response
|
||||
public function list(): Response
|
||||
{
|
||||
$filter = $this->buildFilterOrder(false);
|
||||
$total = $this->newsItemRepository->countAllFilteredByUser($filter->getQueryString());
|
||||
$newsItems = $this->newsItemRepository->findAllFilteredByUser($filter->getQueryString());
|
||||
$filter = $this->buildFilterOrder();
|
||||
$total = $this->newsItemRepository->countAllFilteredBySearchTerm($filter->getQueryString());
|
||||
$newsItems = $this->newsItemRepository->findAllFilteredBySearchTerm($filter->getQueryString());
|
||||
|
||||
$pagination = $this->paginatorFactory->create($total);
|
||||
|
||||
return $this->render('@ChillMain/NewsItem/news_items_history.html.twig', [
|
||||
return new Response($this->environment->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
|
||||
public function showSingleItem(NewsItem $newsItem, Request $request): Response
|
||||
{
|
||||
$newsItem = $this->newsItemRepository->findOneBy(['id' => $id]);
|
||||
|
||||
return $this->render(
|
||||
return new Response($this->environment->render(
|
||||
'@ChillMain/NewsItem/show.html.twig',
|
||||
[
|
||||
'entity' => $newsItem,
|
||||
]
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
private function buildFilterOrder($includeFilterByUser = true, $includeMissionType = false): FilterOrderHelper
|
||||
private function buildFilterOrder(): FilterOrderHelper
|
||||
{
|
||||
$filterBuilder = $this->filterOrderHelperFactory
|
||||
->create(self::class)
|
||||
|
@ -562,10 +562,18 @@ class ChillMainExtension extends Extension implements
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/NewsItem/new.html.twig',
|
||||
],
|
||||
'view' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/NewsItem/view_admin.html.twig',
|
||||
],
|
||||
'edit' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/NewsItem/edit.html.twig',
|
||||
],
|
||||
'delete' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/NewsItem/delete.html.twig',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -57,7 +57,7 @@ class DashboardConfigItem
|
||||
private ?User $user = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json" "jsonb"=true, options={"default": "[]"})
|
||||
* @ORM\Column(type="json", options={"default": "[]", "jsonb": true})
|
||||
*
|
||||
* @Serializer\Groups({"dashboardConfigItem:read"})
|
||||
*/
|
||||
|
@ -22,7 +22,7 @@ class NewsItemRepository implements ObjectRepository
|
||||
{
|
||||
private readonly EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, private ClockInterface $clock)
|
||||
public function __construct(EntityManagerInterface $entityManager, private readonly ClockInterface $clock)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(NewsItem::class);
|
||||
}
|
||||
@ -57,11 +57,14 @@ class NewsItemRepository implements ObjectRepository
|
||||
return NewsItem::class;
|
||||
}
|
||||
|
||||
public function buildBaseQuery(
|
||||
private function buildBaseQuery(
|
||||
string $pattern = null
|
||||
): QueryBuilder {
|
||||
$qb = $this->createQueryBuilder('n');
|
||||
|
||||
$qb->where('n.startDate <= :now');
|
||||
$qb->setParameter('now', $this->clock->now());
|
||||
|
||||
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))'))
|
||||
@ -71,24 +74,28 @@ class NewsItemRepository implements ObjectRepository
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function findAllFilteredByUser(string $pattern = null)
|
||||
public function findAllFilteredBySearchTerm(string $pattern = null)
|
||||
{
|
||||
$qb = $this->buildBaseQuery($pattern);
|
||||
$qb->addOrderBy('n.startDate', 'DESC')
|
||||
$qb
|
||||
->addOrderBy('n.startDate', 'DESC')
|
||||
->addOrderBy('n.id', 'DESC');
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findWithDateFilter($limit = null, $offset = null)
|
||||
/**
|
||||
* @return list<NewsItem>
|
||||
*/
|
||||
public function findCurrentNews(int $limit = null, int $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryWithDateFilter();
|
||||
$qb = $this->buildQueryCurrentNews();
|
||||
|
||||
if ($limit) {
|
||||
if (null !== $limit) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
if ($offset) {
|
||||
if (null !== $offset) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
|
||||
@ -97,7 +104,7 @@ class NewsItemRepository implements ObjectRepository
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function countAllFilteredByUser(string $pattern = null)
|
||||
public function countAllFilteredBySearchTerm(string $pattern = null)
|
||||
{
|
||||
$qb = $this->buildBaseQuery($pattern);
|
||||
|
||||
@ -107,15 +114,15 @@ class NewsItemRepository implements ObjectRepository
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countWithDateFilter()
|
||||
public function countCurrentNews()
|
||||
{
|
||||
return $this->buildQueryWithDateFilter()
|
||||
return $this->buildQueryCurrentNews()
|
||||
->select('COUNT(n)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function buildQueryWithDateFilter(): QueryBuilder
|
||||
private function buildQueryCurrentNews(): QueryBuilder
|
||||
{
|
||||
$now = $this->clock->now();
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
// 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";
|
||||
|
@ -165,6 +165,6 @@ export interface NewsItemType {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
startdate: { date: DateTime };
|
||||
enddate: { date: DateTime | null}
|
||||
startDate: DateTime;
|
||||
endDate: DateTime | null;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="newsItems.length > 0">
|
||||
<h1>{{ $t('widget.news.title') }}</h1>
|
||||
<ul class="scrollable">
|
||||
<NewsItem v-for="item in newsItems" :item="item" :key="item.id" />
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { makeFetch } from '../../../lib/api/apiMethods';
|
||||
import { fetchResults } from '../../../lib/api/apiMethods';
|
||||
import Modal from '../../_components/Modal.vue';
|
||||
import { NewsItemType } from '../../../types';
|
||||
import NewsItem from './NewsItem.vue';
|
||||
@ -17,10 +17,12 @@ import NewsItem from './NewsItem.vue';
|
||||
const newsItems = ref<NewsItemType[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
makeFetch('GET', '/api/1.0/main/news.json')
|
||||
.then((response: { results: NewsItemType[] }) => {
|
||||
fetchResults<NewsItemType>('/api/1.0/main/news/current.json')
|
||||
.then((news): Promise<void> => {
|
||||
// console.log('news articles', response.results)
|
||||
newsItems.value = response.results
|
||||
newsItems.value = news;
|
||||
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch((error: string) => {
|
||||
console.error('Error fetching news items', error);
|
||||
|
@ -15,7 +15,7 @@
|
||||
</template>
|
||||
<template #body>
|
||||
<p class="news-date">
|
||||
{{ $t('widget.news.date') }}: <span>{{ formatDate(item.startdate.date) }}</span>
|
||||
<span>{{ $d(newsItemStartDate(), 'short') }}</span>
|
||||
</p>
|
||||
<div v-html="convertMarkdownToHtml(item.content)"></div>
|
||||
</template>
|
||||
@ -30,12 +30,17 @@ import DOMPurify from 'dompurify';
|
||||
import { DateTime, NewsItemType } from "../../../types";
|
||||
import type { PropType } from 'vue'
|
||||
import { defineProps, ref } from "vue";
|
||||
import {dateToISO} from "../../../chill/js/date";
|
||||
import {ISOToDatetime} from "../../../chill/js/date";
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<NewsItemType>,
|
||||
required: true
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 200,
|
||||
}
|
||||
})
|
||||
|
||||
@ -52,13 +57,13 @@ const closeModal = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const shouldTruncate = (content: string, maxLength = 100): boolean => {
|
||||
return content.length > maxLength;
|
||||
const shouldTruncate = (content: string): boolean => {
|
||||
return content.length > props.maxLength;
|
||||
};
|
||||
|
||||
const truncateContent = (content: string, maxLength = 100): string => {
|
||||
if (shouldTruncate(content, maxLength)) {
|
||||
let truncatedContent = content.slice(0, maxLength);
|
||||
const truncateContent = (content: string): string => {
|
||||
if (shouldTruncate(content)) {
|
||||
let truncatedContent = content.slice(0, props.maxLength);
|
||||
let linkDepth = 0;
|
||||
let linkStartIndex = -1;
|
||||
|
||||
@ -103,13 +108,13 @@ const convertMarkdownToHtml = (markdown: string): string => {
|
||||
return DOMPurify.sanitize(rawHtml)
|
||||
};
|
||||
|
||||
const prepareContent = (content: string, maxLength = 200): string => {
|
||||
const prepareContent = (content: string): string => {
|
||||
const htmlContent = convertMarkdownToHtml(content);
|
||||
return truncateContent(htmlContent, maxLength);
|
||||
return truncateContent(htmlContent);
|
||||
};
|
||||
|
||||
const formatDate = (datetime: DateTime): string|null => {
|
||||
return dateToISO(new Date(datetime.toString()))
|
||||
const newsItemStartDate = (): null|Date => {
|
||||
return ISOToDatetime(props.item?.startDate.datetime);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -40,9 +40,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mbloc col col-lg-12 col-lg-4" v-if="this.dashboardItems">
|
||||
<div v-for="dashboardItem in this.dashboardItems">
|
||||
<template v-for="dashboardItem in this.dashboardItems">
|
||||
<News v-if="dashboardItem.type === 'news'"/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -64,7 +64,8 @@ export default {
|
||||
counterClass: {
|
||||
counter: true //hack to pass class 'counter' in i18n-t
|
||||
},
|
||||
dashboardItems: []
|
||||
dashboardItems: [],
|
||||
masonry: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -75,7 +76,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
const elem = document.querySelector('#dashboards');
|
||||
const masonry = new Masonry(elem, {});
|
||||
this.masonry = new Masonry(elem, {});
|
||||
//Fetch the dashboard items configured for user. Currently response is still hardcoded
|
||||
makeFetch('GET', '/api/1.0/main/dashboard-config-item.json')
|
||||
.then((response) => {
|
||||
@ -84,8 +85,13 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
updated() {
|
||||
this.masonry.layout();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -15,18 +15,20 @@ import AddressModal from "./AddressModal.vue";
|
||||
|
||||
export interface AddressModalContentProps {
|
||||
address_id: number;
|
||||
address_ref_status: AddressRefStatus | null;
|
||||
address_ref_status: AddressRefStatus;
|
||||
}
|
||||
|
||||
const data = reactive<{
|
||||
loading: boolean,
|
||||
working_address: Address | null,
|
||||
working_ref_status: AddressRefStatus | null,
|
||||
}>({
|
||||
interface AddressModalData {
|
||||
loading: boolean,
|
||||
working_address: Address | null,
|
||||
working_ref_status: AddressRefStatus | null,
|
||||
}
|
||||
|
||||
const data: AddressModalData = reactive({
|
||||
loading: false,
|
||||
working_address: null,
|
||||
working_ref_status: null,
|
||||
});
|
||||
} as AddressModalData);
|
||||
|
||||
const props = defineProps<AddressModalContentProps>();
|
||||
|
||||
|
@ -1 +1 @@
|
||||
{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}
|
||||
{{ ('crud.' ~ crud_name ~ '.title_view')|trans({'%crud_name%' : crud_name }) }}
|
||||
|
@ -0,0 +1,30 @@
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<h3>
|
||||
{{ entity.title }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<p>
|
||||
{% if entity.startDate %}
|
||||
<span>{{ entity.startDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
{% if entity.endDate %}
|
||||
<span> - {{ entity.endDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
<div>
|
||||
{{ entity.content|u.truncate(350, '… [' ~ ('news.read_more'|trans) ~ '](' ~ chill_path_add_return_path('chill_main_single_news_item', { 'id': entity.id } ) ~ ')', false)|chill_markdown_to_html }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_single_news_item', { 'id': entity.id } ) }}" class="btn btn-show btn-mini"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,13 @@
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc">
|
||||
<p class="date-label">
|
||||
<span>{{ entity.startDate|format_date('long') }}</span>
|
||||
{% if entity.endDate is not null %}
|
||||
<span> - {{ entity.endDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-bloc">
|
||||
{{ entity.content|chill_markdown_to_html }}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,6 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/delete.html.twig' %}
|
||||
{% endembed %}
|
||||
{% endblock admin_content %}
|
@ -11,17 +11,23 @@
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.title }}</td>
|
||||
<td>{{ entity.startDate|date }}</td>
|
||||
<td>{{ entity.startDate|format_date('long') }}</td>
|
||||
{% if entity.endDate is not null %}
|
||||
<td>{{ entity.endDate|date }}</td>
|
||||
<td>{{ entity.endDate|format_date('long') }}</td>
|
||||
{% else %}
|
||||
<td>{{ 'news.noDate'|trans }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_news_item_view', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_news_item_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_news_item_delete', { 'id': entity.id }) }}" class="btn btn-delete" title="{{ 'delete'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -21,54 +21,41 @@
|
||||
{% for entity in entities %}
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row wrap-header">
|
||||
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ entity.title }}
|
||||
</h3>
|
||||
<div>
|
||||
{% if entity.startDate %}
|
||||
<span>{{ entity.startDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
{% if entity.endDate %}
|
||||
<span> - {{ entity.endDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-col" style="justify-content: flex-end;">
|
||||
<div class="box">
|
||||
<div>
|
||||
{# <blockquote class="chill-user-quote">#}
|
||||
{{ entity.content|u.truncate(350, '…', false)|chill_markdown_to_html }}
|
||||
{# {% if entity.content|length > 350 %}#}
|
||||
{# <a href="{{ chill_path_add_return_path('chill_main_news_items_history', {'news_item_id': entity.id}) }}">{{ 'news.read_more'|trans }}</a>#}
|
||||
{# {% endif %}#}
|
||||
{# </blockquote>#}
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_single_news_item', { 'id': entity.id } ) }}" class="btn btn-show btn-mini"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col">
|
||||
<h2>{{ entity.title }}</h2>
|
||||
</div>
|
||||
<div class="wl-col">
|
||||
<p>
|
||||
{% if entity.startDate %}
|
||||
<span>{{ entity.startDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
{% if entity.endDate %}
|
||||
<span> - {{ entity.endDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-row separator">
|
||||
{{ entity.content|u.truncate(350, '… [' ~ ('news.read_more'|trans) ~ '](' ~ chill_path_add_return_path('chill_main_single_news_item', { 'id': entity.id } ) ~ ')', false)|chill_markdown_to_html }}
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_single_news_item', { 'id': entity.id } ) }}" class="btn btn-show btn-mini"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create">
|
||||
{{ 'Create'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -3,19 +3,17 @@
|
||||
{% block title 'news.show_details'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ entity.title }}</h1>
|
||||
<div class="col-md-10 col-xxl">
|
||||
<div class="news-item-show">
|
||||
<h1>{{ entity.title }}</h1>
|
||||
|
||||
<div class="flex-table">
|
||||
<div class="item-row">
|
||||
<div>
|
||||
<span>{{ entity.startDate|format_date('long') }}</span>
|
||||
{% if entity.endDate is not null %}
|
||||
<span> - {{ entity.endDate|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
{{ entity.content|chill_markdown_to_html }}
|
||||
</div>
|
||||
{% include '@ChillMain/NewsItem/_show.html.twig' with { 'entity': entity } %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('chill_main_news_items_history') }}" class="btn btn-cancel">{{ 'Back to the list'|trans|chill_return_path_label }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,29 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
|
||||
<div class="col-md-10 col-xxl">
|
||||
<div class="news-item-show">
|
||||
<h1>{{ entity.title }}</h1>
|
||||
|
||||
{% include '@ChillMain/NewsItem/_show.html.twig' with { 'entity': entity } %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('chill_crud_news_item_index') }}" class="btn btn-cancel">{{ 'Back to the list'|trans|chill_return_path_label }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_news_item_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_news_item_delete', { 'id': entity.id }) }}" class="btn btn-delete" title="{{ 'delete'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -17,7 +17,9 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
class AdminNewsMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
public function __construct(private readonly AuthorizationCheckerInterface $authorizationChecker) {}
|
||||
public function __construct(private readonly AuthorizationCheckerInterface $authorizationChecker)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
|
||||
]);
|
||||
}
|
||||
|
||||
$menu->addChild($this->translator->trans('news_history.menu'), [
|
||||
$menu->addChild($this->translator->trans('news.menu'), [
|
||||
'route' => 'chill_main_news_items_history',
|
||||
])
|
||||
->setExtras([
|
||||
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Templating\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\NewsItem;
|
||||
|
||||
/**
|
||||
* @implements ChillEntityRenderInterface<NewsItem>
|
||||
*/
|
||||
final readonly class NewsItemRender implements ChillEntityRenderInterface
|
||||
{
|
||||
public function renderBox($entity, array $options): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function renderString($entity, array $options): string
|
||||
{
|
||||
return $entity->getTitle();
|
||||
}
|
||||
|
||||
public function supports($newsItem, array $options): bool
|
||||
{
|
||||
return $newsItem instanceof NewsItem;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class NewsItemApiControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
public function testListCurrentNewsItems()
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request('GET', '/api/1.0/main/news/current.json');
|
||||
$this->assertResponseIsSuccessful('Testing whether the GET request to the news item Api endpoint was successful');
|
||||
|
||||
$responseContent = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (!empty($responseContent['data'][0])) {
|
||||
$this->assertArrayHasKey('title', $responseContent['data'][0]);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\NewsItem;
|
||||
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
/**
|
||||
* Tests the admin pages for news items.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class NewsItemControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
public function generateNewsItemIds(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::$container->get(EntityManagerInterface::class);
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$newsItems = $qb->select('n')->from(NewsItem::class, 'n')
|
||||
->setMaxResults(2)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
foreach ($newsItems as $n) {
|
||||
yield [$n->getId()];
|
||||
}
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
$client->request('GET', '/fr/admin/news_item');
|
||||
|
||||
self::assertResponseIsSuccessful('Test that news item admin page shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateNewsItemIds
|
||||
*/
|
||||
public function testShowSingleItem(int $newsItemId)
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
$client->request('GET', "/fr/amdin/news_item/{$newsItemId}/view");
|
||||
|
||||
$this->assertResponseIsSuccessful('test that single news item admin page loads successfully');
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\NewsItem;
|
||||
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class NewsItemsHistoryControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
public function generateNewsItemIds(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::$container->get(EntityManagerInterface::class);
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$newsItems = $qb->select('n')->from(NewsItem::class, 'n')
|
||||
->setMaxResults(2)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
foreach ($newsItems as $n) {
|
||||
yield [$n->getId()];
|
||||
}
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request('GET', '/fr/news-items/history');
|
||||
|
||||
self::assertResponseIsSuccessful('Test that /fr/news-items history shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateNewsItemIds
|
||||
*/
|
||||
public function testShowSingleItem(int $newsItemId)
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request('GET', "/fr/news-items/{$newsItemId}");
|
||||
|
||||
$this->assertResponseIsSuccessful('test that single news item page loads successfully');
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\NewsItem;
|
||||
use Chill\MainBundle\Repository\NewsItemRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class NewsItemRepositoryTest extends KernelTestCase
|
||||
{
|
||||
private function getNewsItemsRepository(): NewsItemRepository
|
||||
{
|
||||
return self::$container->get(NewsItemRepository::class);
|
||||
}
|
||||
|
||||
public function testFindCurrentNews()
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::$container->get(EntityManagerInterface::class);
|
||||
$repository = $this->getNewsItemsRepository();
|
||||
|
||||
$mockClock = $this->createMock(ClockInterface::class);
|
||||
|
||||
$mockClock->expects($this->once())->method('now')->willReturn(new \DateTime('2023-01-10'));
|
||||
|
||||
$newsItem1 = new NewsItem();
|
||||
$newsItem1->setTitle('This is a mock news item');
|
||||
$newsItem1->setContent('We are testing that the repository returns the correct news items');
|
||||
$newsItem1->setStartDate(new \DateTimeImmutable('2023-01-01'));
|
||||
$newsItem1->setEndDate(new \DateTimeImmutable('2023-01-05'));
|
||||
|
||||
$newsItem2 = new NewsItem();
|
||||
$newsItem2->setTitle('This is a mock news item');
|
||||
$newsItem2->setContent('We are testing that the repository returns the correct news items');
|
||||
$newsItem2->setStartDate(new \DateTimeImmutable('2023-12-15'));
|
||||
$newsItem2->setEndDate($mockClock->now());
|
||||
|
||||
$newsItem3 = new NewsItem();
|
||||
$newsItem3->setTitle('This is a mock news item');
|
||||
$newsItem3->setContent('We are testing that the repository returns the correct news items');
|
||||
$newsItem3->setStartDate(new \DateTimeImmutable('2033-11-03'));
|
||||
$newsItem3->setEndDate(null);
|
||||
|
||||
$em->persist($newsItem1);
|
||||
$em->persist($newsItem2);
|
||||
$em->persist($newsItem3);
|
||||
$em->flush();
|
||||
|
||||
// Call the method to test
|
||||
$result = $repository->findCurrentNews();
|
||||
|
||||
// Assertions
|
||||
$this->assertCount(2, $result);
|
||||
$this->assertInstanceOf(NewsItem::class, $result[0]);
|
||||
$this->assertContains($newsItem2, $result);
|
||||
$this->assertContains($newsItem3, $result);
|
||||
}
|
||||
}
|
@ -892,11 +892,11 @@ paths:
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
|
||||
/1.0/main/news.json:
|
||||
/1.0/main/news/current.json:
|
||||
get:
|
||||
tags:
|
||||
- news items
|
||||
summary: Returns a list of news items
|
||||
summary: Returns a list of news items which are valid
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
|
@ -47,6 +47,8 @@ services:
|
||||
|
||||
Chill\MainBundle\Templating\Entity\AddressRender: ~
|
||||
|
||||
Chill\MainBundle\Templating\Entity\NewsItemRender: ~
|
||||
|
||||
Chill\MainBundle\Templating\Entity\UserRender: ~
|
||||
|
||||
Chill\MainBundle\Templating\Listing\:
|
||||
|
@ -442,7 +442,11 @@ crud:
|
||||
title: Liste des actualités
|
||||
add_new: Créer une nouvelle actualité
|
||||
title_new: Nouvelle actualité
|
||||
title_view: Voir l'actualité
|
||||
title_edit: Modifier une actualité
|
||||
title_delete: Supprimer une actualité
|
||||
button_delete: Supprimer
|
||||
confirm_message_delete: Êtes-vous sûr de vouloir supprimer l'actualité, "%as_string%" ?
|
||||
|
||||
No entities: Aucun élément
|
||||
|
||||
@ -693,10 +697,11 @@ admin:
|
||||
news:
|
||||
noDate: Pas de date de fin
|
||||
startDate: Date de début
|
||||
endDate: Date de fin
|
||||
endDate: Date de fin de publication sur la page d'accueil
|
||||
title: Historique des actualités
|
||||
menu: Actualités
|
||||
no_data: Aucune actualité
|
||||
read_more: Lire la suite
|
||||
show_details: Voir l'actualité
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user