Skip to main content
The admin dashboard (DashboardAdmin) is a protected single-page interface that gives authenticated administrators full CRUD access to every entity in the system, live operational statistics, advanced filtering, and a complete audit log.
The admin layout (AdminLayout.jsx) is accessible only after a successful login. Unauthenticated requests are redirected. Role verification is performed via the useUserRoles hook before rendering sensitive controls.

Dashboard overview

DashboardAdmin (frontend/src/components/admin/DashboardAdmin.jsx) fetches all entity lists in parallel on mount:
// DashboardAdmin.jsx — initial data load
const [
  sections, habitats, animals, tickets,
  visits, orders, species, conservationStatus,
  provinces, userProfiles, auditLog, exhibits,
] = await Promise.all([
  api.getSections(),
  api.getHabitats(),
  api.getAnimals(),
  api.getTickets(),
  api.getVisits(),
  api.getPurchaseOrders(),
  api.getSpecies(),
  api.getConservationStatuses(),
  api.getProvinces(),
  api.getUsersProfiles(),
  api.getAuditLog(),
  api.getExhibits(),
]);

Statistics panel

DashboardStats renders eight metric cards, each showing a live count and a simulated month-over-month growth indicator:
MetricIconData source
Total usuariosUsersuser-profiles
Total entradasTickettickets
Total animalesActivityanimals
Total visitasCalendarvisits
Total órdenesShoppingCartorders
Total especiesStarspecies
Total hábitatsEyehabitats
Total seccionesDollarSignsections
Four derived metrics are also displayed:
  • Densidad de población — animals per habitat (animals / habitats)
  • Tasa de ocupación — visits as a percentage of total tickets
  • Diversidad de especies — unique species count
  • Eficiencia operativa — habitats per section, expressed as a percentage
The DashboardSidebar lists all entity tabs. The active tab drives the table, filter, and form shown in the main content area:
Tab keyEntity labelEditable fields
ticketsEntradaname, description, price, total_slots, currency
sectionsSecciónname
habitatsHábitatname, nums_animals, description, section
animalsAnimalname, age, species, conservation_status, habitat
visitsVisitaday, total_slots
ordersOrdenemail, status, visit, user
speciesEspeciename, scientific_name, description, image
conservation-statusEstado de Conservaciónname (IUCN code)
provincesProvincianame
user-profilesPerfil de Usuariouser, province, phone, address, birth_date, profile_picture, bio
audit-logLog de Auditoríaread-only
exhibitsExhibiciónvalue, label, title

CRUD operations

Create, update, and delete are dispatched through three maps that route the active tab to the correct API function:
// DashboardAdmin.jsx — operation maps (abbreviated)
const deleteMap = {
  section: api.deleteSection,   habitat: api.deleteHabitat,
  animal:  api.deleteAnimal,    ticket:  api.deleteTicket,
  visit:   api.deleteVisit,     purchaseOrder: api.deletePurchaseOrder,
  species: api.deleteSpecies,   conservationStatus: api.deleteConservationStatus,
  // ...
};

const updateMap = { /* mirrors deleteMap with update* functions */ };
const createMap = { /* mirrors deleteMap with create* functions */ };
Before any create or update is submitted, the form values are validated against the appropriate Yup schema:
const formConfigMap = {
  tickets:              { config: ticketFormConfig,             schema: YupTicketSchema },
  animals:              { config: animalFormConfig,             schema: YupAnimalSchema },
  habitats:             { config: habitatFormConfig,            schema: YupHabitatSchema },
  sections:             { config: sectionFormConfig,            schema: YupSectionSchema },
  species:              { config: speciesFormConfig,            schema: YupSpeciesSchema },
  'conservation-status':{ config: conservationStatusFormConfig, schema: YupConservationStatusSchema },
  visits:               { config: visitFormConfig,              schema: YupVisitSchema },
  orders:               { config: orderFormConfig,              schema: YupOrderSchema },
  provinces:            { config: provinceFormConfig,           schema: YupProvinceSchema },
  'user-profiles':      { config: userProfileFormConfig,        schema: YupUserProfileSchema },
};
Entities with image fields (species, user profiles) use multipart/form-data. All others are sent as plain JSON. Delete operations require confirmation via a SweetAlert2 dialog before the API call is made.

Audit logs

Every create and update action performed through the dashboard writes an entry to the audit log:
// DashboardAdmin.jsx — after a successful save
await api.createAuditLog({
  action:     item?.id ? 'update' : 'create',
  table_name: type,
  record_id:  item?.id || result.id,
  user:       user.id,
  old_values: item?.id ? item : null,
  new_values: values,
});
The AuditLog Django model stores each entry:
# backend/api/apiLog/models.py
class AuditLog(models.Model):
    timestamp  = models.DateTimeField(auto_now_add=True)
    user       = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    action     = models.CharField(max_length=255)
    model      = models.CharField(max_length=255)
    record_id  = models.PositiveIntegerField(null=True)
    details    = models.TextField(blank=True, null=True)

    class Meta:
        ordering = ['-timestamp']
The Logs page (frontend/src/pages/Admin/Logs.jsx) fetches the log from GET /api/admin/audit-logs/ and displays it in a table with columns: ID, User, Action, Timestamp.
// Logs.jsx
useEffect(() => {
  const fetchLogs = async () => {
    const response = await axiosInstance.get('/api/admin/audit-logs/');
    setLogs(response.data);
  };
  fetchLogs();
}, []);
Delete operations are not currently written to the audit log in DashboardAdmin.handleDelete. Only create and update actions produce audit entries.

Role-based access

User roles are managed through Django’s built-in Group model. Each UserProfile holds a many-to-many relation to groups:
# backend/api/auth/models.py
class UserProfile(models.Model):
    user   = models.OneToOneField(User, on_delete=models.CASCADE)
    roles  = models.ManyToManyField(Group, blank=True, related_name='user_profiles')
    # phone, address, birth_date, profile_picture, bio, province ...
The frontend reads the current user’s roles via the useUserRoles hook and passes roleNames to DashboardSidebar to control which sections are visible. The AdminLayout component applies an animated loading screen on entry to prevent flash-of-unauthenticated-content during the transition.

Institutional transparency

The public-facing “Quiénes Somos” section includes a transparency hub powered by several tab components:
ComponentContent
TabInformesInstitutional transparency reports
TabDocumentosOfficial documents
TabCertificacionesCertifications
TabPoliticasPolicies
TabOrganigramaTestimoniosOrganisation chart and testimonials
These tabs are part of the TransparenciaTabs component and are accessible to any visitor without authentication.

Build docs developers (and LLMs) love