- FeatureTable: dispatch { featureId } object instead of raw ID
- ProjectMap: selectFeature listener accepts both formats (direct ID or { featureId })
- project-map.blade.php: move selectFeature inside livewire:init handler so Livewire.emit is available
- Added logging for debugging
Fixes: 'Livewire.emit is not a function' on map click and 'Only arrays and Traversables can be unpacked' on table button
- Move Route::model to register() in AppServiceProvider so it persists in route cache
- Re-register in boot() for runtime
- Fix navigation spacing for language/notification/user menu
All 101 tests pass
La app envía client_updated_at como ISO-8601 con milisegundos y Z
(2026-07-21T10:10:25.006Z). MySQL rechaza ese literal en columnas
DATETIME/TIMESTAMP → SQLSTATE[22007] y la operación /sync fallaba
(inspection.create, etc.). Se normaliza con Carbon a 'Y-m-d H:i:s' en la
zona horaria de la app, una sola vez en handle(), antes de cada handler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El importador de plantillas solo parseaba name/label/type/required/options/
min/max/step, dejando fuera group (sección), question (pregunta corta) y help
(ayuda) que el builder manual sí soporta.
- Nuevo orden de columnas: group,name,label,question,type,required,options,min,max,step,help
- parseRows() rellena las 11 claves (coincide con addField()).
- readFileRows() ya no descarta filas por la 1ª columna (ahora es `group`, que
puede ir vacía); filtra por "fila con algún contenido" y parseRows descarta
las que no tengan `name`.
- CSV de ejemplo actualizado (con BOM UTF-8) y con filas que muestran group/
question/help/select.
- Modal de importación: texto de columnas y tabla de preview muestran Grupo y
Pregunta.
Tests: nuevo test csv_import_parses_group_question_and_help. Suite 95 passing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnóstico (reproducido contra el servidor de pruebas con curl):
- POST /livewire/update (_startUpload) genera la URL firmada correcta:
https://avantee.duckdns.org/livewire/upload-file?expires=...&signature=...
- POST inmediato a esa misma URL → 401. La firma se valida contra la URL que
Laravel RECONSTRUYE de la petición entrante; en esa ruta el proxy no le hace
llegar X-Forwarded-Proto, Laravel percibe http:// y la firma no cuadra.
- Livewire muestra entonces el genérico "The uploadFile failed to upload".
Fix: middleware ForceHttpsScheme (prepend, antes de TrustProxies): si APP_URL
empieza por https://, fija X-Forwarded-Proto=https y X-Forwarded-Port=443 en
todas las peticiones. Con trustProxies('*') ya activo, generación y validación
de firmas usan siempre el mismo scheme. En local/tests (APP_URL http) no hace nada.
Nota servidor: git pull && php artisan config:clear && php artisan config:cache.
Sigue siendo recomendable client_max_body_size 60m en OpenResty para KML grandes.
Suite 94 passing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bug: kmlToGeoJson usaba xpath //kml:Placemark exigiendo el namespace
http://www.opengis.net/kml/2.2. KMLs con xmlns de Google Earth
(http://earth.google.com/kml/2.1) o sin namespace daban 0 placemarks →
la capa se creaba pero SIN elementos.
Fix:
- Todos los xpath usan local-name() (namespace-agnostic).
- parseKmlGeometry navega Point/LineString/Polygon/MultiGeometry sin
depender del prefijo del namespace.
- Soporte KMZ: descomprime el zip y parsea el .kml interno (habitualmente doc.kml).
- Nuevo helper kmlChild() para leer <name>/<description> tolerante al ns.
Tests: SpatialFileConverterTest cubre los tres KMLs (opengis 2.2, google 2.1,
sin xmlns). Suite 94 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Nueva InspectionTemplatesTable (DataTableComponent): columnas Plantilla,
Descripción, Campos, Proyectos (uso) y Acciones; filtros en cabecera por nombre,
descripción y uso (en uso / sin uso). Gateada por manage templates.
- GlobalTemplateManager: el listado HTML se sustituye por la tabla embebida.
Acciones Editar/Borrar viajan por eventos (template-edit/template-delete);
el manager emite templates-changed al guardar/borrar/importar para que la tabla
se refresque (#[On('templates-changed')]).
Tests: GlobalTemplatesTest amplía con tabla (render + permiso, evento edit). Suite 91 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Migración: tabla pivot inspection_template_project; drop de phase_id (asociación
a fase descartada); project_id se mantiene en inspection_templates por compat.
Migra automáticamente las plantillas existentes (project_id → pivot).
- Modelos: Project::inspectionTemplates() ↔ InspectionTemplate::projects() (BTM);
retirada la relación phase() de InspectionTemplate.
- GlobalTemplateManager (nuevo, ruta /inspection-templates, permiso manage templates):
catálogo único global, CRUD + import CSV; sin asociación a fase ni a proyecto.
- ProjectTemplatesPicker (nuevo, ruta projects.templates, permiso edit projects):
lista global con checkbox para asignar/desasignar plantillas al proyecto.
- ProjectMap: el selector de plantillas del mapa lee SOLO las asignadas al proyecto
vía pivot. API móvil (bundle + /templates) adaptado al pivot.
- Eliminado el viejo TemplateManager por-proyecto, su vista wrapper y la tabla
ImportTemplatesTable (ya no aplica: todas son globales).
- Acceso "Plantillas" añadido al menú principal (gate manage templates).
Tests: GlobalTemplatesTest (4). Suite 89 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sustituye el modal (proyecto + checkboxes) por una tabla Rappasoft:
- ImportTemplatesTable lista todas las plantillas de proyectos accesibles (excluye el
destino), con filtros en cabecera (nombre, proyecto) y checkbox de selección (bulk).
- Acción masiva "Importar seleccionadas" copia al proyecto destino (dedupe por nombre)
y avisa a TemplateManager por evento (templates-imported) para refrescar/cerrar.
- TemplateManager: eliminada la lógica vieja (importProjectId/importableTemplates/
selectedImportTemplateIds/importFromProject); el modal embebe la tabla.
Tests: TemplateImportTest adaptado (selección+import, excluye destino). Suite 92 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parte 2 del bloque de plantillas/inspecciones:
- Formulario de inspección (mapa): campos agrupados por sección, muestra la
"pregunta" del campo (con la etiqueta como subtexto) y la ayuda; al final,
apartado de Comentarios (notes) y subida de Fotos (media en la inspección).
- ProjectMap: WithFileUploads + inspectionPhotos; saveInspection adjunta las fotos
como media; Inspection gana relación media().
- Visor de inspección: datos agrupados por sección con pregunta/valor, comentarios
y galería de fotos.
Tests: InspectionFormTest (registra inspección con comentarios + foto). Suite 91 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Builder de plantillas (parte 1 del bloque):
- Cada campo admite "Grupo / sección" (título), "Pregunta / revisar" (texto corto,
tras la etiqueta) y "Comentarios / ayuda" (texto largo, al final).
- Plantillas híbridas: toggle "Global" → project_id null; el listado de un proyecto
muestra sus plantillas + las globales (badge "Global").
Tests: TemplateCreationTest (grupo/pregunta/ayuda persisten; global sin proyecto y
visible en otros proyectos). Suite 90 passing.
Pendiente (parte 2): adaptar el formulario de inspección (grupos, comentarios, fotos)
y el visor de inspección.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- bundle: mapFeature incluye feature_type_id e is_active; nuevo array feature_types
(catálogo) para que el móvil resuelva nombres/colores.
- /sync feature.update acepta is_active y feature_type_id (last-write-wins).
- openapi.yaml: bundle documenta feature_types.
Tests: MobileApiTest cubre feature.update con los nuevos campos + bundle. Suite 88 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Migración: tabla feature_types + columnas feature_type_id (FK) e is_active en features.
Modelo FeatureType; Feature gana featureType() + casts is_active.
- Catálogo de tipos (FeatureTypeManager, ruta /feature-types): CRUD con modal
(nombre, descripción, color), contador de usos; gateado por edit layers.
- Editor de elementos del proyecto (FeatureManager, ruta projects.features):
tabla Rappasoft ProjectFeaturesTable (filtros nombre/capa/tipo en cabecera),
toggle activo/inactivo y borrado individual; modal para editar nombre/tipo/activo.
- Acceso: botón "Elementos" en el dashboard del proyecto (edit layers) y enlace a
"Tipos de elemento" desde el editor.
Tests: FeatureManagementTest (6). Suite 87 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Usa la cabecera secundaria de Rappasoft (setSecondaryHeaderEnabled + secondaryHeaderFilter):
- Elementos: Elemento (texto), Capa (select), Fase (select).
- Inspecciones: Fecha (date), Elemento (texto), Resultado (select), Usuario (select);
Plantilla sin filtro.
Tests: MapTablesTest amplía con filtro de capa funcional. Suite 75 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FeatureTable e InspectionTable (DataTableComponent) sustituyen las tablas HTML
de las pestañas "Elementos" e "Inspecciones" del mapa: búsqueda, orden, filtro
(progreso) y acciones.
- Selección de elemento e "ver inspección" se comunican al ProjectMap por eventos
(map-select-feature / map-view-inspection, vía #[On]); seleccionar abre el panel
de edición y centra el mapa igual que antes.
- Las relaciones requieren sus FKs en additionalSelects (layer_id / feature_id,
template_id, user_id) porque Rappasoft no selecciona '*'.
Tests: MapTablesTest (3). Suite 74 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- app/Livewire: 34 componentes agrupados en Issues/, Projects/, Phases/,
Companies/, Users/, Admin/, Inspections/, Layers/, Media/, Common/
(Client/, Reports/, Forms/, Actions/ ya estaban). Namespaces actualizados.
- resources/views/livewire: vistas sueltas movidas a subcarpetas espejo
(companies/, users/, phases/, roles/, inspections/, media/, common/);
render() actualizado.
- Referencias actualizadas sin romper nada: rutas (FQN, nombres de ruta intactos),
tags <livewire:...>/@livewire() a alias con punto, y use de los tests.
- No tocado: Volt de Breeze (auth/profile/navigation), y el portal cliente
(user-nav/client-projects) que ya tenía referencias inconsistentes.
Verificado: 69 rutas OK, vistas compilan, suite 69 passing (solo 2 pre-existentes
sqlite). autoload regenerado con --ignore-platform-reqs (PHP 8.2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Permiso 'assign companies' propio (antes empresas reutilizaba 'assign users');
concedido a los roles que ya tenían 'assign users'.
- ProjectUsersTable y ProjectCompaniesTable (Rappasoft): búsqueda, filtro por rol,
cambio de rol en línea y quitar; gateadas por assign users / assign companies.
- ProjectUsers/ProjectCompanies quedan como contenedor (form de asignación) que
embebe la tabla y refresca el desplegable vía eventos.
- Unificadas confirmaciones (wire:confirm) y notificaciones (dispatch notify).
Tests: ProjectAssignmentsTest (4). Suite 69 passing (solo 2 pre-existentes sqlite).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- PhaseList pasa a contenedor: botón "Agregar fase" + modal crear/editar con todos
los parámetros (nombre, descripción, orden, color, progreso, fechas previstas y
reales) y validación. Antes "Agregar fase" creaba directamente 'Nueva fase'.
- PhaseTable (Rappasoft): orden, nombre+descripción, barra de progreso, fechas,
color y acciones (editar abre el modal vía evento, actualizar progreso, eliminar);
búsqueda y ordenación. Gateado por 'manage phases' + acceso al proyecto.
Tests: PhaseManagementTest (4). Suite 65 passing (solo 2 pre-existentes sqlite).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Issue::TYPES + typeLabels() (ES) + accessors type_label/type_color; columna type
(string, default 'other') + fillable.
- IssueForm: select "Tipo de incidencia" con validación/carga/guardado.
- IssueTable: columna Tipo (badge) + SelectFilter por tipo.
- IssueDetail: badge de tipo en la cabecera.
- Sync offline: issue.create/update aceptan type; bundle (mapIssue) lo incluye.
Tests: IssuesEnhancementsTest (create muestra el campo vía HTTP, edición persiste) +
MobileApiTest (create con type). Suite 61 passing (solo 2 pre-existentes sqlite).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Campo "Idioma por defecto" al crear/editar usuario (columna locale ya existente),
como desplegable Alpine con banderas SVG reales (no emoji, que en Windows se ven
como "ES"/"GB") servidas localmente: public/images/flags/{es,gb}.svg.
- User: locale añadido a fillable. UserForm: propiedad/validación/guardado de locale.
- LanguageSwitcher de la cabecera usa las mismas banderas SVG.
- Regla CSS [x-cloak] en el layout para evitar parpadeo de desplegables Alpine.
Tests: UserLocaleTest (2) — crear/editar persisten el idioma.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Notificaciones (DB): asignación de incidencia (IssueAssigned), asignación de tarea
(IssueTaskAssigned), comentario (IssueCommented) y cambio de estado
(IssueStatusChanged) a reporter+asignado excluyendo al actor.
- Plantillas de checklist: tabla issue_checklist_templates + modelo, gestor CRUD
(IssueChecklistManager, ruta projects.issues.checklists) y "Aplicar plantilla" en
el detalle (alta masiva de tareas).
- Alertas de vencimiento: columna overdue_notified_at + scope overdue, comando
issues:notify-overdue (programado a diario) que avisa al asignado una sola vez;
badge "vencidas" en la tabla y resaltado por tarea en el detalle.
- Reporte desde el mapa: botón "Incidencia" en el panel del feature seleccionado →
formulario con feature pre-vinculado (IssueForm lee ?feature=).
Tests: IssuesEnhancementsTest (7). Suite 57 passing (solo 2 pre-existentes sqlite).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Web:
- IssueTask + IssueComment (modelos, migraciones, soft-deletes, campos de sync).
Issue gana tasks()/comments() y accessor de % de avance derivado de tareas.
- IssueDetail (página): checklist con asignado/fecha límite/progreso, hilo de
comentarios con foto por comentario, galería de fotos de la incidencia y flujo
de verificación open→in_review→resolved/closed (+reabrir) con notas.
- Creación/edición en páginas propias (IssueForm), sin modal; al guardar redirige
al detalle. Rutas projects.issues.create/edit/show.
- Listado con tabla Rappasoft (IssueTable): filtros por estado/prioridad, búsqueda,
barra de progreso y acciones por fila gateadas por permisos; IssueManager queda
como contenedor (cabecera + stats) que embebe la tabla.
- Seguridad: pertenencia al proyecto + permisos por acción (view/create/edit/delete
issues, upload/delete media) en todos los componentes.
API móvil (offline):
- /sync: issue_task.create/update y issue_comment.create (idempotente, LWW).
- /media: parent_entity issue_task / issue_comment.
- bundle + tombstones incluyen issue_tasks / issue_comments.
- openapi.yaml + MOBILE_SYNC_PROTOCOL.md actualizados.
Tests: MobileApiTest 23 passing (+5); IssuesTablePageTest (3) smoke de la tabla.
Branding: logo RTE International — MAI Group (public/images/logo-rte.png) en login
y navegación; application-logo pasa de SVG por defecto a <img>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Milestone 5 (media):
- POST /api/v1/media — multipart upload, attaches to feature/issue/project/
phase/layer, idempotent by uuid, authz member + 'upload media'. Added
uuid+client_updated_at to media.
- Bundle now includes a 'media' array (URLs) for the project's project/feature/
issue attachments (delta-aware).
Milestone 6 (hardening + docs):
- sync_logs table/model: every applied op is logged; /sync short-circuits on a
repeated op uuid -> 'duplicate' (true idempotency for updates too, not just
creates).
- Rate limiting on login (10/min), sync (60/min), media (120/min).
- docs/openapi.yaml: OpenAPI 3 contract for the mobile team.
Tests: 18 passing (added media upload idempotency + sync_logs idempotency).
The mobile API (Milestones 1-6) is now feature-complete on the webapp side.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SyncController now handles the full mutation vocabulary:
- inspection.create (idempotent by uuid; project/layer derived from feature;
authz member + 'create inspections'; status defaults to completed).
- issue.create (idempotent; authz member + 'create issues').
- issue.update (by server id; authz member + 'edit issues'; sets resolved_at
when resolved/closed; last-write-wins conflict).
- feature.update (by server id; authz member + 'update progress'; recomputes
phase progress; last-write-wins conflict).
- Conflict detection: client_updated_at vs server updated_at → returns
'conflict' with the current server value.
Added uuid + client_updated_at to features/inspections/issues (guarded migration)
and their fillables. Tests: 16 passing (added inspection/issue/feature + conflict).
Note: 2 PRE-EXISTING test failures remain (not from this work, sqlite-only):
ExampleTest expects '/'=200 (app redirects), and the dashboard route uses MySQL
FIELD() which sqlite lacks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ProjectApiController bundle now supports incremental sync:
- ?since=<ISO8601> returns only records changed after that time (phases, layers,
features, inspections, issues, templates), each filtered by its own updated_at.
- 'deleted' tombstones (soft-deleted ids since 'since') for phases/layers/
features/inspections/issues so the device can purge locally.
- Bundle now also includes inspections, issues and inspection templates
(with version + content hash for incremental template download).
- New GET /api/v1/templates (accessible projects, ?since= delta).
Tests: 12 passing (added delta, tombstones, templates cases). Note: the 'since'
query param must be URL-encoded by clients (ISO8601 '+' offset).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Milestone 1 (auth foundation):
- Installed laravel/sanctum; HasApiTokens on User; published config + migration.
- routes/api.php with /api/v1; Sanctum 'ability' middleware alias registered.
- AuthController: POST login (long-lived revocable device token w/ ability
mobile-sync + devices table), GET me, POST logout. New Device model/table.
Milestone 2 (vertical slice, offline-first):
- progress_updates: +uuid (client-generated) +client_updated_at.
- ProjectApiController: GET projects (accessibleBy), GET projects/{id}/bundle
(project/phases/layers/features, membership-authorized).
- SyncController: POST sync — batch ops, idempotent by uuid, per-op result
(applied/duplicate/error), server-set user_id, authz by permission+membership.
Currently handles progress_update.create.
Tests: tests/Feature/Api/MobileApiTest (9 passing) — auth, accessible projects,
bundle authz, sync apply+idempotency, permission enforcement.
Also fixed a latent schema bug: projects.reference (and external_reference_1)
existed in the live DB but had no migration — added a guarded migration so fresh
installs match production.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Finishes Phase 2: the /admin route group no longer requires 'manage all'
globally. Each route is gated by its specific permission so a non-super-admin
role can be granted partial admin access:
- /admin/users (+show) -> can:view users; create -> can:create users;
edit -> can:edit users
- /admin/roles, roles/*, permissions -> can:manage roles
- Aligned the role screens' mount checks (RoleForm/RoleView/RolePermissionManager)
from 'manage all' to 'manage roles'.
- Nav 'Administrator' link now shows on can('view users').
Admins keep full access via Gate::before (manage all). Closure routes
(users/roles lists) are now protected at the route level.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Permissions now actually govern access instead of the hard-coded Admin role:
- Super-admin bypass (see all projects / full access) -> can('manage all')
in Project::scopeAccessibleBy, ProjectMap, ProjectDashboard, PhaseGantt,
LayerManager, ProjectReportController.
- Redundant '|| hasRole(Admin)' fallbacks dropped (Gate::before already lets
manage-all through can()): LayerManager (upload/delete layers), MediaManager
(upload), ProjectMap (update progress), ProjectUsers/ProjectCompanies
(assign users).
- Admin-only screens now gated by the matching permission: AdminUsers/UserView
-> can('view users'), UserForm -> can('create users')|can('edit users'),
CompanyView -> can('view companies').
- MediaManager delete: can('delete media') OR owner.
- Kept UserForm's domain guard (can't remove your own Admin role).
Note: the /admin route group still has middleware can:manage all, so admin
screens stay super-admin-only until that group is relaxed per-route.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New 'Ficha' tab (first, default) on the user view: basic info card
(name/username/email/phone/address/member since) plus the 'Validez de acceso'
card and the Empresa card, moved here from the Permissions tab. The Permissions
tab now focuses on roles + the direct-permissions form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1. Role view (Details tab): a small form to add users to the role (select of
users not yet in the role + Add) and a per-row remove button. Uses
assignRole/removeRole.
2. User view (Permissions tab): the same grouped, collapsible permissions form
with switches — operating on the user's DIRECT permissions
(givePermissionTo/revokePermissionTo). Permissions inherited from a role show
as checked+disabled with a 'from role' tag; per-group All/None too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Permissions tab in the role view:
1. Each section is now a collapsible card (Alpine, chevron rotates).
2. Section header has 'All' / 'None' buttons (setGroup grants/revokes every
permission of that group for the role; Admin keeps 'manage all').
3. Permissions render in a single column: name+description on the left, control
on the right.
4. Controls are DaisyUI toggle switches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Migration: add 'group' and 'description' columns to the permissions table.
- PermissionCatalogSeeder (idempotent updateOrCreate): full catalogue across 11
sections — Proyectos, Fases y progreso, Capas y elementos, Inspecciones,
Incidencias, Empresas, Usuarios, Roles, Informes, Archivos, General. Sets
group + description on existing and creates the new ones; does NOT touch role
assignments. Registered in DatabaseSeeder.
- RoleView: group permission toggles by the 'group' column in a defined section
order and show each permission's description.
DB updated locally (migrate + seed run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1. Roles list now uses a Rappasoft table (RoleTable): search/sort, per-row
view/edit/delete, and built-in bulk selection + 'Delete selected'. The
/admin/roles page is a plain view embedding <livewire:role-table />.
RoleForm create/edit now only has Name + Description (permissions removed).
2. New RoleView page (/admin/roles/{role}) with two tabs:
- 'Details': header with role name + Back button; description with Edit/Delete
buttons; table of users holding the role (avatar+name | last name | status).
- 'Permissions': all permissions grouped by section (by resource), each with a
toggle switch to grant/revoke for this role (Admin keeps 'manage all').
Removed the old RoleManager component/view (superseded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per feedback, 'New role' (and Edit) now open a dedicated page instead of a
modal:
- New RoleForm full-page component + view at /admin/roles/create and
/admin/roles/{role}/edit (name, description, permission checkboxes; saves
and redirects back to the list).
- RoleManager trimmed: the create/edit modal and its logic removed; 'New role'
and the per-row/view-modal Edit are now links to the new pages.
- Kept the read-only View modal, single + bulk delete, and protections.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per request:
- Migration: add nullable 'description' to the roles table.
- RoleManager Livewire component + view at /admin/roles:
* Roles list table with per-row checkboxes for bulk selection (+ select-all)
and a 'Delete selected' bulk action (protected roles skipped).
* 'New role' opens a modal form with just Name + Description (and permission
checkboxes to assign).
* Per-row View / Edit / Delete buttons (View modal shows description,
counts and assigned permissions).
- Admin role stays protected (no rename/delete/lose 'manage all').
- /admin/users links to the new Roles screen; the phase-1 permission matrix
stays available via a 'Matrix view' link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 (additive, doesn't touch existing checks):
- Gate::before grants everything to holders of 'manage all' (the Admin role),
robustly (returns true/null, never false; swallows missing-permission).
- New RolePermissionManager Livewire component + view at /admin/permissions:
editable Roles x Permissions matrix (toggle saves instantly), create/delete
roles, create/delete permissions. Admin role and 'manage all' are protected.
- Link to the screen from /admin/users header.
Roles are editable from the UI as chosen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Login authenticated fine but the landing page crashed (so it looked like
'login doesn't work'):
- bootstrap/app.php didn't register Spatie's middleware aliases -> any route
with role:/permission: threw 'Target class [role] does not exist'.
Registered role / permission / role_or_permission.
- config/livewire.php absent -> default layout is the non-existent
components.layouts.app. ProjectList, PhaseProgress and ReportsDashboard
lacked #[Layout('layouts.app')] -> MissingLayoutException. Added it (the
other 10 routed components already had it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt IssueManager to match the 403-line draft view that had no companion
component:
- Modal create/edit form (title, description, priority, status, assignee,
resolution notes shown when resolved/closed)
- Stats bar (open/in_review/resolved/closed/total) and a styled table
- New methods: openForm/closeForm, resolve, close (+ existing save/delete),
projectUsers for the assignee dropdown, resolved_at kept in sync with status
- render() now points to livewire.issues.issue-manager; deleted the old
89-line stub livewire/issue-manager.blade.php
The Issue model already had everything (resolution_notes, resolved_at,
priority_color/status_color accessors, reporter/feature/assignee relations).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deleted (all superseded, recoverable in git history):
- resources/views/projects/edit.blade.php + ProjectController@edit()
- resources/views/projects/create.blade.php + ProjectController@create()
(projects.create/edit routes point to the Livewire ProjectForm; these
controller methods were excluded from the resource and never invoked)
- app/Livewire/ProjectEditTabs.php + project-edit-tabs.blade.php
(old tabbed editor, functionality recovered inside ProjectForm)
- app/Livewire/LayerUpload.php + layer-upload.blade.php (superseded by LayerManager)
Kept resources/views/livewire/issues/issue-manager.blade.php as a reference
for the future rich Issues screen (its companion component was never built).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The edit/create project page used a stripped-down inline form. Rewired it
to the existing-but-orphaned pieces:
- Project Data uses the rich partial project-data-form (labels-left/field-right
layout, sections Identification/Location/Planning, address search + Leaflet
map with draggable marker + reverse/forward geocoding, country dropdown)
- When editing, tabs added for Phases / Users / Companies (nested Livewire
components phase-list / project-users / project-companies)
- ProjectForm now provides $countryList (the partial's country dropdown needs it)
- Added the map JS the partial was missing: inits #project-location-map, search
box, and calls $this->setLocation(lat,lng,address,country) so the wire:model
fields update
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full restore of the 7d854ff snapshot (2026-06-16 18:05, before the security
review). Forward commit, no history rewrite — f8a1310 and all later commits
remain recoverable in history.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>