inline css for html invoices (#1526)

This commit is contained in:
Kevin Papst
2020-03-06 03:21:39 +01:00
committed by GitHub
parent 76a6ea57a1
commit a50a59bbca
25 changed files with 270 additions and 69 deletions

View File

@@ -62,7 +62,8 @@ const Sortable = require('sortablejs/Sortable.min');
global.Sortable = Sortable;
// ------ AdminLTE framework ------
require('./sass/admin-lte.scss');
require('./sass/bootstrap.scss');
require('./sass/fontawesome.scss');
require('admin-lte/dist/css/AdminLTE.min.css');
require('admin-lte/dist/css/skins/_all-skins.css');
require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte-extensions.scss');

2
assets/invoice.js Normal file
View File

@@ -0,0 +1,2 @@
require('./sass/invoice.scss');

View File

@@ -14,7 +14,6 @@
@import 'toolbar';
@import 'sidebar';
@import 'footer';
@import 'invoice';
@import 'datatable';
@import 'datatable-extensions';
@import 'calendar';

9
assets/sass/bootstrap.scss vendored Normal file
View File

@@ -0,0 +1,9 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
@import '~bootstrap-sass/assets/stylesheets/bootstrap';

View File

@@ -1,3 +1,4 @@
@import '~daterangepicker/daterangepicker.css';
.drp-calendar {
.calendar-table {

View File

@@ -1,10 +1,12 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
@import '~bootstrap-sass/assets/stylesheets/bootstrap';
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';
@import '~daterangepicker/daterangepicker.css';

View File

@@ -5,9 +5,31 @@
* file that was distributed with this source code.
*/
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/type";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/list-group";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/panels";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/wells";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";
body.invoice_print {
background-color: #eee;
.table.no-border, .table.no-border td, .table.no-border th {
border: 0;
}
h1, h2, h3 {
font-family: 'Source Sans Pro', sans-serif;
}
.invoice {
margin: 105px auto 30px auto;
padding: 50px 65px;
@@ -15,17 +37,33 @@ body.invoice_print {
position: relative;
max-width: 210mm;
box-shadow: 0 0 20px rgba(80,80,80,0.7);
background: #fff;
border: 1px solid #f4f4f4;
}
.page-header h3 {
border-color: #ddd;
.page-header {
margin: 10px 0 20px 0;
font-size: 22px;
>small {
color: #666;
display: block;
margin-top: 5px;
}
h3 {
border-color: #ddd;
}
}
/*
.page-header {
margin: 10px 0 20px 0;
font-size: 22px;
}
*/
div.invoice-address {
margin-top: 30px;
margin-bottom: 30px;
}
table.invoice-meta th {
padding-right: 40px;
}
@@ -35,11 +73,11 @@ body.invoice_print {
table.invoice-sum th, table.invoice-sum td {
text-align: right;
}
.invoice-items {
margin-top: 2em;
margin-bottom: 3em;
.table {
thead th {
font-weight: bold;
@@ -48,7 +86,7 @@ body.invoice_print {
}
tfoot {
border-top: 1px solid #ddd;
tr:first-child th,
tr:first-child td {
padding-top: 15px
@@ -61,7 +99,7 @@ body.invoice_print {
}
}
}
}
.footer {
@@ -73,36 +111,36 @@ body.invoice_print {
}
/* ================================ FREELANCER INVOICE ================================ */
#freelancer-invoice {
box-sizing: border-box;
h1 {
font: bold 100% sans-serif;
letter-spacing: 0.5em;
text-align: center;
text-transform: uppercase;
}
h2 {
font: bold 1.5em sans-serif;
margin-bottom: 1em;
}
/* table */
table {
width: 100%;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* header */
header {
&:after {
clear: both;
@@ -110,7 +148,7 @@ body.invoice_print {
display: table;
font-size: 1em;
}
address {
float: left;
font-style: normal;
@@ -119,7 +157,7 @@ body.invoice_print {
font-size: 0.7em;
}
}
article address {
font-size: 1em;
float: left;
@@ -136,44 +174,44 @@ body.invoice_print {
content: "";
display: table;
}
article h1 {
clip: rect(0 0 0 0);
position: absolute;
}
article p {
margin-bottom: 20px;
}
/* table meta & balance */
table.meta:after, table.balance:after {
clear: both;
content: "";
display: table;
}
/* table meta */
table.meta {
float: right;
width: 50%;
font-size: 85%;
th {
text-align: right;
font-weight: normal;
}
td {
width: 120px;
text-align: right;
}
}
/* table balance */
table.balance {
float: right;
margin-top: 1em;
@@ -181,43 +219,43 @@ body.invoice_print {
text-align: right;
font-weight: normal;
}
td {
width: 140px;
text-align: right;
line-height: 22px;
}
.total {
font-weight: bold;
}
}
/* table items */
table.inventory {
clear: both;
width: 100%;
margin-top: 2em;
border-bottom: 1px solid #000;
}
table.inventory th,
table.inventory td {
padding: 5px;
}
table.inventory thead th {
font-weight: bold;
border-width: 0 0 1px 0;
border-bottom: 1px solid #000;
padding-bottom: 15px
}
table.inventory tbody tr:first-child td {
padding-top: 15px
}
table.inventory tbody tr:last-child td {
padding-bottom: 15px
}

View File

@@ -3,13 +3,10 @@
}
.swal2-container {
top: 95px !important;
// never block the top two bars double-bars display (smaller displays)
@media only screen and (max-width: 768px) {
top: 110px !important;
}
// never block the top bar on single-bar display (larger devices)
@media only screen and (min-width: 768px) {
top: 60px !important;
width: 80%;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,10 +5,19 @@
"build/runtime.664a9501.js",
"build/0.f7fae2b9.js",
"build/1.c24a9c6f.js",
"build/app.a2f9e47b.js"
"build/app.c145a319.js"
],
"css": [
"build/app.761d11cb.css"
"build/app.cc7cd665.css"
]
},
"invoice": {
"js": [
"build/runtime.664a9501.js",
"build/invoice.50473330.js"
],
"css": [
"build/invoice.3764169b.css"
]
},
"chart": {
@@ -24,10 +33,10 @@
"build/runtime.664a9501.js",
"build/0.f7fae2b9.js",
"build/1.c24a9c6f.js",
"build/calendar.75c80d8f.js"
"build/calendar.ed1bc977.js"
],
"css": [
"build/calendar.ade7bcdf.css"
"build/calendar.8ede69b5.css"
]
}
},
@@ -35,11 +44,13 @@
"build/runtime.664a9501.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
"build/0.f7fae2b9.js": "sha384-DR5A41RIECdWBd6xODR0b1hvGqcEYUyn9vNPreqK9VOCQWTF6bgxB4RVmqlOKilT",
"build/1.c24a9c6f.js": "sha384-JPoKdrVtBemSiVBoAnmSxLML7xXM9zYeuwOPYQv/kLzt/P4cmLY5r9gH8oaGRPFG",
"build/app.a2f9e47b.js": "sha384-ZWdZQRG+Wt/75uEbUoHJ/HvnebZeVxcCVRgXTzzB/UAC/QuF82yFVReHFLZobuc6",
"build/app.761d11cb.css": "sha384-oJNwsNVNsMab+doks+SnE7/iM2Yg6KQRfpd1wa83ZYG2k9YwHVimtjC81llk5jMB",
"build/app.c145a319.js": "sha384-Pz9lT9FyrDn3Acp3DVntiPSiweHoEeOQQI+igDYb9tnT3exSuVGiMu9wZIrDBiWA",
"build/app.cc7cd665.css": "sha384-PRe5N/ABLWoC7r1k1vPNiZ7T65q0fnfCZMEWqUk5VIXAu+v3lnzgMMEhKubn2M0G",
"build/invoice.50473330.js": "sha384-2BXic5Sgorf2tXai6zSAN4wLY2dbg06L03/xMKW6itMcszvtnRArKzfBh6DNcF3f",
"build/invoice.3764169b.css": "sha384-oILxBeIu3sNXxVnxhHmyHxqvLY2Rm5P2lzuJVtbxBIcnSdLS31SUSW6nVWNej1eg",
"build/2.a47ad919.js": "sha384-1h2P/tsl+bh8qgJd7S9irQHKuns7UATVxeFGLOCH85GPFXfAdRzgzx3nk5MrxzrV",
"build/chart.c9943f57.js": "sha384-I57c9DtU3AOG2kzKqIZkIu0hi1aGYHRZ5QG4LKC9+9slzJnAMttPGXoL2cQG3m6y",
"build/calendar.75c80d8f.js": "sha384-veJqOVIfhaWjL5e0RMZdvVuMXofe+k5HsBhfChyR1mrXgK+LcwRtXKJT905mwZ+e",
"build/calendar.ade7bcdf.css": "sha384-ta519W5+NY1a8FBh7rD4zORYTfUXCYTLci0Z25nf4BfodmuXqyPVNEe7dSe2/fRd"
"build/calendar.ed1bc977.js": "sha384-veJqOVIfhaWjL5e0RMZdvVuMXofe+k5HsBhfChyR1mrXgK+LcwRtXKJT905mwZ+e",
"build/calendar.8ede69b5.css": "sha384-ta519W5+NY1a8FBh7rD4zORYTfUXCYTLci0Z25nf4BfodmuXqyPVNEe7dSe2/fRd"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["invoice"],{AXCM:function(n,i,o){},OMhV:function(n,i,o){o("AXCM")}},[["OMhV","runtime"]]]);

View File

@@ -2,11 +2,13 @@
"build/0.f7fae2b9.js": "build/0.f7fae2b9.js",
"build/1.c24a9c6f.js": "build/1.c24a9c6f.js",
"build/2.a47ad919.js": "build/2.a47ad919.js",
"build/app.css": "build/app.761d11cb.css",
"build/app.js": "build/app.a2f9e47b.js",
"build/calendar.css": "build/calendar.ade7bcdf.css",
"build/calendar.js": "build/calendar.75c80d8f.js",
"build/app.css": "build/app.cc7cd665.css",
"build/app.js": "build/app.c145a319.js",
"build/calendar.css": "build/calendar.8ede69b5.css",
"build/calendar.js": "build/calendar.ed1bc977.js",
"build/chart.js": "build/chart.c9943f57.js",
"build/invoice.css": "build/invoice.3764169b.css",
"build/invoice.js": "build/invoice.50473330.js",
"build/runtime.js": "build/runtime.664a9501.js",
"build/fonts/fa-brands-400.eot": "build/fonts/fa-brands-400.088a34f7.eot",
"build/fonts/fa-brands-400.ttf": "build/fonts/fa-brands-400.273dc9bf.ttf",

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Twig;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class EncoreExtension extends AbstractExtension implements ServiceSubscriberInterface
{
/**
* @var EntrypointLookupInterface
*/
private $encoreService;
/**
* @var string
*/
private $publicDir;
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container, string $projectDirectory)
{
$this->container = $container;
$this->publicDir = $projectDirectory . '/public';
}
public static function getSubscribedServices()
{
return [
EntrypointLookupInterface::class,
];
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return [
new TwigFunction('encore_entry_css_source', [$this, 'getEncoreEntryCssSource']),
];
}
public function getEncoreEntryCssSource(string $packageName): string
{
$files = $this->container
->get(EntrypointLookupInterface::class)->getCssFiles($packageName);
$source = '';
foreach ($files as $file) {
$source .= file_get_contents($this->publicDir . '/' . $file);
}
return $source;
}
}

View File

@@ -5,8 +5,9 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<title>{% block title %}{{ model.numberGenerator.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
{{ encore_entry_link_tags('app') }}
{{ encore_entry_script_tags('app') }}
<style type="text/css">
{{ encore_entry_css_source('invoice')|raw }}
</style>
</head>
<body class="invoice_print">
<div class="wrapper">

View File

@@ -81,7 +81,7 @@
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
{% endif %}
</td>
<td>{{ entry.duration|duration(isDecimal) }}</td>
<td class="text-nowrap">{{ entry.duration|duration(isDecimal) }}</td>
</tr>
{% endfor %}
</tbody>
@@ -92,7 +92,7 @@
<th></th>
{% endif %}
<th>{{ 'invoice.total_working_time'|trans({}, 'messages', language) }}</th>
<th>{{ model.calculator.timeWorked|duration(isDecimal) }}</th>
<th class="text-nowrap">{{ model.calculator.timeWorked|duration(isDecimal) }}</th>
</tr>
</tfoot>
</table>

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Twig;
use App\Twig\EncoreExtension;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface;
use Twig\TwigFunction;
/**
* @covers \App\Twig\EncoreExtension
*/
class EncoreExtensionTest extends TestCase
{
protected function getSut(array $files = []): EncoreExtension
{
$entryLookup = $this->createMock(EntrypointLookupInterface::class);
$entryLookup->expects($this->any())->method('getCssFiles')->willReturn($files);
$container = new Container(new ParameterBag([]));
$container->set(EntrypointLookupInterface::class, $entryLookup);
return new EncoreExtension($container, __DIR__);
}
public function testGetSubscribedServices()
{
self::assertEquals([EntrypointLookupInterface::class], EncoreExtension::getSubscribedServices());
}
public function testGetFunctions()
{
$functions = ['encore_entry_css_source'];
$sut = $this->getSut();
$twigFunctions = $sut->getFunctions();
self::assertCount(count($functions), $twigFunctions);
$i = 0;
/** @var TwigFunction $filter */
foreach ($twigFunctions as $filter) {
self::assertInstanceOf(TwigFunction::class, $filter);
self::assertEquals($functions[$i++], $filter->getName());
}
}
public function testGetEncoreEntryCssSource()
{
$sut = $this->getSut(['test.css', 'test1.css']);
$css = 'body { margin: 0; }p
{
color: red; font-style: italic; }';
self::assertEquals($css, $sut->getEncoreEntryCssSource('blub'));
}
}

View File

@@ -0,0 +1 @@
body { margin: 0; }

View File

@@ -0,0 +1,3 @@
p
{
color: red; font-style: italic; }

View File

@@ -10,6 +10,7 @@ Encore
.cleanupOutputBeforeBuild()
.addEntry('app', './assets/app.js')
.addEntry('invoice', './assets/invoice.js')
.addEntry('chart', './assets/chart.js')
.addEntry('calendar', './assets/calendar.js')
.copyFiles({ from: './assets/images', to: 'images/[path][name].[ext]' })