From d8e8460bc853a87e3216d32c0d0ea8f4f76e64c0 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 09:29:35 +0200 Subject: [PATCH 01/40] Passing invoice id to invoice entry worklog form class --- src/Controller/InvoiceEntryWorklogController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Controller/InvoiceEntryWorklogController.php b/src/Controller/InvoiceEntryWorklogController.php index 1c31afb7..c1dd16f1 100644 --- a/src/Controller/InvoiceEntryWorklogController.php +++ b/src/Controller/InvoiceEntryWorklogController.php @@ -48,7 +48,9 @@ public function worklogs(Request $request, Invoice $invoice, InvoiceEntry $invoi } $filterData = new InvoiceEntryWorklogsFilterData(); - $form = $this->createForm(InvoiceEntryWorklogFilterType::class, $filterData); + $form = $this->createForm(InvoiceEntryWorklogFilterType::class, $filterData, [ + 'invoiceId' => $invoice->getId(), + ]); $form->add('version', EntityType::class, [ 'class' => Version::class, From 40376b24be3373f085acb1aef0c8292fd7f315fb Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 09:30:09 +0200 Subject: [PATCH 02/40] Prepared form class to recieve invoice id, and getting periodFrom and periodTo as data for date fields --- src/Form/InvoiceEntryWorklogFilterType.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Form/InvoiceEntryWorklogFilterType.php b/src/Form/InvoiceEntryWorklogFilterType.php index f3e2a975..8b6d68e3 100644 --- a/src/Form/InvoiceEntryWorklogFilterType.php +++ b/src/Form/InvoiceEntryWorklogFilterType.php @@ -3,6 +3,7 @@ namespace App\Form; use App\Model\Invoices\InvoiceEntryWorklogsFilterData; +use App\Repository\InvoiceRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateType; @@ -11,8 +12,14 @@ class InvoiceEntryWorklogFilterType extends AbstractType { + public function __construct( + private readonly InvoiceRepository $invoiceRepository, + ) { + } + public function buildForm(FormBuilderInterface $builder, array $options): void { + $invoice = $this->invoiceRepository->find($options['invoiceId'] ?? null); $builder ->add('isBilled', ChoiceType::class, [ 'required' => false, @@ -31,6 +38,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label_attr' => ['class' => 'label'], 'row_attr' => ['class' => 'form-row'], 'help' => 'worklog.period_from_helptext', + 'data' => $invoice?->getPeriodFrom(), 'widget' => 'single_text', 'html5' => true, 'attr' => ['class' => 'form-element'], @@ -42,6 +50,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'row_attr' => ['class' => 'form-row'], 'attr' => ['class' => 'form-element'], 'help' => 'worklog.period_to_helptext', + 'data' => $invoice?->getPeriodTo(), 'widget' => 'single_text', 'html5' => true, ]) @@ -72,6 +81,7 @@ public function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'method' => 'GET', 'data_class' => InvoiceEntryWorklogsFilterData::class, + 'invoiceId' => null, ]); } } From 4886af41c02ed1a3b0d7e8dcdda917bdda5faf0f Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 09:33:18 +0200 Subject: [PATCH 03/40] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 735f0700..093ce753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-146](https://github.com/itk-dev/economics/pull/146) + 2034: Invoice date select continuity. * [PR-145](https://github.com/itk-dev/economics/pull/145) 2059: Specify workload report week definition. * [PR-143](https://github.com/itk-dev/economics/pull/143) From 8131b98a34b7d5ea8570077c1878d96daed62775 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 11:21:12 +0200 Subject: [PATCH 04/40] added button to template, created stylings, js controller and got js to call controller --- .../invoice-sync-worklogs_controller.js | 45 ++++++++++++++ assets/styles/app.css | 20 ++++++- .../InvoiceEntryWorklogController.php | 58 +++++++++++++++++++ src/Form/InvoiceEntryWorklogFilterType.php | 1 + templates/invoice_entry/worklogs.html.twig | 7 ++- translations/messages.da.yaml | 1 + 6 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 assets/controllers/invoice-sync-worklogs_controller.js diff --git a/assets/controllers/invoice-sync-worklogs_controller.js b/assets/controllers/invoice-sync-worklogs_controller.js new file mode 100644 index 00000000..50ae6e5e --- /dev/null +++ b/assets/controllers/invoice-sync-worklogs_controller.js @@ -0,0 +1,45 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + updateUrl; + connect() { + console.log('Hello from connect'); + this.updateUrl = this.element.dataset.updateUrl; + } + + syncWorklogs(e) { + let syncButton = e.target; + syncButton.disabled = true; + syncButton.innerHTML = '\n' + + ' \n' + + '' + + fetch(this.updateUrl, { + method: "POST", + mode: "same-origin", + cache: "no-cache", + credentials: "same-origin", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + }) + .then(async (resp) => { + if (!resp.ok) { + resp.json().then((err) => { + syncButton.innerText = `failed: ${err.message}`; + }); + } else { + syncButton.innerText = "ok"; + } + }) + .catch((err) => { + syncButton.innerText = `failed${err.message}`; + syncButton.disabled = false; + }) + .finally(() => { + syncButton.disabled = false; + }); + } +} diff --git a/assets/styles/app.css b/assets/styles/app.css index 711482e0..feb4db8e 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -163,7 +163,25 @@ .worklogs-filter-form { @apply grid gap-3 mb-6 md:grid-cols-4; } - + .worklogs-filter-form-header-group { + @apply flex; + } + .worklogs-filter-form-header-group h1 { + flex: 5; + } + .worklogs-filter-form-header-group #sync-worklogs { + flex: 1; + @apply p-4 h-16 m-auto; + } + .worklogs-filter-form-header-group #sync-worklogs svg { + max-height: 85%; + margin: auto; + animation: spin 2s linear infinite + } + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } .choices__list--dropdown .choices__item--selectable { padding: 1em !important; } diff --git a/src/Controller/InvoiceEntryWorklogController.php b/src/Controller/InvoiceEntryWorklogController.php index c1dd16f1..1cd92b91 100644 --- a/src/Controller/InvoiceEntryWorklogController.php +++ b/src/Controller/InvoiceEntryWorklogController.php @@ -158,4 +158,62 @@ public function selectWorklogs(Request $request, InvoiceEntry $invoiceEntry, Wor return new Response(null, 200); } + + /** + * @throws EconomicsException + */ + #[Route('/{projectId}/sync', name: 'app_invoice_entry_project_worklogs_sync', methods: ['POST'])] + public function sync(): Response + { + die('hgest'); + /*$dataProviders = $this->dataProviderRepository->findBy(['enabled' => true]); + + foreach ($dataProviders as $dataProvider) { + $projects = $this->projectRepository->findBy(['include' => true, 'dataProvider' => $dataProvider]); + + $numberOfProjects = count($projects); + + $io->info("Processing worklogs for $numberOfProjects projects that are included (project.include)"); + + foreach ($projects as $project) { + $io->writeln("Processing worklogs for {$project->getName()}"); + + $this->dataSynchronizationService->syncWorklogsForProject($project->getId(), function ($i, $length) use ($io) { + if (0 == $i) { + $io->progressStart($length); + } elseif ($i >= $length - 1) { + $io->progressFinish(); + } else { + $io->progressAdvance(); + } + }, $dataProvider); + + $io->writeln(''); + } + }*/ + + + + /*try { + $projectId = $project->getId(); + + if (null == $projectId) { + return new Response('Not found', 404); + } + + $dataProvider = $project->getDataProvider(); + + if (null != $dataProvider) { + $dataSynchronizationService->syncIssuesForProject($projectId, null, $dataProvider); + $dataSynchronizationService->syncWorklogsForProject($projectId, null, $dataProvider); + } + + return new JsonResponse([], 200); + } catch (\Throwable $exception) { + return new JsonResponse( + ['message' => $exception->getMessage()], + (int) ($exception->getCode() > 0 ? $exception->getCode() : 500) + ); + }*/ + } } diff --git a/src/Form/InvoiceEntryWorklogFilterType.php b/src/Form/InvoiceEntryWorklogFilterType.php index 8b6d68e3..cb214ee1 100644 --- a/src/Form/InvoiceEntryWorklogFilterType.php +++ b/src/Form/InvoiceEntryWorklogFilterType.php @@ -5,6 +5,7 @@ use App\Model\Invoices\InvoiceEntryWorklogsFilterData; use App\Repository\InvoiceRepository; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ButtonType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\FormBuilderInterface; diff --git a/templates/invoice_entry/worklogs.html.twig b/templates/invoice_entry/worklogs.html.twig index 67ed5f16..f681fb3e 100644 --- a/templates/invoice_entry/worklogs.html.twig +++ b/templates/invoice_entry/worklogs.html.twig @@ -3,13 +3,18 @@ {% block title %}{{ 'worklog.title'|trans }}{% endblock %} {% block content %} -

{{ 'worklog.title'|trans }}

+ {{ dump(invoice.id) }} +
+

{{ 'worklog.title'|trans }}

+ +
{{ form_start(form) }}
{{ form_rest(form) }}
+ {{ form_end(form) }}
diff --git a/translations/messages.da.yaml b/translations/messages.da.yaml index b0a67072..6fe3e198 100644 --- a/translations/messages.da.yaml +++ b/translations/messages.da.yaml @@ -351,6 +351,7 @@ worklog: only_available: "Kun \"frie\" worklogs" only_available_helptext: "Worklogs der ikke er i en anden fakturalinje." title_show: "Worklogs" + sync_worklogs: "Synkroniser worklogs" invoice_entry: amount: "Antal enheder" From 9c45c694a8d23f2fba30dd7075165eeca35a4b7e Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 14:01:51 +0200 Subject: [PATCH 05/40] Added logic to worklog sync method --- .../InvoiceEntryWorklogController.php | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/src/Controller/InvoiceEntryWorklogController.php b/src/Controller/InvoiceEntryWorklogController.php index 1cd92b91..4e141be3 100644 --- a/src/Controller/InvoiceEntryWorklogController.php +++ b/src/Controller/InvoiceEntryWorklogController.php @@ -10,8 +10,10 @@ use App\Form\InvoiceEntryWorklogFilterType; use App\Model\Invoices\InvoiceEntryWorklogsFilterData; use App\Repository\IssueRepository; +use App\Repository\ProjectRepository; use App\Repository\WorklogRepository; use App\Service\BillingService; +use App\Service\DataSynchronizationService; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -163,42 +165,17 @@ public function selectWorklogs(Request $request, InvoiceEntry $invoiceEntry, Wor * @throws EconomicsException */ #[Route('/{projectId}/sync', name: 'app_invoice_entry_project_worklogs_sync', methods: ['POST'])] - public function sync(): Response + public function sync(Request $request, DataSynchronizationService $dataSynchronizationService, ProjectRepository $projectRepository, $projectId): Response { - die('hgest'); - /*$dataProviders = $this->dataProviderRepository->findBy(['enabled' => true]); - - foreach ($dataProviders as $dataProvider) { - $projects = $this->projectRepository->findBy(['include' => true, 'dataProvider' => $dataProvider]); - - $numberOfProjects = count($projects); - - $io->info("Processing worklogs for $numberOfProjects projects that are included (project.include)"); - - foreach ($projects as $project) { - $io->writeln("Processing worklogs for {$project->getName()}"); - - $this->dataSynchronizationService->syncWorklogsForProject($project->getId(), function ($i, $length) use ($io) { - if (0 == $i) { - $io->progressStart($length); - } elseif ($i >= $length - 1) { - $io->progressFinish(); - } else { - $io->progressAdvance(); - } - }, $dataProvider); - - $io->writeln(''); + try { + if (null == $projectId) { + throw new \Exception('No projectId provided'); } - }*/ - - - /*try { - $projectId = $project->getId(); + $project = $projectRepository->find($projectId); - if (null == $projectId) { - return new Response('Not found', 404); + if (null == $project) { + throw new \Exception('No project found'); } $dataProvider = $project->getDataProvider(); @@ -214,6 +191,6 @@ public function sync(): Response ['message' => $exception->getMessage()], (int) ($exception->getCode() > 0 ? $exception->getCode() : 500) ); - }*/ + } } } From 17e43c126385352325340de0dbd5b0b2e103f46c Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 14:02:13 +0200 Subject: [PATCH 06/40] Added styling and improved javascript handling button --- .../invoice-sync-worklogs_controller.js | 26 +++++++++++++------ src/Form/InvoiceEntryWorklogFilterType.php | 1 - templates/invoice_entry/worklogs.html.twig | 3 +-- translations/messages.da.yaml | 1 + 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/assets/controllers/invoice-sync-worklogs_controller.js b/assets/controllers/invoice-sync-worklogs_controller.js index 50ae6e5e..ef03ae1e 100644 --- a/assets/controllers/invoice-sync-worklogs_controller.js +++ b/assets/controllers/invoice-sync-worklogs_controller.js @@ -3,16 +3,19 @@ import { Controller } from "@hotwired/stimulus"; export default class extends Controller { updateUrl; connect() { - console.log('Hello from connect'); this.updateUrl = this.element.dataset.updateUrl; } syncWorklogs(e) { let syncButton = e.target; + let originalText = syncButton.dataset.originaltext; + let successText = syncButton.dataset.successtext; syncButton.disabled = true; - syncButton.innerHTML = '\n' + - ' \n' + - '' + syncButton.innerHTML = ` + + + +`; fetch(this.updateUrl, { method: "POST", @@ -28,18 +31,25 @@ export default class extends Controller { .then(async (resp) => { if (!resp.ok) { resp.json().then((err) => { + syncButton.classList.add("btn-danger"); syncButton.innerText = `failed: ${err.message}`; }); } else { - syncButton.innerText = "ok"; + syncButton.innerText = successText; + syncButton.classList.add("btn-success"); } }) .catch((err) => { - syncButton.innerText = `failed${err.message}`; - syncButton.disabled = false; + syncButton.classList.add("btn-danger"); + syncButton.innerText = `failed: ${err.message}`; }) .finally(() => { - syncButton.disabled = false; + setTimeout(() => { + syncButton.disabled = false; + syncButton.classList.remove("btn-success"); + syncButton.innerText = originalText; + }, 3000); + }); } } diff --git a/src/Form/InvoiceEntryWorklogFilterType.php b/src/Form/InvoiceEntryWorklogFilterType.php index cb214ee1..8b6d68e3 100644 --- a/src/Form/InvoiceEntryWorklogFilterType.php +++ b/src/Form/InvoiceEntryWorklogFilterType.php @@ -5,7 +5,6 @@ use App\Model\Invoices\InvoiceEntryWorklogsFilterData; use App\Repository\InvoiceRepository; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ButtonType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\FormBuilderInterface; diff --git a/templates/invoice_entry/worklogs.html.twig b/templates/invoice_entry/worklogs.html.twig index f681fb3e..52d0e36c 100644 --- a/templates/invoice_entry/worklogs.html.twig +++ b/templates/invoice_entry/worklogs.html.twig @@ -3,10 +3,9 @@ {% block title %}{{ 'worklog.title'|trans }}{% endblock %} {% block content %} - {{ dump(invoice.id) }}

{{ 'worklog.title'|trans }}

- +
{{ form_start(form) }} diff --git a/translations/messages.da.yaml b/translations/messages.da.yaml index 6fe3e198..7448e752 100644 --- a/translations/messages.da.yaml +++ b/translations/messages.da.yaml @@ -352,6 +352,7 @@ worklog: only_available_helptext: "Worklogs der ikke er i en anden fakturalinje." title_show: "Worklogs" sync_worklogs: "Synkroniser worklogs" + sync_worklogs_success: "Synkronisering fuldført" invoice_entry: amount: "Antal enheder" From 5352ac27f8192ddacabb16edecc007865d74860f Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 14:37:29 +0200 Subject: [PATCH 07/40] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093ce753..03dd337b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-147](https://github.com/itk-dev/economics/pull/147) + 2033: Sync worklogs from invoice entry. * [PR-146](https://github.com/itk-dev/economics/pull/146) 2034: Invoice date select continuity. * [PR-145](https://github.com/itk-dev/economics/pull/145) From f6f355e0e4d22e37398d5761e2366c3673e4bf61 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 8 Aug 2024 14:48:26 +0200 Subject: [PATCH 08/40] Added page reload post sync success --- assets/controllers/invoice-sync-worklogs_controller.js | 3 ++- translations/messages.da.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/controllers/invoice-sync-worklogs_controller.js b/assets/controllers/invoice-sync-worklogs_controller.js index ef03ae1e..28e0200f 100644 --- a/assets/controllers/invoice-sync-worklogs_controller.js +++ b/assets/controllers/invoice-sync-worklogs_controller.js @@ -48,7 +48,8 @@ export default class extends Controller { syncButton.disabled = false; syncButton.classList.remove("btn-success"); syncButton.innerText = originalText; - }, 3000); + window.location.reload(); + }, 2000); }); } diff --git a/translations/messages.da.yaml b/translations/messages.da.yaml index 7448e752..541d5539 100644 --- a/translations/messages.da.yaml +++ b/translations/messages.da.yaml @@ -352,7 +352,7 @@ worklog: only_available_helptext: "Worklogs der ikke er i en anden fakturalinje." title_show: "Worklogs" sync_worklogs: "Synkroniser worklogs" - sync_worklogs_success: "Synkronisering fuldført" + sync_worklogs_success: "Succes - siden opdateres.." invoice_entry: amount: "Antal enheder" From e70d1a5f7da7306d10fee4c6acea2e97edab41da Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 9 Aug 2024 15:49:14 +0200 Subject: [PATCH 09/40] Modified page-header component to utilize upcoming action-button component based on its type --- templates/components/page-header.html.twig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/templates/components/page-header.html.twig b/templates/components/page-header.html.twig index cac24f8e..3af85bd7 100644 --- a/templates/components/page-header.html.twig +++ b/templates/components/page-header.html.twig @@ -1,6 +1,14 @@

{{ title }}

- {% if link_url|default(false) %} - {{ link_text }} + {% if type is defined %} + {% if type == 'link' %} + {{ link_text }} + {% endif %} + {% if type == 'action' %} + {% include 'components/action-button.html.twig' with { + 'url': url + } %} + {% endif %} {% endif %} +
From c4e9667b381a51d370df4596279c5ee722d0b0c5 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 9 Aug 2024 15:49:49 +0200 Subject: [PATCH 10/40] Added action button twig component --- templates/components/action-button.html.twig | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 templates/components/action-button.html.twig diff --git a/templates/components/action-button.html.twig b/templates/components/action-button.html.twig new file mode 100644 index 00000000..5a9c3fb1 --- /dev/null +++ b/templates/components/action-button.html.twig @@ -0,0 +1,54 @@ +{# +This Twig template represents a stylized button component with different states that could be managed by a StumilusJS Controller. + +Parameters: +- url (string, optional): + An URL which can be accessed when the button is clicked. Defaults to an empty string. +- reload (boolean, optional): + If set to true, triggers a page reload when the button is clicked. Defaults to false. +- default_text (string, optional): + The default text displayed on the button. Defaults to 'default'. +- loader_text (string, optional): + The text displayed when the button is in the state of loading. Should be defined together with link_text variable. +- success_text (string, optional): + The text displayed when an action related to the button is successfully finished. Defaults to 'success'. +- error_text (string, optional): + The text displayed when an action related to the button has failed. Defaults to 'error'. +#} + + + From 78c75061f8809f497808cceb2ad67c2fef5f6913 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 9 Aug 2024 15:50:17 +0200 Subject: [PATCH 11/40] Added js controller for action-button and attached postrequesthandler --- .../controllers/action-button_controller.js | 88 +++++++++++++++++++ assets/helpers/postRequestHandler.js | 35 ++++++++ 2 files changed, 123 insertions(+) create mode 100644 assets/controllers/action-button_controller.js create mode 100644 assets/helpers/postRequestHandler.js diff --git a/assets/controllers/action-button_controller.js b/assets/controllers/action-button_controller.js new file mode 100644 index 00000000..81c8cd40 --- /dev/null +++ b/assets/controllers/action-button_controller.js @@ -0,0 +1,88 @@ +import { Controller } from "@hotwired/stimulus"; +import { postRequestHandler } from "../helpers/postRequestHandler"; + +const states = { + default: "default", + loading: "loading", + success: "success", + error: "error" +}; + +export default class extends Controller { + + static targets = ["actionDefault", "actionLoading", "actionSuccess", "actionError"]; + + action(e) { + const targets = { + default: this.actionDefaultTarget, + loading: this.actionLoadingTarget, + success: this.actionSuccessTarget, + error: this.actionErrorTarget, + parent: e.target, + }; + const data = e.target.dataset; + const url = data.url; + const reload = data.reload; + + triggerState(states.loading, targets); + postRequestHandler(url) + .then(result => { + if (result.success) { + triggerState(states.success, targets); + setTimeout(()=>{ + reload && window.location.reload(); + triggerState(states.default, targets); + }, 2000) + } else { + triggerState(states.error, targets); + alert(result.error); + } + }); + } +} + +/** + * Applies a state to the provided targets based on the given state value. + * + * @param {string} state - The state value to apply. + * @param {object} targets - The targets to apply the state to. + */ +function triggerState(state, targets) { + resetState(targets); + + switch (state) { + case states.default: + targets.default.classList.remove('hidden'); + break; + case states.loading: + targets.loading.classList.remove("hidden"); + break; + case states.success: + targets.success.classList.remove("hidden"); + targets.parent.classList.add('btn-success'); + break; + case states.error: + targets.error.classList.remove("hidden"); + targets.parent.classList.add('btn-error'); + break; + default: + console.log("State not recognized"); + } +} + +/** + * Resets the state of the targets. + * + * @param {object} targets - The targets to reset. + * @return {void} + */ +function resetState(targets) +{ + Object.entries(targets).forEach(([type, target]) => { + target.classList.remove('btn-error', 'btn-success'); + if (type === "parent") { + return; + } + target.classList.add('hidden'); + }); +} diff --git a/assets/helpers/postRequestHandler.js b/assets/helpers/postRequestHandler.js new file mode 100644 index 00000000..1aaea825 --- /dev/null +++ b/assets/helpers/postRequestHandler.js @@ -0,0 +1,35 @@ +export const postRequestHandler = async (updateUrl) => { + let result = { + success: false, + status: null, + data: null, + error: null + }; + + try { + const response = await fetch(updateUrl, { + method: "POST", + mode: "same-origin", + cache: "no-cache", + credentials: "same-origin", + headers: { "Content-Type": "application/json" }, + redirect: "follow", + referrerPolicy: "no-referrer" + }); + + result.status = response.status; + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message); + } else { + result.success = true; + result.data = await response.json(); + } + } catch (error) { + console.error(error.message); + result.error = error.message; + } + + return result; +}; From d4013d9759b78104af9e2f8a7c9a3fad1cf0185f Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 9 Aug 2024 15:50:33 +0200 Subject: [PATCH 12/40] Added icons to icon twig component --- templates/components/icons.html.twig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/templates/components/icons.html.twig b/templates/components/icons.html.twig index 3a64319a..41d5cf87 100644 --- a/templates/components/icons.html.twig +++ b/templates/components/icons.html.twig @@ -49,4 +49,8 @@ + {% elseif icon == 'spinner' %} + + + {% endif %} From 75130b86a5ac978e10c6a15a18498abb3423c7dd Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 9 Aug 2024 15:50:58 +0200 Subject: [PATCH 13/40] Implemented new action button for worklog entry page header --- templates/invoice_entry/worklogs.html.twig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/templates/invoice_entry/worklogs.html.twig b/templates/invoice_entry/worklogs.html.twig index 52d0e36c..14727d08 100644 --- a/templates/invoice_entry/worklogs.html.twig +++ b/templates/invoice_entry/worklogs.html.twig @@ -3,10 +3,14 @@ {% block title %}{{ 'worklog.title'|trans }}{% endblock %} {% block content %} -
-

{{ 'worklog.title'|trans }}

- -
+ {% include 'components/page-header.html.twig' with { + 'title': 'invoices.title'|trans, + 'type': 'action', + 'default_text': 'worklog.sync_worklogs'|trans, + 'success_text': 'worklog.sync_worklogs_success'|trans, + 'url': path('app_invoice_entry_project_worklogs_sync', {'projectId': invoice.project.id, 'invoice': invoice.id}), + 'reload': true, + } %} {{ form_start(form) }}
From 1d56113c9e88f1de33cdde54f504b3aa6cdee2ef Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 12 Aug 2024 08:22:14 +0200 Subject: [PATCH 14/40] Converted some inline css to tailwind and added rest to app.css --- assets/styles/app.css | 23 ++++++-------- templates/components/action-button.html.twig | 32 +++----------------- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/assets/styles/app.css b/assets/styles/app.css index feb4db8e..3f7d402c 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -163,25 +163,20 @@ .worklogs-filter-form { @apply grid gap-3 mb-6 md:grid-cols-4; } - .worklogs-filter-form-header-group { - @apply flex; - } - .worklogs-filter-form-header-group h1 { - flex: 5; - } - .worklogs-filter-form-header-group #sync-worklogs { - flex: 1; - @apply p-4 h-16 m-auto; - } - .worklogs-filter-form-header-group #sync-worklogs svg { - max-height: 85%; + .action-button > span:not(.hidden) > svg { margin: auto; animation: spin 2s linear infinite } + @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } + .choices__list--dropdown .choices__item--selectable { padding: 1em !important; } diff --git a/templates/components/action-button.html.twig b/templates/components/action-button.html.twig index 5a9c3fb1..1e1b5096 100644 --- a/templates/components/action-button.html.twig +++ b/templates/components/action-button.html.twig @@ -16,39 +16,17 @@ Parameters: The text displayed when an action related to the button has failed. Defaults to 'error'. #} - - From 9a3e8b87be9a6a106021c7064c86b689568ef833 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 12 Aug 2024 08:41:31 +0200 Subject: [PATCH 15/40] Cleaned up --- .../invoice-sync-worklogs_controller.js | 56 ------------------- .../InvoiceEntryWorklogController.php | 33 ----------- templates/components/action-button.html.twig | 2 +- templates/components/page-header.html.twig | 7 ++- templates/invoice_entry/worklogs.html.twig | 4 +- 5 files changed, 9 insertions(+), 93 deletions(-) delete mode 100644 assets/controllers/invoice-sync-worklogs_controller.js diff --git a/assets/controllers/invoice-sync-worklogs_controller.js b/assets/controllers/invoice-sync-worklogs_controller.js deleted file mode 100644 index 28e0200f..00000000 --- a/assets/controllers/invoice-sync-worklogs_controller.js +++ /dev/null @@ -1,56 +0,0 @@ -import { Controller } from "@hotwired/stimulus"; - -export default class extends Controller { - updateUrl; - connect() { - this.updateUrl = this.element.dataset.updateUrl; - } - - syncWorklogs(e) { - let syncButton = e.target; - let originalText = syncButton.dataset.originaltext; - let successText = syncButton.dataset.successtext; - syncButton.disabled = true; - syncButton.innerHTML = ` - - - -`; - - fetch(this.updateUrl, { - method: "POST", - mode: "same-origin", - cache: "no-cache", - credentials: "same-origin", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - }) - .then(async (resp) => { - if (!resp.ok) { - resp.json().then((err) => { - syncButton.classList.add("btn-danger"); - syncButton.innerText = `failed: ${err.message}`; - }); - } else { - syncButton.innerText = successText; - syncButton.classList.add("btn-success"); - } - }) - .catch((err) => { - syncButton.classList.add("btn-danger"); - syncButton.innerText = `failed: ${err.message}`; - }) - .finally(() => { - setTimeout(() => { - syncButton.disabled = false; - syncButton.classList.remove("btn-success"); - syncButton.innerText = originalText; - window.location.reload(); - }, 2000); - - }); - } -} diff --git a/src/Controller/InvoiceEntryWorklogController.php b/src/Controller/InvoiceEntryWorklogController.php index 4e141be3..fd3529c9 100644 --- a/src/Controller/InvoiceEntryWorklogController.php +++ b/src/Controller/InvoiceEntryWorklogController.php @@ -160,37 +160,4 @@ public function selectWorklogs(Request $request, InvoiceEntry $invoiceEntry, Wor return new Response(null, 200); } - - /** - * @throws EconomicsException - */ - #[Route('/{projectId}/sync', name: 'app_invoice_entry_project_worklogs_sync', methods: ['POST'])] - public function sync(Request $request, DataSynchronizationService $dataSynchronizationService, ProjectRepository $projectRepository, $projectId): Response - { - try { - if (null == $projectId) { - throw new \Exception('No projectId provided'); - } - - $project = $projectRepository->find($projectId); - - if (null == $project) { - throw new \Exception('No project found'); - } - - $dataProvider = $project->getDataProvider(); - - if (null != $dataProvider) { - $dataSynchronizationService->syncIssuesForProject($projectId, null, $dataProvider); - $dataSynchronizationService->syncWorklogsForProject($projectId, null, $dataProvider); - } - - return new JsonResponse([], 200); - } catch (\Throwable $exception) { - return new JsonResponse( - ['message' => $exception->getMessage()], - (int) ($exception->getCode() > 0 ? $exception->getCode() : 500) - ); - } - } } diff --git a/templates/components/action-button.html.twig b/templates/components/action-button.html.twig index 1e1b5096..b8ef1526 100644 --- a/templates/components/action-button.html.twig +++ b/templates/components/action-button.html.twig @@ -21,7 +21,7 @@ Parameters: class="action-default pointer-events-none"{{ stimulus_target('action-button', 'actionDefault') }}> {{ default_text|default('default') }}
diff --git a/translations/messages.da.yaml b/translations/messages.da.yaml index c6080709..ac9b1aea 100644 --- a/translations/messages.da.yaml +++ b/translations/messages.da.yaml @@ -543,18 +543,18 @@ reports: management-4th-quarter: '4. Kvartal' management-export-xls: 'Eksportér til excel' - workload_report: - title: 'Workloadrapport' - select_data_provider: 'Vælg datakilde' - workload: 'Normtid' - logged_hours: 'Loggede timer' - select_viewmode: 'Vælg visningstype' - select_view_period_type: 'Vælg periodetype' - week: 'Uge' - workers: 'Medarbejdere' - worker: 'Medarbejder' - hidden-entries: 'Skjulte medarbejdere' - submit: 'Hent' +workload_report: + title: 'Workloadrapport' + select_data_provider: 'Vælg datakilde' + workload: 'Normtid' + logged_hours: 'Loggede timer' + select_viewmode: 'Vælg visningstype' + select_view_period_type: 'Vælg periodetype' + week: 'Uge' + workers: 'Medarbejdere' + worker: 'Medarbejder' + hidden-entries: 'Skjulte medarbejdere' + submit: 'Hent' hour_report: title: 'Timerapport'