feat: worklog admin page - #349
Merged
Merged
Conversation
jeppekroghitk
approved these changes
Sep 7, 2026
| use Symfony\Component\Routing\Attribute\Route; | ||
| use Symfony\Component\Security\Http\Attribute\IsGranted; | ||
|
|
||
| #[Route('/admin/worklog', name: 'app_worklog_')] |
Contributor
There was a problem hiding this comment.
Is the underscore intended? "app_worklog_"
Contributor
Author
There was a problem hiding this comment.
Yes. It's a prefix for the name for each route in the controller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Link to ticket
https://leantime.itkdev.dk/#/tickets/showTicket/8000
Description
Adds a standalone worklog page at
/admin/worklog, so worklogs can be searched andfiltered across the whole system.
Until now a worklog could only be reached through something else: the invoice-entry
worklog picker (scoped to one project and one invoice entry) or the workload-report
drill-down modal from PR-346 (scoped to one worker and one period). There was no way to
answer "where did these hours go?" without first knowing which invoice or which worker
to look under.
The page follows the established list-page pattern in this repo —
FilterDataDTO →FilterTypeform →Repository::getFilteredPagination()→ thin controller → Twig table— modelled on
ProductController/IssueController. The row markup is deliberately theone from PR-346 (
templates/reports/workload_report_worklogs.html.twig) so the two viewsread the same, plus Worker, Data provider and Invoice columns.
Filters: free-text search (worklog description, issue name, tracker issue id), date
range, worker, project, data provider, billed status.
Access:
ROLE_ADMIN, in the existing Admin submenu. Read-only — no export, no editing.Paging: 25 per page, sortable columns.
Notes for the reviewer
Three decisions worth a second pair of eyes:
string, and a token riding along in the URL makes a filtered link unshareable. The
existing filter forms (
ProductFilterType,IssueFilterType) don't do this and carry a_tokenquery param as a result.WorkloadReportControlleralready disables it thesame way.
WorklogRepository::createFilterDataQueryBuilder()weredeliberately not inherited. That method passes
'worklog.isBilled = FALSE OR worklog.isBilled is NULL'toandWhere()unparenthesised— DQL binds
ANDtighter, so with any other predicate the whole query widens — and itcalls
$filterData->periodTo->modify('tomorrow'), mutating the DTO in place. The newmethod wraps the OR in
expr()->orX()and clones the date. The originals are untouchedhere because fixing them changes invoice-entry behaviour; that belongs on its own branch.
sortFieldAllowListis set (thesort key comes straight from the query string, and no existing repository allow-lists
it), and the page size is 25 rather than the 10 hardcoded everywhere else — ten rows is
too few for a page whose whole point is scanning.
Considered and dropped
Client and Account filters were both scoped out because neither would match anything:
Accounthas no Doctrine association toWorklog,IssueorClient.Issue::$accountKey/$accountIdare strings written only byAppFixturesand neverqueried.
Project::$clientsis a ManyToMany, but nothing writes to the join table — the onlycall to
addClient()is insideClient::addProject(), which has no callers. The onereal worklog→client path is
Worklog → InvoiceEntry → Invoice → Client, which existsonly for invoiced worklogs and would silently hide every unbilled hour.
Both are worth adding once those associations are actually populated.
Screenshot of the result
Checklist