add new table view

This commit is contained in:
2026-07-08 14:47:53 +02:00
parent a23f3587ab
commit 8b3f3e4608
6 changed files with 213 additions and 80 deletions
+3 -1
View File
@@ -51,6 +51,7 @@ export default class extends Controller {
this.resultTarget.innerHTML = '';
this.hideError();
this.stepValue = 1;
// window.history.pushState(null, null, "/");
}
// ---- pliki ----
@@ -93,7 +94,8 @@ export default class extends Controller {
const res = await fetch(this.endpointUrl, { method: 'POST', body: fd });
if (!res.ok) throw new Error('Błąd serwera (' + res.status + ')');
// const hashSummary = res.url.replace(/^https?:\/\/[^/]+\//, "/").replace(this.endpointUrl, "");
// window.history.pushState(null, null, hashSummary);
this.resultTarget.innerHTML = await res.text();
this.stepValue = 3;
} catch (err) {
+2 -1
View File
@@ -1,3 +1,4 @@
@import "table";
.main-container {
font-family: 'Public Sans', system-ui, sans-serif;
color: #1C1C1A;
@@ -12,7 +13,7 @@
.card {
position: relative;
width: 100%;
max-width: 660px;
max-width: 750px;
background: #FBFBF9;
border: 1px solid #E6E5E0;
border-radius: 18px;
+38
View File
@@ -0,0 +1,38 @@
.summaryTable {
.table-head { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
.table-title { font-size: 13px; font-weight: 600; }
.table-wrap {
border: 1px solid #E6E5E0; border-radius: 10px; overflow-x: auto; background: #FFFFFF;
table { width: 100%; border-collapse: collapse; font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 11.5px; }
th {
padding: 9px; font-weight: 600; color: #6B6A64; font-size: 10px;
text-transform: uppercase; letter-spacing: .03em;
border-bottom: 1px solid #E6E5E0; white-space: nowrap; background: #FAFAF7;
}
td { padding: 9px; border-bottom: 1px solid #F0EFEB; white-space: nowrap; }
tfoot tr { background: #FAFAF7; }
.foot-label {
font-family: 'Public Sans', sans-serif; font-size: 12px; font-weight: 600;
color: #6B6A64; text-transform: uppercase; letter-spacing: .04em;
}
.foot-value { text-align: right; font-weight: 700; color: #1C1C1A; font-size: 14px; }
}
.note { margin-top: 10px; font-size: 12px; color: #6B6A64; }
.actions { display: flex; gap: 10px; margin-top: 22px; }
.btn-accent {
color: #FFFFFF; border: none; border-radius: 9px; padding: 11px 18px;
font-size: 13.5px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.btn-ghost {
background: none; border: 1px solid #E6E5E0; border-radius: 9px; padding: 11px 18px;
font-size: 13.5px; font-weight: 600; color: #3B3A36; cursor: pointer; font-family: inherit;
}
}
+58 -70
View File
@@ -23,57 +23,57 @@ use Symfony\Component\Validator\Constraints\File;
class TaxesController extends AbstractController
{
#[Route(path: '/taxes/{type}', name: 'app_taxes')]
public function index(Request $request, string $type): Response
{
if (!in_array($type, ["crypto", "dividend"])) {
return $this->redirectToRoute("homepage");
}
$a=[];
$form = $this->createFormBuilder([])
->add("uploads",FileType::class, [
'data_class' => null,
'multiple' => true,
'attr'=>[
'multiple' => 'multiple',
],
'constraints' => [
new All([
'constraints' => [
new File([
'maxSize' => '2048k',
'mimeTypes' => [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/atom+xml',
'application/xml',
'text/xml',
'text/xml-external-parsed-entity',
'text/csv',
'text/plain',
// 'application/x-pdf',
],
'mimeTypesMessage' => 'Please upload a valid XLSX or XML document',
])
]
])
],
])
->add("upload",SubmitType::class)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$files = $form->get('uploads')->getData();
$hash = $this->saveFiles($files);
return $this->redirectToRoute("app_taxes_{$type}_show",["hash" => $hash]);
}
return $this->renderForm('taxes/index.html.twig', [
'controller_name' => 'TaxesController',
'form' => $form,
'sheet' => $a
]);
}
// #[Route(path: '/taxes/{type}', name: 'app_taxes')]
// public function index(Request $request, string $type): Response
// {
// if (!in_array($type, ["crypto", "dividend"])) {
// return $this->redirectToRoute("homepage");
// }
//
// $a=[];
// $form = $this->createFormBuilder([])
// ->add("uploads",FileType::class, [
// 'data_class' => null,
// 'multiple' => true,
// 'attr'=>[
// 'multiple' => 'multiple',
// ],
// 'constraints' => [
// new All([
// 'constraints' => [
// new File([
// 'maxSize' => '2048k',
// 'mimeTypes' => [
// 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// 'application/atom+xml',
// 'application/xml',
// 'text/xml',
// 'text/xml-external-parsed-entity',
// 'text/csv',
// 'text/plain',
//// 'application/x-pdf',
// ],
// 'mimeTypesMessage' => 'Please upload a valid XLSX or XML document',
// ])
// ]
// ])
// ],
// ])
// ->add("upload",SubmitType::class)
// ->getForm();
// $form->handleRequest($request);
// if ($form->isSubmitted() && $form->isValid()) {
// $files = $form->get('uploads')->getData();
// $hash = $this->saveFiles($files);
// return $this->redirectToRoute("app_taxes_{$type}_show",["hash" => $hash]);
// }
//
// return $this->renderForm('taxes/index.html.twig', [
// 'controller_name' => 'TaxesController',
// 'form' => $form,
// 'sheet' => $a
// ]);
// }
private function saveFiles(array $files) : string
{
@@ -99,28 +99,13 @@ class TaxesController extends AbstractController
return $hash;
}
#[Route(path: '/taxes/summary/{type}', name: 'app_taxes_summary', methods: ['POST'])]
#[Route(path: '/taxes/{type}', name: 'app_taxes_summary', methods: ['POST'])]
public function takeSummary(string $type, Request $request, DividendsService $tax, ?MarketNames $market): Response
{
$files = $request->files->all()['pliki'];
$hash = $this->saveFiles($files);
if ($hash) {
$tax->setHash($hash);
if ($tax->readFiles($market)) {
$gen = $tax->generate();
// dd($gen);
return $this->render("taxes/show.html.twig", [
"taxes" => $gen->getSummaryByCountry(),
"errorDividend" => $gen->getErrorDividend(),
"errorString" => $gen->getErrorString(),
"rawCount" => $gen->takeCountRawData(),
"markets" => $gen->getMarkets(),
"markets_all" => MarketNames::cases(),
"hash_param" => $tax->takeHash(),
"current_market" => $market,
]);
}
return $this->redirectToRoute("app_taxes_{$type}_show", ["hash" => $hash]);
}
return $this->json([
"type" => $type,
@@ -133,8 +118,10 @@ class TaxesController extends AbstractController
{
if ($tax->readFiles($market)) {
$gen = $tax->generate();
// dd($gen);
return $this->render("taxes/show.html.twig", [
$taxes = $gen->getSummaryByCountry();
$sum = $taxes['sum'];
unset($taxes['sum']);
return $this->render("taxes/table.html.twig", [
"taxes" => $gen->getSummaryByCountry(),
"errorDividend" => $gen->getErrorDividend(),
"errorString" => $gen->getErrorString(),
@@ -143,6 +130,7 @@ class TaxesController extends AbstractController
"markets_all" => MarketNames::cases(),
"hash_param" => $tax->takeHash(),
"current_market" => $market,
"sum" => $sum,
]);
}
return new Response("Brak danych do wyświetlenia.");
@@ -42,12 +42,19 @@ class DividendsSummary
$this->countFromSheets();
if (count($this->allData) > 0) {
$sum = 0;
$profitSum = 0;
$summaryRow = [
"tax" => 0,
"profit" => 0,
"origin" => 0,
"destiny" => 0,
];
foreach ($this->allData as $key => $item) {
$val = round($item['tax'], 2);
$sum += $val;
$profitSum += $item['incomeAll'];
$summaryRow["tax"] += $val;
$summaryRow["profit"] += $item['incomeAll'];
$summaryRow["origin"] += $item['destiny_tax'];
$summaryRow["destiny"] += $item['tax_cost'];
$return[$key] = [
"country" => \Locale::getDisplayRegion("-".$key),
"income" => $item['incomeAll'],
@@ -58,11 +65,11 @@ class DividendsSummary
"incomesArray" => $item['incomes']
];
}
$summaryRow = array_map(function ($item) {
return intval(ceil($item));
}, $summaryRow);
// >
$return['sum'] = [
"tax" => intval(ceil($sum)),
"profit" => $profitSum
];
$return['sum'] = $summaryRow;
}
return $return;
+97
View File
@@ -0,0 +1,97 @@
<div class="summaryTable">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>L.p.</th>
<th>Country</th>
<th>Income</th>
{% if taxes|first.cost is defined %}
<th>Cost</th>
{% endif %}
{% if taxes|first.destiny_tax is defined %}
<th>Origin tax</th>
{% endif %}
{% if taxes|first.tax_cost is defined %}
<th>Deductable tax (15%)</th>
{% endif %}
{% if taxes|first.profit is defined %}
<th>Profit/Loss</th>
{% endif %}
<th>Tax</th>
</tr>
</thead>
<tbody>
{% for key,tax in taxes %}
{% if tax.country is defined %}
{% set colspan = tax|length %}
{% if tax.income > 0 %}
<tr class="{% if tax.profit is defined and tax.profit < 0 %}table-danger{% else %}table-success{% endif %}">
<td>{{ loop.index }}</td>
<td>{{ tax.country }}</td>
<td>{{ tax.income|price }}</td>
{% if tax.cost is defined %}
<td>{{ tax.cost|price }}</td>
{% endif %}
{% if tax.destiny_tax is defined %}
<td>{{ tax.destiny_tax|price }}</td>
{% endif %}
{% if tax.tax_cost is defined %}
<td>{{ tax.tax_cost|price }}</td>
{% endif %}
{% if tax.profit is defined %}
<td>{{ tax.profit|price }}</td>
{% endif %}
<td>{{ tax.tax|price }}</td>
</tr>
{% if tax.incomesArray is defined and tax.incomesArray|length > 0 %}
{% set colspan = colspan-1 %}
{% for incomePerc,value in tax.incomesArray %}
<tr>
<td colspan="2"></td>
<td>{{ incomePerc }}% - {{ value|price }}</td>
<td colspan="3"></td>
</tr>
{% endfor %}
{% endif %}
{% endif %}
{% elseif key == "sum" %}
{# <tr class="">#}
{# <td colspan="{{ colspan-2 }}">Summary:</td>#}
{# <td>{{ tax.profit|price("PLN",0) }}</td>#}
{# <td>{{ tax.tax|price("PLN",0) }}</td>#}
{# </tr>#}
{% else %}
<tr class="">
<td colspan="{{ colspan-1 }}">Summary:</td>
<td>{{ tax|price("PLN",0) }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="2" class="foot-label">Podsumowanie</td>
<td class="foot-label">{{ sum.profit|price("PLN",0) }}</td>
<td class="foot-label">{{ sum.origin|price("PLN",0) }}</td>
<td class="foot-label">{{ sum.destiny|price("PLN",0) }}</td>
<td colspan="1"></td>
</tr>
<tr>
<td colspan="2" class="foot-label">Do zaplaty</td>
<td class="foot-value" colspan="4">{{ sum.tax|price("PLN",0) }}</td>
</tr>
</tfoot>
</table>
</div>
{#{% if wynik.footNote %}#}
<div class="note">Kwota do dopłaty w Polsce po uwzględnieniu podatku zapłaconego za granicą.</div>
{#{% endif %}#}
<div class="actions">
{# <button type="button" class="btn-accent" style="background: {{ wynik.accent }}">Eksportuj do PIT</button>#}
<button type="button" class="btn-ghost" data-action="app#restart">Od nowa</button>
</div>
</div>