Skip to content

feat(ui): projects index page through registerPage (M4 slice 1) - #3

Closed
gdarko wants to merge 3 commits into
feat/apifrom
feat/ui-projects
Closed

gdarko wants to merge 3 commits into
feat/apifrom
feat/ui-projects

Conversation

@gdarko

@gdarko gdarko commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

The first UI slice of the module (M4, slice 1). The sidebar entry already
points at /admin/modules/tasks-projects; until now that landed on the host's
404 view. It now lands on a real projects index.

What is in it

  • resources/js/api.ts + resources/js/types/ - endpoint constants and
    thin typed functions over the injected axios client for projects (list with
    page, limit, status, search; create; update; archive; unarchive;
    delete), members, settings, and the host customer list the form reads.
  • pages/ProjectsIndexPage.vue - BasePage + BasePageHeader with a Home
    to Projects breadcrumb, a filter toggle and a New project button; a
    BaseFilterWrapper whose search input is debounced into a server-side
    BaseTable fetcher, with a status filter of Active / Archived / All; columns
    for name (colour swatch and identifier), status, customer, rate per hour and
    due date, and an actions dropdown for edit, archive/restore and delete
    (window.confirm for now). BaseEmptyPlaceholder when a company has no
    projects at all.
  • components/ProjectFormModal.vue - create and edit in one BaseModal.
    Rates are typed in major units and stored as minor units, budgets are typed
    in hours and stored as minutes, and 422 errors land under their fields
    through BaseInputGroup.
  • init.ts - registers the English bundle and the page through
    extensions.registerPage, behind tasks-projects:view-project. The page
    receives the host client, notifier and router as props, since a module bundle
    shares the host's Vue instance but not its Pinia or router injections.
  • Backend - ProjectService::create and update now default a project's
    currency_id from its customer through CompanyDataReader::findCustomer.
    A currency the caller names wins, and an explicit null still clears it.

Checks

pnpm run lint, pnpm exec tsc --noEmit, pnpm run build (with dist/
committed), composer run lint, composer run test (229 tests, 889
assertions, 6 of them new), validate-module and validate-package.

Driven in the running dev stack with Playwright: the sidebar link lands on the
page, a project created through the modal appears in the table, search and the
status filter narrow the list, and archive, restore and delete all round-trip.
The console stays clean.

Notes and follow-ups

  • The customer column shows customer_id for now; the name lookup is a later
    slice, as is the project detail page the router prop is there for.
  • The list endpoint has no sort parameter, so every column is declared
    unsortable rather than offering a control that does nothing.
  • The status badge overrides the host BaseBadge colours with ! utilities:
    the host stylesheet is injected a second time after the module's, so an
    unimportant token class loses to the badge's built-in colour.

https://claude.ai/code/session_01DCf36XDKprZifej8dc2r1E

The typed client wraps the module's project, member and settings endpoints
over the axios instance the host injects. The index page renders them with
the host's own BasePage, BaseTable and BaseModal chrome: a debounced search
and status filter, an actions dropdown for edit, archive and delete, and a
create/edit modal that converts money to minor units and budgets to minutes
on the way out and back.

Every string the page renders comes from the module's own i18n bundle, and
every colour from the host's semantic tokens.
`registerPage` mounts the index at /admin/modules/tasks-projects, where the
sidebar entry already points, behind the module's own view-project ability.
The page receives the host client, notifier and router as props, because a
module bundle runs on the host's Vue instance but not on its injections.
A project carries one currency and its rates are compared in it, so filing
one under a customer without naming a currency now reads the customer's own
through the host company reader, and changing the customer moves the project
with it. A currency the caller names always wins, and an explicit null still
clears it.
@gdarko

gdarko commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #14, which carries this stack consolidated into three commits on top of main.

@gdarko gdarko closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant