Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/fredy-rizo/despacho-frontend/llms.txt

Use this file to discover all available pages before exploring further.

The admin dashboard, rendered by dashboardPage.vue at the /dashboard route, gives authorized staff a full view of all reservation submissions made through the public booking form. Every API call is authenticated with a Bearer token extracted from localStorage, and every response is passed through ValidateSession to catch expired or invalid sessions before they cause silent failures. The page loads automatically on mount and supports real-time client-side search across client names and email addresses.

Access Control

The dashboard is not guarded by Vue Router navigation guards. Instead, protection is enforced at the data-layer: every function that touches the API first calls getAuthHeaders(), which internally invokes validateUser({ rol: 1 }).
// src/pages/users/whoWeAre/dashboardPage.vue
const getAuthHeaders = () => {
  const user = validateUser({ rol: 1 })
  if (!user) return router.push("/login")

  const headers = new Headers()
  headers.append("authorization", "Bearer " + user.token)
  headers.append("Content-Type", "application/json")
  return headers
}
If validateUser({ rol: 1 }) returns a falsy value — because the token is missing, expired, or the stored role does not match 1 — the user is immediately redirected to /login and the API call is aborted. No reservation data is ever fetched for unauthenticated visitors.

Session Validation

After each API response, ValidateSession(data, router) is called with the parsed JSON and the Vue Router instance. If the server returns a 401 or 403 status code embedded in the response body, ValidateSession clears localStorage and redirects to /login automatically:
const data = await res.json()
if (!ValidateSession(data, router)) return
This two-layer approach (validateUser before the request, ValidateSession after) ensures stale tokens are handled both proactively and reactively.

Loading Reservations

cargarReservas(modo) is called on onMounted() (defaulting to 'all') and again whenever a filter button is clicked. The modo argument determines which endpoint is targeted:
POST http://localhost:2101/api/form/reserve/list/reservation-all
Authorization: Bearer <token>
Content-Type: application/json
Returns every reservation in the system regardless of reservation_accepted status.
POST http://localhost:2101/api/form/reserve/list/reservation-true
Authorization: Bearer <token>
Content-Type: application/json
Returns only reservations where reservation_accepted === true.
POST http://localhost:2101/api/form/reserve/list/reservation-false
Authorization: Bearer <token>
Content-Type: application/json
Returns only reservations where reservation_accepted === false.
The filter buttons in the UI map to these modes directly:
Button labelcargarReservas argument
Todas'all'
Aceptadas'true'
Pendientes'false'

Reservations Table

Reservations are displayed in a q-table with row-key="_id", a loading state controlled by loading.value, and rows sourced from the filteredRows computed property.

Table Columns

namelabelfieldalign
full_nameNombrefull_nameleft
emailEmailemailleft
required_serviceServiciorequired_servicecenter
preferred_dateFechapreferred_datecenter
preferred_hourHorapreferred_hourcenter
actionsAccionesactionscenter
The search input (q-input, outlined, dense, clearable, rounded) is bound to the search ref. The filteredRows computed property filters reservas on every keystroke:
// dashboardPage.vue — computed property
const filteredRows = computed(() => {
  if (!search.value) return reservas.value
  return reservas.value.filter(
    (r) =>
      r.full_name.toLowerCase().includes(search.value.toLowerCase()) ||
      r.email.toLowerCase().includes(search.value.toLowerCase()),
  )
})
Filtering is entirely client-side — no additional API call is made when the user types. The comparison is case-insensitive via .toLowerCase() on both the row value and the search term.

Row Actions

Each row in the actions column renders up to four q-btn elements (unelevated, rounded, dense). The Confirmar button is conditionally rendered only when props.row.reservation_accepted === false.

1 — Ver (View Detail)

Clicking Ver sets detalle.value = row and opens the modalDetalle dialog. The modal displays every field from the reservation:

Fields shown

full_name, email, phone, required_service, preferred_date, preferred_hour, additional_message (falls back to “Sin mensaje” when empty)

Status chip

A q-chip at the top of the modal shows the reservation state:
Confirmada (color="green-7") when reservation_accepted === true
Pendiente (color="orange-7") when reservation_accepted === false

2 — Confirmar (Confirm Reservation)

1

Trigger

Staff clicks Confirmar on any row where reservation_accepted is false. The button is hidden for already-confirmed reservations.
2

Build request

confirmarReserva(row) calls getAuthHeaders() to obtain a valid Bearer token, then serializes the row’s existing date and time:
const raw = JSON.stringify({
  preferred_date: row.preferred_date,
  preferred_hour: row.preferred_hour,
})
3

Call API

POST http://localhost:2101/api/form/reserve/accept/:id/reserve
Authorization: Bearer <token>
Content-Type: application/json

{ "preferred_date": "2025-09-15", "preferred_hour": "10:30 AM" }
The :id segment is row._id from the table row.
4

Handle response

ValidateSession(data, router) is called on the response. If the session is valid, $q.notify({ type: 'positive', message: 'Reserva confirmada correctamente' }) is shown and cargarReservas() re-fetches the current list.

3 — Modificar (Edit Date/Time)

Clicking Modificar opens the modalModificar dialog pre-populated with the row’s existing date and time:
const abrirModificar = (row) => {
  modificarData.value = {
    id: row._id,
    preferred_date: row.preferred_date,
    preferred_hour: row.preferred_hour,
  }
  modalModificar.value = true
}
The modal contains two q-input fields:
  • preferred_datetype="date", outlined, dense
  • preferred_hourtype="time", outlined, dense
On Guardar, confirmarModificacion() calls the same POST /api/form/reserve/accept/:id/reserve endpoint with the new values, closes the modal on success, shows a positive notify ("Reserva modificada correctamente"), and re-fetches the list.
The modify flow uses the same /accept/:id/reserve endpoint as confirm. The distinction between “modifying” and “confirming” is purely in the payload: Confirmar re-sends the original date/time to set reservation_accepted = true, while Modificar sends new date/time values.

4 — Eliminar (Delete)

const eliminar = async (id) => {
  try {
    const headers = getAuthHeaders()
    if (!headers) return

    const response = await fetch(
      `http://localhost:2101/api/form/reserve/remove/reservation/${id}`,
      { method: "POST", headers },
    )

    console.log(await response.text())
    cargarReservas()
  } catch (error) {
    console.error(error)
  }
}
POST http://localhost:2101/api/form/reserve/remove/reservation/:id
Authorization: Bearer <token>
Content-Type: application/json
No confirmation dialog is shown before deletion. The list is automatically refreshed by calling cargarReservas() after the request completes.
Deletion is immediate and irreversible. There is no undo mechanism or soft-delete — the reservation is permanently removed from the backend. Consider adding a q-dialog confirmation prompt before calling eliminar in production.

API Reference

All dashboard endpoints require a valid Bearer token in the Authorization header.
reservation_accepted
boolean
The primary status field on every reservation object. true means the reservation has been confirmed by staff; false means it is still pending review.
_id
string
MongoDB ObjectId used as the row-key in q-table and as the :id path parameter in accept/remove endpoints.
full_name
string
Client’s full name as submitted through the public booking form.
email
string
Client’s email address.
phone
string
Client’s phone number.
required_service
string
One of the six legal service areas: Derecho civil, Derecho familiar, Derecho mercantil, Derecho penal, Derecho laboral, or Derecho inmobiliario.
preferred_date
string
Preferred consultation date in YYYY-MM-DD format.
preferred_hour
string
Preferred consultation time in H:MM AM/PM format (e.g., "10:30 AM").
additional_message
string
Optional message from the client. May be an empty string or absent.

Logout

The Cerrar sesión button in the page header executes a one-liner that wipes all local storage and redirects to the public landing page:
const logout = () => {
  localStorage.clear()
  router.push("/")
}
This clears the stored token and user role, ensuring that subsequent visits to /dashboard will trigger the validateUser redirect back to /login.

Build docs developers (and LLMs) love