- Added initialTab prop to EditModal for setting the active tab on open. - Updated state management in ClinicDetailPage to handle initial tab for editing. - Refactored openEdit function to set the initial tab before opening the edit modal. - Combined specialties, insurances, and services sections in the sidebar for better organization. - Improved modal rendering using createPortal for better context handling. style: increase z-index for modal overlay - Updated the z-index of the overlay class in styles.css to ensure modals appear above other elements. feat: implement multi-role dashboard functionality - Created a new prompt for multi-role dashboard implementation. - Defined roles and their access levels in the admin panel. - Updated backend to support user role identification and context retrieval. - Enhanced frontend to dynamically render components based on user roles. - Added new routes and components for role-specific dashboards. chore: add skills for admin endpoint and page creation - Created SKILL.md files for adding admin endpoints and pages. - Provided templates and guidelines for implementing new admin features. chore: sync database after entity changes - Added a new skill for syncing the database after any entity modifications.
2.9 KiB
2.9 KiB
name, description
| name | description |
|---|---|
| add-admin-endpoint | Add a new paginated admin API endpoint to AdminApiController. Use when the user wants to add a backend admin list, stats, or action endpoint — things like "add an endpoint for X", "create an admin API for Y", "I need a route that lists Z". |
Target file
src/Admin/Controller/AdminApiController.php
All admin endpoints live here. The class already has #[IsGranted('ROLE_ADMIN')] and injects EntityManagerInterface $em.
Checklist
-
Stats endpoint (optional but standard): a separate
#[Route('/api/v1/admin/{entity}/stats')]method that returns counts via raw SQL ($this->em->getConnection()->fetchOne()). Return with$this->success([...]). -
List endpoint: use QueryBuilder with
->getArrayResult()— never load full entities for list queries (entity getters may not exist for all fields). Pattern:
#[Route('/api/v1/admin/{entities}', methods: ['GET'])]
public function list{Entity}(Request $request): JsonResponse
{
$page = max(1, (int) $request->query->get('page', 1));
$limit = min(100, max(1, (int) $request->query->get('limit', 15)));
$search = trim((string) $request->query->get('search', ''));
$qb = $this->em->createQueryBuilder()
->select('e.id, e.uuid, e.someField, e.createdAt')
->from(SomeEntity::class, 'e');
if ($search !== '') {
$qb->andWhere('e.name LIKE :s')->setParameter('s', "%$search%");
}
$total = (clone $qb)->select('COUNT(e.id)')->getQuery()->getSingleScalarResult();
$items = $qb
->orderBy('e.id', 'DESC')
->setFirstResult(($page - 1) * $limit)
->setMaxResults($limit)
->getQuery()
->getArrayResult();
return $this->paginated($items, (int) $total, $page, $limit);
}
- Action endpoints (toggle status, etc.) follow this shape:
#[Route('/api/v1/admin/{entities}/{uuid}/status', methods: ['POST'])]
public function toggle{Entity}Status(string $uuid): JsonResponse
{
$entity = $this->em->getRepository(SomeEntity::class)->findOneBy(['uuid' => $uuid]);
if (!$entity) return $this->error('NOT_FOUND', 'Entity not found', 404);
$entity->setIsActive(!$entity->getIsActive());
$this->em->flush();
return $this->success(['is_active' => $entity->getIsActive()]);
}
Critical rules
- Always use
getArrayResult()for list queries. Never call entity getters inside admin list methods. createdAtandupdatedAtare Unix integer timestamps — do not format them in PHP, let the frontend handle it.- For JOINs to categories (city, state, specialty), use LEFT JOIN in DQL and select the name field directly into the array result.
- Response shape for lists:
$this->paginated($items, $total, $page, $limit)— frontend readsdata?.datafor items anddata?.meta?.totalRecordsfor count. - Add
useimports for any new entity class at the top of the file. - Run
/sync-dbonly if a new entity or column was added as part of this change.