Charly 3 роки тому
джерело
коміт
b3d67d1980
18 змінених файлів з 11629 додано та 11205 видалено
  1. +2
    -1
      assets/app/frontend/scss/carto-liste.scss
  2. +1
    -0
      composer.json
  3. +207
    -1
      composer.lock
  4. +8
    -0
      config/routes.yaml
  5. +1
    -0
      config/services.yaml
  6. +21
    -0
      public/assets/css/pdf.css
  7. +0
    -11151
      public/build/app-frontend.2fba4f18.css
  8. +11152
    -0
      public/build/app-frontend.94f1a107.css
  9. +1
    -1
      public/build/entrypoints.json
  10. +1
    -1
      public/build/manifest.json
  11. +142
    -38
      src/Controller/Frontend/CartoController.php
  12. +6
    -0
      src/Form/SearchListForm.php
  13. +21
    -0
      src/Repository/SearchRepositoryQuery.php
  14. +3
    -3
      src/Repository/SearchStore.php
  15. +12
    -0
      symfony.lock
  16. +6
    -7
      templates/frontend/carto-liste.html.twig
  17. +2
    -2
      templates/frontend/carto.html.twig
  18. +43
    -0
      templates/frontend/pdf.html.twig

+ 2
- 1
assets/app/frontend/scss/carto-liste.scss Переглянути файл

@@ -84,7 +84,7 @@

.navigation {
text-align: center;
margin-top: 25px;
margin-top: 15px;

.pagination {
display: inline;
@@ -120,6 +120,7 @@
margin-top: 25px;
padding-left: 10px;
padding-right: 10px;
border: 0;
}

.export-excel {

+ 1
- 0
composer.json Переглянути файл

@@ -13,6 +13,7 @@
"doctrine/doctrine-bundle": "^2.3",
"doctrine/doctrine-migrations-bundle": "^3.1",
"doctrine/orm": "^2.8",
"dompdf/dompdf": "^1.0",
"easycorp/easyadmin-bundle": "^3.3",
"friendsofsymfony/ckeditor-bundle": "^2.3",
"knplabs/knp-paginator-bundle": "^5.6",

+ 207
- 1
composer.lock Переглянути файл

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1e01a9e34309d6c393cefe735e0d1baa",
"content-hash": "4f261c49c4e3d64b08acfca7246f15bf",
"packages": [
{
"name": "artgris/filemanager-bundle",
@@ -1705,6 +1705,78 @@
},
"time": "2020-07-30T16:57:33+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "8768448244967a46d6e67b891d30878e0e15d25c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/8768448244967a46d6e67b891d30878e0e15d25c",
"reference": "8768448244967a46d6e67b891d30878e0e15d25c",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-mbstring": "*",
"phenx/php-font-lib": "^0.5.2",
"phenx/php-svg-lib": "^0.3.3",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9",
"squizlabs/php_codesniffer": "^3.5"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-develop": "0.7-dev"
}
},
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "Fabien Ménager",
"email": "fabien.menager@gmail.com"
},
{
"name": "Brian Sweeney",
"email": "eclecticgeek@gmail.com"
},
{
"name": "Gabriel Bull",
"email": "me@gabrielbull.com"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v1.0.2"
},
"time": "2021-01-08T14:18:52+00:00"
},
{
"name": "easycorp/easyadmin-bundle",
"version": "v3.5.7",
@@ -2720,6 +2792,91 @@
},
"time": "2020-08-11T21:06:11+00:00"
},
{
"name": "phenx/php-font-lib",
"version": "0.5.2",
"source": {
"type": "git",
"url": "https://github.com/PhenX/php-font-lib.git",
"reference": "ca6ad461f032145fff5971b5985e5af9e7fa88d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/ca6ad461f032145fff5971b5985e5af9e7fa88d8",
"reference": "ca6ad461f032145fff5971b5985e5af9e7fa88d8",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5 || ^6 || ^7"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Fabien Ménager",
"email": "fabien.menager@gmail.com"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/PhenX/php-font-lib",
"support": {
"issues": "https://github.com/PhenX/php-font-lib/issues",
"source": "https://github.com/PhenX/php-font-lib/tree/0.5.2"
},
"time": "2020-03-08T15:31:32+00:00"
},
{
"name": "phenx/php-svg-lib",
"version": "v0.3.3",
"source": {
"type": "git",
"url": "https://github.com/PhenX/php-svg-lib.git",
"reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/5fa61b65e612ce1ae15f69b3d223cb14ecc60e32",
"reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32",
"shasum": ""
},
"require": {
"sabberworm/php-css-parser": "^8.3"
},
"require-dev": {
"phpunit/phpunit": "^5.5|^6.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Fabien Ménager",
"email": "fabien.menager@gmail.com"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/PhenX/php-svg-lib",
"support": {
"issues": "https://github.com/PhenX/php-svg-lib/issues",
"source": "https://github.com/PhenX/php-svg-lib/tree/master"
},
"time": "2019-09-11T20:02:13+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@@ -3127,6 +3284,55 @@
},
"time": "2021-05-03T11:20:27+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "8.3.1",
"source": {
"type": "git",
"url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
"reference": "d217848e1396ef962fb1997cf3e2421acba7f796"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/d217848e1396ef962fb1997cf3e2421acba7f796",
"reference": "d217848e1396ef962fb1997cf3e2421acba7f796",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"codacy/coverage": "^1.4",
"phpunit/phpunit": "~4.8"
},
"type": "library",
"autoload": {
"psr-0": {
"Sabberworm\\CSS": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
"source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.3.1"
},
"time": "2020-06-01T09:10:00+00:00"
},
{
"name": "sensio/framework-extra-bundle",
"version": "v5.6.1",

+ 8
- 0
config/routes.yaml Переглянути файл

@@ -26,6 +26,14 @@ app_carto_liste:
path: /cartographie-list
controller: App\Controller\Frontend\CartoController::cartoListe

app_export_csv:
path: /export-csv
controller: App\Controller\Frontend\CartoController::exportCsv

app_export_pdf:
path: /export-pdf
controller: App\Controller\Frontend\CartoController::exportPdf

app_carto_carte:
path: /cartographie-carte
controller: App\Controller\Frontend\CartoController::cartoCarte

+ 1
- 0
config/services.yaml Переглянути файл

@@ -9,6 +9,7 @@ parameters:
app.path_uploads: '/uploads'
app.admin.logo: 'Logo-Aux-Actes-Citoyens.jpg'
app.reminder.route_render_modal: 'sov_admin_reminder_render_modal'
app.assets_directory: '%kernel.project_dir%/public/assets/'

services:
# default configuration for services in *this* file

+ 21
- 0
public/assets/css/pdf.css Переглянути файл

@@ -0,0 +1,21 @@
.table-search {
border: 1px solid #568b6f;
}

thead {
border-bottom: 1px solid #568b6f;
}

th {
color: #568b6f;
background-color: #d0dfd7;
padding: 40px 20px 20px 20px;
}

.table-search tbody tr:nth-child(2n+1) {
background-color: #dcdcdc;
}

.table-search tbody td {
padding: 5px 10px 5px 10px;
}

+ 0
- 11151
public/build/app-frontend.2fba4f18.css
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 11152
- 0
public/build/app-frontend.94f1a107.css
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 1
- 1
public/build/entrypoints.json Переглянути файл

@@ -121,7 +121,7 @@
"/build/app-frontend.9cc512c2.js"
],
"css": [
"/build/app-frontend.2fba4f18.css"
"/build/app-frontend.94f1a107.css"
]
}
}

+ 1
- 1
public/build/manifest.json Переглянути файл

@@ -17,7 +17,7 @@
"build/sov-ticket.js": "/build/sov-ticket.df76c7a0.js",
"build/app-backend.css": "/build/app-backend.3d882954.css",
"build/app-backend.js": "/build/app-backend.e87ef456.js",
"build/app-frontend.css": "/build/app-frontend.2fba4f18.css",
"build/app-frontend.css": "/build/app-frontend.94f1a107.css",
"build/app-frontend.js": "/build/app-frontend.9cc512c2.js",
"build/runtime.js": "/build/runtime.3095b250.js",
"build/vendors-node_modules_core-js_internals_export_js.2e873f33.js": "/build/vendors-node_modules_core-js_internals_export_js.2e873f33.js",

+ 142
- 38
src/Controller/Frontend/CartoController.php Переглянути файл

@@ -8,9 +8,14 @@ use App\Repository\ProjectBoostStore;
use App\Repository\ProjectInspiringStore;
use App\Repository\RevoltStore;
use Doctrine\ORM\EntityManagerInterface;
use Dompdf\Dompdf;
use Dompdf\Options;
use Knp\Component\Pager\PaginatorInterface;
use Lc\SovBundle\Generator\CsvGenerator;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

class CartoController extends DefaultController
{
@@ -20,6 +25,8 @@ class CartoController extends DefaultController
protected ProjectBoostStore $projectBoostStore;
protected ProjectInspiringStore $projectInspiringStore;
protected PaginatorInterface $paginator;
protected Environment $templating;
protected ParameterBagInterface $parameterBag;

public function __construct(
EntityManagerInterface $em,
@@ -27,7 +34,9 @@ class CartoController extends DefaultController
RevoltStore $revoltStore,
ProjectBoostStore $projectBoostStore,
ProjectInspiringStore $projectInspiringStore,
PaginatorInterface $paginator
PaginatorInterface $paginator,
Environment $templating,
ParameterBagInterface $parameterBag
) {
$this->em = $em;
$this->dreamStore = $dreamStore;
@@ -35,6 +44,8 @@ class CartoController extends DefaultController
$this->projectBoostStore = $projectBoostStore;
$this->projectInspiringStore = $projectInspiringStore;
$this->paginator = $paginator;
$this->templating = $templating;
$this->parameterBag = $parameterBag;
}

public function cartoInteractive()
@@ -49,47 +60,29 @@ class CartoController extends DefaultController

public function cartoListe(Request $request)
{
$dreamArray = $revoltArray = $projectBoostArray = $projectInspiringArray = $resultArray = $resultArrayPagination = array();
$resultArrayPagination = array();

$form = $this->createForm(SearchListForm::class);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$page = $data['page'];
$description = $data['search'];
$categoryArray = $data['category'];
$territoryArray = $data['territory'];
$thematicArray = $data['thematic'];

if (in_array('dream', $categoryArray) || empty($categoryArray)) {
$dreamArray = $this->dreamStore->filterSearch($description, $territoryArray, $thematicArray);
}
if (in_array('revolt', $categoryArray) || empty($categoryArray)) {
$revoltArray = $this->revoltStore->filterSearch($description, $territoryArray, $thematicArray);
}
if (in_array('projectBoost', $categoryArray) || empty($categoryArray)) {
$projectBoostArray = $this->projectBoostStore->filterSearch(
$description,
$territoryArray,
$thematicArray
);
}
if (in_array('projectInspiring', $categoryArray) || empty($categoryArray)) {
$projectInspiringArray = $this->projectInspiringStore->filterSearch(
$description,
$territoryArray,
$thematicArray
);
}

$resultArray = array_merge($dreamArray, $revoltArray, $projectBoostArray, $projectInspiringArray);
$resultArray = $this->generateResultArray($data);

$resultArrayPagination = $this->paginator->paginate(
$resultArray,
$page,
10
);
if ($form->get('search_button')->isClicked()) {
$page = $data['page'];

$resultArrayPagination = $this->paginator->paginate(
$resultArray,
$page,
10
);
} elseif ($form->get('export_excel')->isClicked()) {
return $this->exportCsv($resultArray);
} elseif ($form->get('export_pdf')->isClicked()) {
return $this->exportPdf($resultArray);
}
}

return $this->render(
@@ -102,6 +95,39 @@ class CartoController extends DefaultController
);
}

private function generateResultArray($data): array
{
$dreamArray = $revoltArray = $projectBoostArray = $projectInspiringArray = array();

$subthematic = $data['search'];
$categoryArray = $data['category'];
$territoryArray = $data['territory'];
$thematicArray = $data['thematic'];

if (in_array('dream', $categoryArray) || empty($categoryArray)) {
$dreamArray = $this->dreamStore->filterSearch($subthematic, $territoryArray, $thematicArray);
}
if (in_array('revolt', $categoryArray) || empty($categoryArray)) {
$revoltArray = $this->revoltStore->filterSearch($subthematic, $territoryArray, $thematicArray);
}
if (in_array('projectBoost', $categoryArray) || empty($categoryArray)) {
$projectBoostArray = $this->projectBoostStore->filterSearch(
$subthematic,
$territoryArray,
$thematicArray
);
}
if (in_array('projectInspiring', $categoryArray) || empty($categoryArray)) {
$projectInspiringArray = $this->projectInspiringStore->filterSearch(
$subthematic,
$territoryArray,
$thematicArray
);
}

return array_merge($dreamArray, $revoltArray, $projectBoostArray, $projectInspiringArray);
}

public function cartoCarte()
{
return $this->render(
@@ -112,7 +138,43 @@ class CartoController extends DefaultController
);
}

public function exportListCsv()
public function exportPdf($resultArray = array())
{
// Configure Dompdf according to your needs
$pdfOptions = new Options();
$pdfOptions->set('defaultFont', 'Arial');

// Instantiate Dompdf with our options
$dompdf = new Dompdf($pdfOptions);

if (empty($resultArray)) {
$resultArray = $this->generateAllResultArray();
}

// Retrieve the HTML generated in our twig
$html = $this->templating->render('frontend/pdf.html.twig', [
'resultArray' => $resultArray,
'css' => file_get_contents(
$this->parameterBag->get('app.assets_directory') . 'css/pdf.css'
)
]);

// return new Response($html);
// Load HTML to Dompdf
$dompdf->loadHtml($html);

// (Optional) Setup the paper size and orientation 'portrait' or 'portrait'
$dompdf->setPaper('A4', 'portrait');


// Render the HTML as PDF
$dompdf->render();
$dompdf->stream('Export-pdf.pdf', [
"Attachment" => true
]);
}

public function exportCsv($resultArray = array())
{
$csv = new CsvGenerator();
$csv->enableConvertEncoding('ISO-8859-1');
@@ -126,11 +188,53 @@ class CartoController extends DefaultController
];
$csv->setColumns($columns);

$data = [
if (empty($resultArray)) {
$resultArray = $this->generateAllResultArray();
}

];
$csv->row($data);
$csv = $this->generateCsvData($csv, $resultArray);

return $csv->getReponse();
}

private function generateAllResultArray(): array
{
$dreamArray = $this->dreamStore->get();
$revoltArray = $this->revoltStore->get();
$projectBoostArray = $this->projectBoostStore->get();
$projectInspiringArray = $this->projectInspiringStore->get();

return array_merge($dreamArray, $revoltArray, $projectBoostArray, $projectInspiringArray);
}

private function generateCsvData($csv, $resultArray)
{
foreach ($resultArray as $result) {
$territory = $subthematic = $thematic = "";
if ($result->getIndividualData()) {
$territory = $result->getIndividualData()->getTerritory()->getName();
} elseif ($result->getCollectifData()->getTerritory()) {
$territory = $result->getCollectifData()->getTerritory()->getName();
}

if ($result->getSubthematic()) {
$subthematic = $result->getSubthematic()->getName();
}

if ($result->getThematic()) {
$thematic = $result->getThematic()->getName();
}

$data = [
'category' => $result->__toString(),
'thematic' => $thematic,
'subthematic' => $subthematic,
'territory' => $territory
];

$csv->row($data);
}

return $csv;
}
}

+ 6
- 0
src/Form/SearchListForm.php Переглянути файл

@@ -59,6 +59,12 @@ class SearchListForm extends AbstractType
])
->add('search_button', SubmitType::class, [
'attr' => ['class' => 'search-button'],
])
->add('export_excel', SubmitType::class, [
'attr' => ['class' => 'export export-excel'],
])
->add('export_pdf', SubmitType::class, [
'attr' => ['class' => 'export export-pdf'],
]);
}


+ 21
- 0
src/Repository/SearchRepositoryQuery.php Переглянути файл

@@ -12,6 +12,7 @@ class SearchRepositoryQuery extends AbstractRepositoryQuery implements Repositor
{
protected $isJoinCollectifData = false;
protected $isJoinIndividualData = false;
protected $isJoinSubthematic = false;

public function __construct($repository, PaginatorInterface $paginator)
{
@@ -34,6 +35,15 @@ class SearchRepositoryQuery extends AbstractRepositoryQuery implements Repositor
->setParameter(':thematic', $thematicArray);
}

public function filterBySubthematicName(string $subthematic): self
{
$this->joinSubthematic();

return $this
->andWhere('subT.name LIKE :subthematic')
->setParameter(':subthematic', '%' . $subthematic . '%');
}

public function filterByDescription(string $description): self
{
return $this
@@ -51,6 +61,17 @@ class SearchRepositoryQuery extends AbstractRepositoryQuery implements Repositor
->setParameter(':territory', $territoryArray);
}

public function joinSubthematic(): self
{
if (!$this->isJoinSubthematic) {
$this->isJoinSubthematic = true;

return $this
->leftJoin('.subthematic', 'subT');
}
return $this;
}

public function joinCollectifData(): self
{
if (!$this->isJoinCollectifData) {

+ 3
- 3
src/Repository/SearchStore.php Переглянути файл

@@ -34,7 +34,7 @@ class SearchStore extends AbstractStore implements StoreInterface
}

public function filterSearch(
?string $description,
?string $subthematic,
ArrayCollection $territoryArray,
ArrayCollection $thematicArray,
$query = null
@@ -47,8 +47,8 @@ class SearchStore extends AbstractStore implements StoreInterface
if (!($territoryArray->isEmpty())) {
$query->filterByTerritory($territoryArray);
}
if (!empty($description)) {
$query->filterByDescription($description);
if (!empty($subthematic)) {
$query->filterBySubthematicName($subthematic);
}

return $query->find();

+ 12
- 0
symfony.lock Переглянути файл

@@ -91,6 +91,9 @@
"doctrine/sql-formatter": {
"version": "1.1.1"
},
"dompdf/dompdf": {
"version": "v1.0.2"
},
"easycorp/easyadmin-bundle": {
"version": "3.0",
"recipe": {
@@ -151,6 +154,12 @@
"oomphinc/composer-installers-extender": {
"version": "2.0.0"
},
"phenx/php-font-lib": {
"version": "0.5.2"
},
"phenx/php-svg-lib": {
"version": "v0.3.3"
},
"phpdocumentor/reflection-common": {
"version": "2.2.0"
},
@@ -175,6 +184,9 @@
"psr/log": {
"version": "1.1.4"
},
"sabberworm/php-css-parser": {
"version": "8.3.1"
},
"sensio/framework-extra-bundle": {
"version": "5.2",
"recipe": {

+ 6
- 7
templates/frontend/carto-liste.html.twig Переглянути файл

@@ -23,6 +23,12 @@
{{ form_row(form.search_button, { 'label': 'Rechercher' }) }}
</div>
</div>
<div class="row">
<div class="col">
{{ form_widget(form.export_excel, { 'label': 'Exporter au format Excel' }) }}
{{ form_widget(form.export_pdf, { 'label': 'Exporter au format PDF' }) }}
</div>
</div>
{{ form_end(form) }}

{% if resultArray %}
@@ -30,13 +36,6 @@
{{ knp_pagination_render(resultArray) }}
</div>

<div class="row">
<div class="col">
<a href="" class="export export-excel" title="Exporter Excel">Exporter au format Excel</a>
<a href="" class="export export-pdf" title="Exporter au format PDF">Exporter au format PDF</a>
</div>
</div>

<table class="table-search">
<thead>
<tr>

+ 2
- 2
templates/frontend/carto.html.twig Переглянути файл

@@ -74,12 +74,12 @@

<div class="row">
<div class="col-6">
<a class="download excel" href="" title="Télécharger">
<a class="download excel" href="{{ path('app_export_csv') }}" title="Télécharger">
EXCEL
</a>
</div>
<div class="col-6">
<a class="download pdf" href="" title="Télécharger">
<a class="download pdf" href="{{ path('app_export_pdf') }}" title="Télécharger">
PDF
</a>
</div>

+ 43
- 0
templates/frontend/pdf.html.twig Переглянути файл

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
{{ css }}
</style>
</head>
<body>
<table class="table-search">
<thead>
<tr>
<th>Catégorie</th>
<th>Thématique</th>
<th>Contribution</th>
<th>Lieu</th>
</tr>
</thead>
<tbody>
{% for result in resultArray %}
<tr>
<td>
{{ result }}
</td>
<td>
{{ result.thematic }}
</td>
<td>
{{ result.subthematic }}
</td>
<td>
{% if result.individualData is not empty %}
{{ result.individualData.territory }}
{% elseif result.collectifData is not empty %}
{{ result.collectifData.territory }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

Завантаження…
Відмінити
Зберегти