diff --git a/.idea/leantime-oss.iml b/.idea/leantime-oss.iml index dc52e2c72..8530328b0 100644 --- a/.idea/leantime-oss.iml +++ b/.idea/leantime-oss.iml @@ -4,6 +4,8 @@ + + @@ -101,6 +103,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/php.xml b/.idea/php.xml index 77b5878f0..b59f6585c 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -160,6 +160,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/phpspec.xml b/.idea/phpspec.xml index e352a0ed1..cb4fc8cde 100644 --- a/.idea/phpspec.xml +++ b/.idea/phpspec.xml @@ -32,6 +32,9 @@ + + \ No newline at end of file diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml index eec2c0189..1f89c4b6a 100644 --- a/.idea/phpunit.xml +++ b/.idea/phpunit.xml @@ -3,8 +3,8 @@ diff --git a/app/Core/Middleware/RequestRateLimiter.php b/app/Core/Middleware/RequestRateLimiter.php index e475eafb0..f81c673e8 100644 --- a/app/Core/Middleware/RequestRateLimiter.php +++ b/app/Core/Middleware/RequestRateLimiter.php @@ -10,6 +10,7 @@ use Leantime\Core\Frontcontroller; use Leantime\Core\IncomingRequest; use Leantime\Core\Middleware\Request; +use Leantime\Domain\Api\Services\Api; use Symfony\Component\HttpFoundation\Response; /** @@ -55,7 +56,7 @@ public function handle(IncomingRequest $request, Closure $next): Response //API Routes Limit if ($request instanceof ApiRequest) { $apiKey = ""; - $key = app()->make(ApiRequest::class)->getAPIKeyUser($apiKey); + $key = app()->make(Api::class)->getAPIKeyUser($apiKey); $limit = 10; } diff --git a/app/Domain/Timesheets/Controllers/ShowMy.php b/app/Domain/Timesheets/Controllers/ShowMy.php index 469ddf112..7454bd7c8 100644 --- a/app/Domain/Timesheets/Controllers/ShowMy.php +++ b/app/Domain/Timesheets/Controllers/ShowMy.php @@ -137,13 +137,16 @@ public function saveTimeSheet(array $postData): void "kind" => $kind, ); - try { - $this->timesheetService->upsertTime($ticketId, $values); - $this->tpl->setNotification("Timesheet saved successfully", "success", "save_timesheet"); - } catch (\Exception $e) { - $this->tpl->setNotification("Error logging time: " . $e->getMessage(), "error", "save_timesheet"); - error_log($e); - continue; + //This should not be the case since we set the input to disabled, but check anyways + if($timestamp !== "false" && $timestamp != false) { + try { + $this->timesheetService->upsertTime($ticketId, $values); + $this->tpl->setNotification("Timesheet saved successfully", "success", "save_timesheet"); + } catch (\Exception $e) { + $this->tpl->setNotification("Error logging time: " . $e->getMessage(), "error", "save_timesheet"); + error_log($e); + continue; + } } } } diff --git a/app/Domain/Timesheets/Controllers/ShowMyList.php b/app/Domain/Timesheets/Controllers/ShowMyList.php index 7cbfda7d6..141af2757 100644 --- a/app/Domain/Timesheets/Controllers/ShowMyList.php +++ b/app/Domain/Timesheets/Controllers/ShowMyList.php @@ -49,8 +49,8 @@ public function run(): Response // The front end javascript is hardcode to start the week on mondays, so we use that here too. //Get start of the week in current users timezone and then switch to UTC - $dateFrom = dtHelper()->userNow()->startOfMonth()->setToDbTimezone(); - $dateTo = dtHelper()->userNow()->endOfMonth()->setToDbTimezone(); + $dateFrom = dtHelper()->userNow()->startOfWeek(CarbonInterface::MONDAY)->setToDbTimezone(); + $dateTo = dtHelper()->userNow()->endOfWeek()->setToDbTimezone(); if (!empty($_POST['dateFrom'])) { $dateFrom = dtHelper()->parseUserDateTime($_POST['dateFrom'])->setToDbTimezone(); diff --git a/app/Domain/Timesheets/Services/Timesheets.php b/app/Domain/Timesheets/Services/Timesheets.php index 47d32616f..0b94a3dd3 100644 --- a/app/Domain/Timesheets/Services/Timesheets.php +++ b/app/Domain/Timesheets/Services/Timesheets.php @@ -316,10 +316,12 @@ public function getWeeklyTimesheets(int $projectId, CarbonInterface $fromDate, i $currentWorkDate = dtHelper()->parseDbDateTime($timesheet['workDate']); // Detect timezone offset + $workdateOffsetStart = ($currentWorkDate->setToUserTimezone()->secondsSinceMidnight() / 60 / 60); // Various Entries can be in different timezones and thus would not be caught by upsert or grouping by // default Creating new rows for each timezone adjustment + //to avoid timezone collisions we disable adding new times to rows that were created in an different timezone $timezonedTime = $currentWorkDate->format("H:i:s"); $groupKey = $timesheet["ticketId"] . "-" . $timesheet["kind"] . "-" . $timezonedTime; @@ -336,7 +338,7 @@ public function getWeeklyTimesheets(int $projectId, CarbonInterface $fromDate, i "day1" => array( "start" => $fromDate, "end" => $fromDate->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", @@ -344,42 +346,42 @@ public function getWeeklyTimesheets(int $projectId, CarbonInterface $fromDate, i "day2" => array( "start" => $fromDate->addDays(1), "end" => $fromDate->addDays(1)->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->addDays(1)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->addDays(1)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", ), "day3" => array( "start" => $fromDate->addDays(2), "end" => $fromDate->addDays(2)->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->addDays(2)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->addDays(2)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", ), "day4" => array( "start" => $fromDate->addDays(3), "end" => $fromDate->addDays(3)->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->addDays(3)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->addDays(3)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", ), "day5" => array( "start" => $fromDate->addDays(4), "end" => $fromDate->addDays(4)->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->addDays(4)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->addDays(4)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", ), "day6" => array( "start" => $fromDate->addDays(5), "end" => $fromDate->addDays(5)->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->addDays(5)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->addDays(5)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", ), "day7" => array( "start" => $fromDate->addDays(6), "end" => $fromDate->addDays(6)->addHours(23)->addMinutes(59), - "actualWorkDate" => $fromDate->addDays(6)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second), + "actualWorkDate" => $workdateOffsetStart === 0 ? $fromDate->addDays(6)->setTime($currentWorkDate->hour, $currentWorkDate->minute, $currentWorkDate->second) : "", "hours" => 0, "description" => "", ), @@ -397,9 +399,11 @@ public function getWeeklyTimesheets(int $projectId, CarbonInterface $fromDate, i // stored as UTC timezone shoifted value in the db. // If the value is not exact but falls within the time period we're adding a new row for ($i = 1; $i < 8; $i++) { + $start = $timesheetGroups[$groupKey]["day" . $i]["start"]; $end = $timesheetGroups[$groupKey]["day" . $i]["end"]; - if ($currentWorkDate->gte($start) && $currentWorkDate->lte($end)) { + + if ($currentWorkDate->gte($start) && $currentWorkDate->lt($end)) { $timesheetGroups[$groupKey]["day" . $i]['hours'] += $timesheet['hours']; $timesheetGroups[$groupKey]["day" . $i]['actualWorkDate'] = $currentWorkDate; $timesheetGroups[$groupKey]["day" . $i]['description'] = $timesheet['description']; @@ -409,6 +413,18 @@ public function getWeeklyTimesheets(int $projectId, CarbonInterface $fromDate, i } } + /*for ($i = 1; $i < 8; $i++) { + if ($timesheetGroups[$groupKey]["day" . $i]['actualWorkDate'] == $currentWorkDate) { + $timesheetGroups[$groupKey]["day" . $i]['hours'] += $timesheet['hours']; + $timesheetGroups[$groupKey]["day" . $i]['description'] = $timesheet['description']; + // No need to check further, we found what we came for + break; + } + }*/ + + + + // Add to rowsum $timesheetGroups[$groupKey]["rowSum"] += $timesheet['hours']; } diff --git a/app/Domain/Timesheets/Templates/showMy.tpl.php b/app/Domain/Timesheets/Templates/showMy.tpl.php index 904a38697..755d2ba92 100644 --- a/app/Domain/Timesheets/Templates/showMy.tpl.php +++ b/app/Domain/Timesheets/Templates/showMy.tpl.php @@ -258,9 +258,10 @@ - addDays($i)->setToUserTimezone()->isToday()) { echo "active"; - } ?>">
+ } ?> + ">
addDays($i)->formatDateForUser(); @@ -293,7 +294,7 @@ __($tpl->get('kind')[$timeRow['kind']]); ?> + data-tippy-content="This entry was likely created using a different timezone. Only existing entries can be updated in this timezone"> @@ -304,19 +305,23 @@ setToUserTimezone()->isToday()) { + if ($timeRow[$dayKey]["start"]->setToUserTimezone()->isToday()) { echo " active"; } ?>"> formatDateForUser() . "|" . $timeRow[$dayKey]["actualWorkDate"]->getTimestamp(); + $inputNameKey = $timeRow["ticketId"] . "|" . $timeRow["kind"] . "|" . ($timeRow[$dayKey]["actualWorkDate"] ? $timeRow[$dayKey]["actualWorkDate"]->formatDateForUser() : "false" ). "|" . ($timeRow[$dayKey]["actualWorkDate"] ? $timeRow[$dayKey]["actualWorkDate"]->getTimestamp() : "false"); ?> name="" value="" + + data-tippy-content="Cannot add time entry in previous timezone" + /> @@ -418,7 +423,7 @@ class="hourCell"
- +
diff --git a/package-lock.json b/package-lock.json index c91b791e0..50de130fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "leantime", - "version": "3.1.2", + "version": "3.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "leantime", - "version": "3.1.2", + "version": "3.1.3", "license": "AGPL-3.0", "dependencies": { "@assuradeurengilde/fontawesome-iconpicker": "^3.2.3", diff --git a/public/assets/css/components/forms.css b/public/assets/css/components/forms.css index 076dd2e60..e388e191d 100644 --- a/public/assets/css/components/forms.css +++ b/public/assets/css/components/forms.css @@ -50,12 +50,12 @@ select, textarea, input[type="text"], input[type="password"], input[type="dateti color:var(--primary-font-color); } -input:read-only, +input[type="text"]:read-only, input:disabled, .btn-primary[disabled]{ border:none; box-shadow:none; - padding-left: 0px; + opacity:0.4; } body input:focus { diff --git a/public/dist/mix-manifest.json b/public/dist/mix-manifest.json index 867c74e0c..7a09c8817 100644 --- a/public/dist/mix-manifest.json +++ b/public/dist/mix-manifest.json @@ -1,19 +1,19 @@ { - "/js/compiled-htmx.3.1.2.min.js": "/js/compiled-htmx.3.1.2.min.js", - "/js/compiled-htmx-headSupport.3.1.2.min.js": "/js/compiled-htmx-headSupport.3.1.2.min.js", - "/css/main.3.1.2.min.css": "/css/main.3.1.2.min.css", - "/css/editor.3.1.2.min.css": "/css/editor.3.1.2.min.css", - "/css/app.3.1.2.min.css": "/css/app.3.1.2.min.css", - "/js/compiled-footer.3.1.2.min.js": "/js/compiled-footer.3.1.2.min.js", - "/js/compiled-app.3.1.2.min.js": "/js/compiled-app.3.1.2.min.js", - "/js/compiled-frameworks.3.1.2.min.js": "/js/compiled-frameworks.3.1.2.min.js", - "/js/compiled-framework-plugins.3.1.2.min.js": "/js/compiled-framework-plugins.3.1.2.min.js", - "/js/compiled-global-component.3.1.2.min.js": "/js/compiled-global-component.3.1.2.min.js", - "/js/compiled-calendar-component.3.1.2.min.js": "/js/compiled-calendar-component.3.1.2.min.js", - "/js/compiled-table-component.3.1.2.min.js": "/js/compiled-table-component.3.1.2.min.js", - "/js/compiled-editor-component.3.1.2.min.js": "/js/compiled-editor-component.3.1.2.min.js", - "/js/compiled-gantt-component.3.1.2.min.js": "/js/compiled-gantt-component.3.1.2.min.js", - "/js/compiled-chart-component.3.1.2.min.js": "/js/compiled-chart-component.3.1.2.min.js", + "/js/compiled-htmx.3.1.3.min.js": "/js/compiled-htmx.3.1.3.min.js", + "/js/compiled-htmx-headSupport.3.1.3.min.js": "/js/compiled-htmx-headSupport.3.1.3.min.js", + "/css/main.3.1.3.min.css": "/css/main.3.1.3.min.css", + "/css/editor.3.1.3.min.css": "/css/editor.3.1.3.min.css", + "/css/app.3.1.3.min.css": "/css/app.3.1.3.min.css", + "/js/compiled-footer.3.1.3.min.js": "/js/compiled-footer.3.1.3.min.js", + "/js/compiled-app.3.1.3.min.js": "/js/compiled-app.3.1.3.min.js", + "/js/compiled-frameworks.3.1.3.min.js": "/js/compiled-frameworks.3.1.3.min.js", + "/js/compiled-framework-plugins.3.1.3.min.js": "/js/compiled-framework-plugins.3.1.3.min.js", + "/js/compiled-global-component.3.1.3.min.js": "/js/compiled-global-component.3.1.3.min.js", + "/js/compiled-calendar-component.3.1.3.min.js": "/js/compiled-calendar-component.3.1.3.min.js", + "/js/compiled-table-component.3.1.3.min.js": "/js/compiled-table-component.3.1.3.min.js", + "/js/compiled-editor-component.3.1.3.min.js": "/js/compiled-editor-component.3.1.3.min.js", + "/js/compiled-gantt-component.3.1.3.min.js": "/js/compiled-gantt-component.3.1.3.min.js", + "/js/compiled-chart-component.3.1.3.min.js": "/js/compiled-chart-component.3.1.3.min.js", "/images/03-1.png": "/images/03-1.png", "/images/32px.png": "/images/32px.png", "/images/40px.png": "/images/40px.png",