feat/crm-ops #55
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
psa-systems/mokosh-apps!55
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/crm-ops"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The CompanySitesCard rendered the sites list read-only - the backend has full CRUD (POST/PUT/DELETE /v1/contacts/sites) but the only way to create or edit a site was a hand-rolled curl. Wire the modal: - 'New Site' action button on the card header opens an empty modal pre-populated with the current company_id. - Each site name in the rows is now a button that opens the same modal pre-filled with that site's data (edit mode). - Form fields cover the editable set of CreateSiteRequest / UpdateSiteRequest: name (required), full address (line1, line2, city, state, postal_code, country), phone, timezone, is_primary checkbox. - Save POSTs to /contacts/sites in create mode or PUTs to /contacts/sites/{id} in edit mode. On success the sites resource restarts so the card re-renders with the new state. - Edit mode adds a Delete button in the modal footer (window.confirm guard) that hits DELETE /contacts/sites/{id}; the card refreshes the same way. SiteSummary gains phone + timezone so the existing list response populates the edit form without a follow-up fetch. notes / latitude / longitude stay off the surface for now; can be added when geofencing or service-area work lands. CompanySitesCard now takes company_id as a prop so the create path knows which company to link to. CompanyDetailPage threads company_id_str through to the card.Both list pages were client-side over the first page returned: the backend sent up to 25 rows, the client then paginated and filtered THAT slice. With a tenant of more than 25 companies / contacts you saw only the first page's subset and the page counter capped at 1. The 'Type' filter on Companies and the search box on Contacts were both client-only too - empty query never round-tripped 'company_type' or 'q' to the server. Push every dimension to the backend. Companies list: - Build the path as /contacts/companies?page=N&per_page=25&q=...&company_type=...&sort=...&sort_dir=... and let the backend filter + paginate + sort. - Pass company_sort_query() => ('name'|'company_type', 'asc'|'desc') from the table-header sort state. - Total page count reads PaginatedResponse.meta.total instead of the client-side filtered length. - Search and Type onchange handlers reset page to 1 so filtering from page 5 doesn't leave you stranded with zero matches on a no-longer-existent page. Contacts list: - Same pattern, with the addition of two new filter dropdowns the doc 02 A.4 listed: - contact_type filter (Primary / Technical / Billing / Other) - is_portal_user filter (Portal users only / Non-portal only) - contact_sort_query() => ('last_name'|'company_name', 'asc'|'desc'). PaginatedCompanies / PaginatedContacts gain a meta field deserialized from PaginationResponse.meta; only .total is read for now, the rest of the meta (has_next/has_prev) is still derived from page+total at the DataTable level. Wrong query-param name fix: every '?page_size=' across the file plus the CompanyPicker was '?per_page=' on the server. Renamed eight call sites (list pages, sites card, ticket sub-resources, picker). Only the picker was visibly affected before this change because the server silently fell back to default per_page=25; the rest were already asking for the default. Helpers: - company_sort_query / contact_sort_query map the SortKey enums to the column names the server's PaginationParams.sort understands. - urlencoding_minimal: tiny percent-encoder for query values; same function the picker already had, hoisted to file scope so the list pages can use it without re-importing.The CompanyContactsCard on the company-detail page had an 'Add Contact' affordance that pointed at /contacts/new and dropped the user into an empty form. They then had to retype the company name into the picker they just clicked away from. Make the add-contact flow round-trip the company: - CompanyContactsCard takes the parent company's id + name as props and builds an href as /contacts/new?company_id={id}&company_name={name}. - Switched the affordance from a routed Link to a plain <a href> because Dioxus' Link only accepts a Route enum value and the Route enum doesn't carry query params for ContactNew. The router still intercepts same-origin anchor clicks so navigation stays SPA. - ContactNewPage reads window.location.search via web_sys on mount, parses company_id + company_name with UrlSearchParams, validates the id as a UUID, and seeds the ContactForm's CompanyPicker with the prefilled selection. The picker then renders as the 'selected chip + Change' layout it already had for the edit path, so the user can override if they came from the wrong company by mistake. - An invalid or missing company_id falls back to an empty form - there is no error condition, just an unprefilled CompanyPicker. This closes the 'add contact from company detail' loop without having to refactor every Link { to: Route::ContactNew {} } across the app or thread a typed query param through the Dioxus Route enum.