diff --git a/project/assets/controllers/app_controller.js b/project/assets/controllers/app_controller.js new file mode 100644 index 0000000..4bca3b5 --- /dev/null +++ b/project/assets/controllers/app_controller.js @@ -0,0 +1,168 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + static targets = [ + 'railStep', 'step', 'accentDot', 'choiceLabel', 'dropzone', + 'input', 'chips', 'submitBtn', 'addMore', 'error', 'result','loading-all', + ]; + static values = { + step: { type: Number, default: 1 }, + hash: { type: String, default: '' }, + showUrl: {type: String, default: null }, + }; + + connect() { + this.type = null; + this.endpointUrl=null; + this.files = []; + if(this.showUrlValue !== null) { + this.loadTable(); + this.stepValue = 0; + } + this.render(); + } + + // ---- nawigacja ---- + pick(event) { + const btn = event.currentTarget; + this.type = btn.dataset.type; + this.choiceLabelTarget.textContent = btn.dataset.label; + this.accentDotTarget.style.background = this.accent; + this.endpointUrl = btn.dataset.endpoint; + this.files = []; + this.updateChips(); + this.hideError(); + this.stepValue = 2; + } + + back() { + if (this.stepValue <= 2) { + this.type = null; + this.stepValue = 1; + } else { + this.stepValue = 2; + } + } + + restart() { + this.type = null; + this.files = []; + this.updateChips(); + this.resultTarget.innerHTML = ''; + this.hideError(); + this.stepValue = 1; + } + + // ---- pliki ---- + openPicker() { + this.inputTarget.click(); + } + + onFiles() { + this.files = Array.from(this.inputTarget.files || []); + this.updateChips(); + } + + dragOver(event) { + event.preventDefault(); + this.dropzoneTarget.classList.add('is-drag'); + } + + dragLeave() { + this.dropzoneTarget.classList.remove('is-drag'); + } + + drop(event) { + event.preventDefault(); + this.dropzoneTarget.classList.remove('is-drag'); + this.files = Array.from(event.dataTransfer.files || []); + this.updateChips(); + } + + async submit() { + if (this.files.length === 0) { + this.openPicker(); + return; + } + this.hideError(); + this.setLoading(true); + try { + const fd = new FormData(); + fd.append('typ', this.type); + this.files.forEach((f) => fd.append('pliki[]', f)); + + const res = await fetch(this.endpointUrl, { method: 'POST', body: fd }); + if (!res.ok) throw new Error('Błąd serwera (' + res.status + ')'); + + this.resultTarget.innerHTML = await res.text(); + this.stepValue = 3; + } catch (err) { + this.showError(err.message || 'Nie udało się obliczyć zestawienia.'); + } finally { + this.setLoading(false); + } + } + + async loadTable(){ + this.setLoading(true); + try { + const res = await fetch(this.showUrlValue, { method: 'GET'}); + if(!res.ok){ + throw new Error('Błąd serwera (' + res.status + ')'); + } + this.resultTarget.innerHTML = await res.text(); + this.stepValue = 3; + }catch (err) { + this.showError(err.message || 'Nie udało się zaladowac zestawienia.'); + } finally { + this.setLoading(false); + } + } + + // ---- reakcja na zmianę kroku ---- + stepValueChanged() { + this.render(); + } + + render() { + this.stepTargets.forEach((el) => { + el.hidden = Number(el.dataset.step) !== this.stepValue; + }); + this.railStepTargets.forEach((el) => { + const idx = Number(el.dataset.index); + el.classList.toggle('is-active', idx === this.stepValue); + el.classList.toggle('is-done', idx < this.stepValue); + }); + } + + // ---- helpery ---- + get accent() { + return this.type === 'krypto' ? '#C79A3A' : '#3F8F6B'; + } + + updateChips() { + const has = this.files.length > 0; + this.chipsTarget.hidden = !has; + this.addMoreTarget.hidden = !has; + this.chipsTarget.innerHTML = this.files + .map((f) => `${f.name}`) + .join(''); + this.submitBtnTarget.textContent = has ? 'Oblicz zestawienie' : 'Wybierz pliki'; + } + + setLoading(on) { + this.submitBtnTarget.disabled = on; + this.submitBtnTarget.textContent = on + ? 'Obliczanie…' + : (this.files.length ? 'Oblicz zestawienie' : 'Wybierz pliki'); + } + + showError(msg) { + this.errorTarget.textContent = msg; + this.errorTarget.hidden = false; + } + + hideError() { + this.errorTarget.hidden = true; + } +} diff --git a/project/assets/styles/main.less b/project/assets/styles/main.less index a879875..0168727 100644 --- a/project/assets/styles/main.less +++ b/project/assets/styles/main.less @@ -41,6 +41,27 @@ display: flex; align-items: center; gap: 10px; + + &.is-done { + .rail-dot { + background: #E7EFE9; + color: #3F8F6B; + border-color: #CDE3D5; + } + .rail-label { + color: #3B3A36; + } + } + &.is-active{ + .rail-dot { + background: #1C1C1A; + color: #FBFBF9; + border-color: #1C1C1A; + } + .rail-label { + color: #1C1C1A; + } + } } .rail-dot { width: 26px; @@ -68,27 +89,6 @@ margin: 0 14px; min-width: 24px; } - - &.is-done { - .rail-dot { - background: #E7EFE9; - color: #3F8F6B; - border-color: #CDE3D5; - } - .rail-label { - color: #3B3A36; - } - } - &.is-active{ - .rail-dot { - background: #1C1C1A; - color: #FBFBF9; - border-color: #1C1C1A; - } - .rail-label { - color: #1C1C1A; - } - } } /* ---- wybór ---- */ .choices { @@ -148,6 +148,10 @@ } .head-label { font-size: 14px; font-weight: 600; } } +.link { + margin-left: auto; background: none; border: none; color: #6B6A64; + font-size: 13px; text-decoration: underline; padding: 0; cursor: pointer; font-family: inherit; +} .dropzone { border: 1.5px dashed #CFCEC8; border-radius: 14px; background: #FFFFFF; padding: 34px 24px; text-align: center; transition: all .15s; @@ -164,5 +168,8 @@ padding: 11px 20px; font-size: 13.5px; font-weight: 600; cursor: pointer; font-family: inherit; &:disabled { opacity: .6; cursor: default; } } + .add-more { display: block; margin: 12px auto 0; } } - +.loading-all { + margin: auto; +} \ No newline at end of file diff --git a/project/src/Controller/IndexController.php b/project/src/Controller/IndexController.php index 1d111cc..86b5e21 100644 --- a/project/src/Controller/IndexController.php +++ b/project/src/Controller/IndexController.php @@ -8,9 +8,11 @@ use Symfony\Component\Routing\Annotation\Route; class IndexController extends AbstractController { - #[Route(path: '/', name: 'homepage')] - public function index() : Response + #[Route(path: '/{hash}', name: 'homepage', defaults: ['hash'=>null])] + public function index($hash) : Response { - return $this->render("index/index.html.twig"); + return $this->render("index/index.html.twig",[ + 'currentHash'=>$hash, + ]); } } \ No newline at end of file diff --git a/project/src/Controller/TaxesController.php b/project/src/Controller/TaxesController.php index e7643f9..fba2863 100644 --- a/project/src/Controller/TaxesController.php +++ b/project/src/Controller/TaxesController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use _PHPStan_5a70c2d68\Nette\Utils\Json; use App\Enum\MarketNames; use App\Helper\Currency; use App\Helper\DataNeed\NeedFileName; @@ -13,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -62,24 +64,8 @@ class TaxesController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $files = $form->get('uploads')->getData(); - $hash = substr(md5(random_int(0, mt_getrandmax())),0,16); - if ($files) { - $i=1; - /** @var UploadedFile $file */ - foreach ($files as $file) { - $newName = $i; - $checkName = NeedFileName::check($file->getClientOriginalName()); - if ($checkName !== null) { - $newName .= "-".$checkName; - } - $file->move( - $this->getParameter("uploads_path")."/".$hash, - $newName.".".$file->getClientOriginalExtension() - ); - $i++; - } - return $this->redirectToRoute("app_taxes_{$type}_show",["hash" => $hash]); - } + $hash = $this->saveFiles($files); + return $this->redirectToRoute("app_taxes_{$type}_show",["hash" => $hash]); } return $this->renderForm('taxes/index.html.twig', [ @@ -89,6 +75,59 @@ class TaxesController extends AbstractController ]); } + private function saveFiles(array $files) : string + { + if (!count($files)) { + return ""; + } + $hash = substr(md5(random_int(0, mt_getrandmax())), 0, 16); + $i = 1; + /** @var UploadedFile $file */ + foreach ($files as $file) { + $newName = $i; + $checkName = NeedFileName::check($file->getClientOriginalName()); + if ($checkName !== null) { + $newName .= "-" . $checkName; + } + $file->move( + $this->getParameter("uploads_path") . "/" . $hash, + $newName . "." . $file->getClientOriginalExtension() + ); + $i++; + } + + return $hash; + } + + #[Route(path: '/taxes/summary/{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->json([ + "type" => $type, + "request" => $files, + ]); + } + #[Route(path: '/taxes/dividend/{hash}/{market}', name: 'app_taxes_dividend_show', defaults: ['market' => null])] public function showDividend(DividendsService $tax, ?MarketNames $market) : Response { @@ -105,9 +144,8 @@ class TaxesController extends AbstractController "hash_param" => $tax->takeHash(), "current_market" => $market, ]); - } - return new Response("error"); + return new Response("Brak danych do wyświetlenia."); } #[Route(path: '/taxes/crypto/{hash}', name: 'app_taxes_crypto_show')] public function showCrypto(CryptoService $tax) : Response diff --git a/project/src/Services/MasterServices.php b/project/src/Services/MasterServices.php index ddf696d..40c3228 100644 --- a/project/src/Services/MasterServices.php +++ b/project/src/Services/MasterServices.php @@ -10,7 +10,7 @@ class MasterServices protected string $uploadPath; protected array $allFiles; - public function __construct(protected EntityManager $manager, protected string $hashParam) + public function __construct(protected EntityManager $manager, protected ?string $hashParam) { } @@ -23,6 +23,9 @@ class MasterServices { $fileNames = []; $dir=$this->uploadPath . "/" . $this->hashParam; + if (!is_dir($dir)) { + return false; + } $handle = opendir($dir); while (($inDir = readdir($handle)) !== false) { if ($inDir !== "." && $inDir !== "..") { @@ -46,4 +49,9 @@ class MasterServices { return $this->hashParam; } + + public function setHash(string $hash): void + { + $this->hashParam = $hash; + } } \ No newline at end of file diff --git a/project/templates/index/index.html.twig b/project/templates/index/index.html.twig index e8c3390..ea9cf75 100644 --- a/project/templates/index/index.html.twig +++ b/project/templates/index/index.html.twig @@ -3,10 +3,18 @@ {% block title %}Rozliczenie podatkowe{% endblock %} {% block body %}
-
+
{% for row in [{i:'1', label:'Wybór'}, {i:'2', label:'Pliki'}, {i:'3', label:'Wynik'}] %} -
+
{{ row.i }}
{{ row.label }}
@@ -15,8 +23,13 @@ {% endif %} {% endfor %}
-
- -
-
+
{% endblock %} \ No newline at end of file