Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/luisumit/LaPreviaRestobar/llms.txt

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

The Admin dashboard is the back-office control panel for La Previa Restobar. It gives administrators a real-time dashboard of sales metrics and table occupancy, a filterable product catalog with full create / edit / delete capabilities, a date-range sales report exportable to PDF or CSV, and a QR code that links to the public digital menu. Critically, the Admin is the only role that can add or modify products, set pricing, and configure inventory tracking. AdminMainScreen is reached from AppNavigation once userRole == UserRole.ADMIN is confirmed, and AdminStockScheduler.schedulePeriodicCheck(context) is triggered on every composition via LaunchedEffect(Unit).

Workflow

1

Monitor Operations

The Panel tab renders AdminDashboardSection, which shows eight metric cards (sales today, sales this year, active orders, active products, best-selling product, occupied tables, low-stock count, out-of-stock count) and a StockAlertCard listing product names with alerts.
2

Review Sales Reports

The Reportes tab renders SalesReportSection. A SingleChoiceSegmentedButtonRow filters by AdminReportFilter (Día / Sem. / Mes / Año / Rango). A ReportRangeSelector card exposes two date pickers for custom ranges. Metrics include total sales, gross profit, total orders, charged orders, cancelled orders, products sold, and best-selling product. Reports are exported via viewModel.exportReportToPdf() or viewModel.exportReportToExcel() and saved to Downloads/LaPreviaReportes/.
3

Manage Products

The Productos tab renders ProductsAdminSection. A search field and SingleChoiceSegmentedButtonRow (All / Active / Out of Stock / Low Stock) filter the list. Each ProductAdminCard has edit and delete icon buttons. The floating + button calls viewModel.showProductForm(), opening ProductFormDialog. Tapping an edit icon calls viewModel.showProductForm(product), pre-populating the dialog for update mode.
4

Distribute the Menu QR

The QR tab renders MenuQrSection and MenuQrCard, which generates a ZXing QR bitmap for PUBLIC_MENU_URL = "https://laprevia-restobar.web.app". This QR is intended to be printed for table cards.

Screens

AdminMainScreen

AdminMainScreen is the single-screen entry point for the Admin role. It hosts a Scaffold whose top bar contains the app title, a test notification button (calls AdminStockScheduler.triggerImmediateCheck(context) for immediate verification), and a logout button. Below the top bar, AdminSectionTabs renders a four-tab TabRow (Panel / Reportes / Productos / QR) with a secondary indicator. The FAB only appears when selectedAdminTab == 2 (Productos).

ProductAdminCard

ProductAdminCard (in presentation/screens/admin/) renders a single product row inside ProductsAdminSection. It surfaces the product name, category, sale price, stock level, active status, and whether inventory tracking is enabled. Edit and delete icon buttons delegate to onEdit and onDelete lambdas passed from AdminMainScreen.

ProductFormDialog

ProductFormDialog is a Dialog-wrapped Surface with a scrollable Column of form fields. It is used for both create and edit modes — the product: Product? parameter determines which. Form fields:
FieldTypeNotes
nameOutlinedTextFieldRequired; shows isError = true when blank
descriptionOutlinedTextFieldOptional; up to 3 lines
salePriceOutlinedTextField (numeric)Maps to Product.salePrice: Double?
costPriceOutlinedTextField (numeric)Used for gross profit calculation in reports
trackInventorySwitchWhen true, reveals the stock configuration section
stockOutlinedTextField (numeric)Only shown when trackInventory == true; required when shown
minStockOutlinedTextField (numeric)Threshold used by AdminStockWorker for low-stock alerts
categoryOutlinedTextFieldRequired; auto-populated from existing categories in uiState.categories
imageUrlOutlinedTextFieldOptional URL for product image
isActiveSwitchInactive products are hidden from the waiter’s product catalog
The Crear / Actualizar button is disabled until name.isNotBlank() && category.isNotBlank() && (!trackInventory || stock.toDoubleOrNull() != null).

AdminViewModel

AdminViewModel is a @HiltViewModel that exposes a single AdminUiState data class via a StateFlow, rather than individual StateFlows per field. This simplifies state management for a screen with many concurrent data concerns.

AdminUiState

data class AdminUiState(
    val products: List<Product> = emptyList(),
    val categories: List<String> = emptyList(),
    val tables: List<Table> = emptyList(),
    val dashboardMetrics: AdminDashboardMetrics = AdminDashboardMetrics(),
    val reportFilter: AdminReportFilter = AdminReportFilter.DAY,
    val customReportStart: Long = startOfDay(System.currentTimeMillis()),
    val customReportEnd: Long = endOfDay(System.currentTimeMillis()),
    val report: SalesReport = SalesReport(),
    val isLoading: Boolean = false,
    val error: String? = null,
    val success: String? = null,
    val warning: String? = null,
    val selectedProduct: Product? = null,
    val showProductForm: Boolean = false,
    val showDeleteDialog: Boolean = false,
    val isOffline: Boolean = false,
    val pendingSyncCount: Int = 0,
    val occupiedTables: Int = 0,
    val activeOrdersCount: Int = 0,
    val criticalStockCount: Int = 0
)

Key Methods

MethodDescription
createProduct(product)Saves to Room with syncStatus = "PENDING", then pushes to Firebase if online
updateProduct(product)Updates Room with new version/timestamp, syncs to Firebase
deleteProduct()Deletes from Room and Firebase; uses uiState.selectedProduct
showProductForm(product?)Sets showProductForm = true and selectedProduct; null = create mode
hideProductForm()Resets form state
manualSync()Calls syncManager.syncProducts() and syncManager.downloadProducts()
selectReportFilter(filter)Rebuilds SalesReport for the chosen AdminReportFilter
selectCustomReportRange(start, end)Sets CUSTOM filter with normalised day boundaries
exportReportToPdf()Generates a PDF via PdfDocument and writes to MediaStore Downloads
exportReportToExcel()Generates a BOM-prefixed UTF-8 CSV and writes to MediaStore Downloads
checkLowStockImmediately()Queries Room for tracked products and updates uiState.warning
refreshProducts()Reloads from Room then Firebase

Product CRUD Snippet

fun createProduct(product: Product) {
    viewModelScope.launch {
        val finalProduct = if (product.id.isEmpty())
            product.copy(id = UUID.randomUUID().toString())
        else
            product

        // Offline-first: always write to Room first
        db.productDao().insert(finalProduct.toEntity().copy(syncStatus = "PENDING"))

        if (_isInternetAvailable.value) {
            try {
                firebaseProductRepository.createProduct(finalProduct)
                db.productDao().updateStatus(finalProduct.id, "SYNCED")
                showMessage("Producto '${finalProduct.name}' creado y sincronizado",
                            isSuccess = true)
            } catch (e: Exception) {
                showMessage("Producto guardado localmente. Se sincronizara despues",
                            isWarning = true)
            }
        } else {
            showMessage("SIN INTERNET - Producto guardado localmente", isWarning = true)
        }

        refreshProducts()
        hideProductForm()
        checkLowStockImmediately()
    }
}

connectionStatusText Computed Property

val connectionStatusText: String
    get() =
        if (!_isInternetAvailable.value)        "SIN INTERNET - Modo offline"
        else if (hasPendingSync)                "${_uiState.value.pendingSyncCount} pendiente(s)"
        else                                    "Conectado - Todo sincronizado"
This string is forwarded to ConnectionStatusBanner in the top bar and to the DashboardHeaderCard subtitle in ProductsAdminSection.

Low-Stock Notifications via WorkManager

Product stock alerts are delivered to the admin through Android’s WorkManager rather than through a foreground Firebase listener. This ensures alerts fire even when the app is in the background.

AdminStockWorker

AdminStockWorker is a @HiltWorker that extends CoroutineWorker. It is injected with AppDatabase by Hilt and runs on Dispatchers.IO.
override suspend fun doWork(): Result {
    return withContext(Dispatchers.IO) {
        val products = db.productDao().getAll()
        val trackedProducts = products.filter { it.trackInventory }

        val outOfStock = trackedProducts.filter { it.stock == 0.0 }
        val lowStock   = trackedProducts.filter { it.stock > 0 && it.stock <= it.minStock }

        if (outOfStock.isNotEmpty() || lowStock.isNotEmpty()) {
            sendNotification(outOfStock, lowStock)
        }
        Result.success()
    }
}
sendNotification() posts a PRIORITY_HIGH notification on channel admin_stock_channel. The notification title summarises the counts (N agotados + M stock bajo) and the body lists up to three product names per category, with a “y N más…” suffix when the list is longer. Tapping the notification opens MainActivity with extras open_inventory = true and notification_type = "stock_alert".

AdminStockScheduler

AdminStockScheduler is a Kotlin object that wraps WorkManager enqueueing. It registers a PeriodicWorkRequest under the unique work name "admin_stock_worker" using ExistingPeriodicWorkPolicy.KEEP, so a second call during the same app session does not restart the timer.
fun schedulePeriodicCheck(context: Context) {
    val workRequest = PeriodicWorkRequestBuilder<AdminStockWorker>(
        12, TimeUnit.HOURS,   // repeat interval
         2, TimeUnit.HOURS    // flex interval
    )
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.NOT_REQUIRED)
                .build()
        )
        .setInitialDelay(1, TimeUnit.HOURS)
        .build()

    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        "admin_stock_worker",
        ExistingPeriodicWorkPolicy.KEEP,
        workRequest
    )
}
The scheduler also exposes:
  • cancelPeriodicCheck(context) — cancels the unique work by name.
  • triggerImmediateCheck(context) — enqueues a OneTimeWorkRequest for immediate execution; this is what the notification bell button in AdminMainScreen’s top bar calls during development and testing.
schedulePeriodicCheck is called inside LaunchedEffect(Unit) at the top of AdminMainScreen, so the periodic task is registered (or kept as-is if already scheduled) every time the admin enters their dashboard.
The minStock field in ProductFormDialog directly controls the low-stock threshold used by AdminStockWorker. Products with trackInventory = false are entirely ignored by the worker, regardless of their stock value.

Build docs developers (and LLMs) love