🌴

La Palma Express

Ferry Terminal

🌴

La Palma Express

Ferry Terminal

v{{ APP_VERSION }}

{{ loginError }}

🔍
🔍

No products found

Current Order

#{{ currentOrderNumber }}

{{ item.name }}

${{ item.price.toFixed(2) }} each

{{ item.quantity }}
${{ (item.price * item.quantity).toFixed(2) }}
Subtotal${{ cartSubtotal.toFixed(2) }}
Discount-${{ cartDiscount.toFixed(2) }}
Tax (11.5%)${{ cartTax.toFixed(2) }}
Total${{ cartTotal.toFixed(2) }}

Search Results

{{ selectedCategoryData.name }}

Favorites

🔍

No products found

Current Order

#{{ currentOrderNumber }}
🔍
👤

{{ getCustomerDisplayName(selectedCustomer) }}

🏠 Resident (10% off)

🛒

Cart is empty

{{ item.name }}

${{ item.price.toFixed(2) }} each

{{ item.quantity }}
${{ (item.price * item.quantity).toFixed(2) }}
Subtotal ${{ cartSubtotal.toFixed(2) }}
Resident Discount (10%) -${{ cartDiscount.toFixed(2) }}
Tax (11.5%) ${{ cartTax.toFixed(2) }}
Total ${{ cartTotal.toFixed(2) }}

Orders

#{{ order.order_number }}

👤 {{ order.customer_name }}

{{ getOrderAge(order.created_at) }}

{{ order.order_type?.replace('_', ' ') }}

{{ order.status }} {{ order.payment_status }}
{{ order.item_count }} items ${{ parseFloat(order.total).toFixed(2) }}
Order Customer Type Items Total Status Payment Actions

#{{ order.order_number }}

{{ getOrderAge(order.created_at) }}

{{ order.customer_name }} {{ order.order_type?.replace('_', ' ') }} {{ order.item_count }} items ${{ parseFloat(order.total).toFixed(2) }} {{ order.status }} {{ order.payment_status }}
📋

No orders found

Products

🔍
{{ selectedProducts.length }} selected
Product Category Price Status Actions
{{ product.category_icon || '📦' }}

{{ product.name }}

{{ product.name_es }}

{{ parseFloat(product.inventory_stock || 0).toFixed(1) }} {{ product.inventory_unit }}
{{ product.category_icon }} {{ product.category_name }} ${{ parseFloat(product.price).toFixed(2) }} {{ product.is_active ? 'Active' : 'Inactive' }}

Categories

Drag to reorder. Order affects display on POS.

{{ category.icon }}

{{ category.name }}

{{ category.name_es || '—' }}

{{ category.is_active ? 'Active' : 'Inactive' }}

Kitchen

Select Recipe

No production recipes configured yet. Tap "Create Recipe" to add one.

{{ kitchenRecipeEditId ? 'Edit Recipe' : 'Create Recipe' }}

Links this recipe to a product on the POS menu. Sales will auto-deduct inventory.

{{ ing._name }}

{{ ing._unit }}

{{ kitchenRecipeDetail.name }}

Makes {{ kitchenRecipeDetail.output_quantity }} per batch

Menu Product: {{ kitchenRecipeDetail.linked_product.name }} ${{ parseFloat(kitchenRecipeDetail.linked_product.price).toFixed(2) }}
No menu product linked — will be auto-created on first batch

Ingredients needed:

{{ ing.item_name }} {{ (ing.quantity_required * kitchenBatchMultiplier).toFixed(2) }} {{ ing.unit }} (have {{ parseFloat(ing.quantity_on_hand).toFixed(2) }})
{{ kitchenBatchMultiplier }}

Record Waste

Closing Count

{{ item.name }}

Expected: {{ parseFloat(item.expected_quantity).toFixed(1) }} {{ item.unit }}

No prepared items to count.

Receive Stock

Activity Log

{{ entry.entry_type === 'batch' ? '🧑‍🍳' : '🗑️' }} {{ entry.entry_type === 'batch' ? 'Batch Produced' : 'Waste Recorded' }} {{ entry.entry_type }}

{{ entry.item_name }}

By {{ entry.performed_by }}

{{ new Date(entry.entry_date).toLocaleDateString() }}

{{ new Date(entry.entry_date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }}

Exp: {{ new Date(entry.expires_at).toLocaleDateString() }}

No activity found{{ kitchenLogFilter !== 'all' ? ' for this filter' : '' }}.

TIME'S UP!

{{ bakingSession.steps[bakingSession.current_step - 1]?.title }}

Baking

{{ recipe.name }}

{{ recipe.output_quantity }} {{ recipe.output_unit }} of {{ recipe.output_item_name }}

Stock: {{ parseFloat(recipe.current_stock).toFixed(1) }}
{{ recipe.step_count }} steps {{ recipe.total_time }} min No steps yet

No production recipes found. Create recipes in the Kitchen section first.

{{ bakingSelectedRecipe.name }} — Steps

Total time: {{ bakingSelectedRecipe.total_time || 0 }} min · {{ (bakingSelectedRecipe.steps || []).length }} steps

{{ step.step_number }}

{{ step.title }}

{{ step.description }}

{{ step.step_type }} {{ step.duration_minutes }}m

No steps yet. Add steps below.

Add Step

{{ bakingSession.recipe_name }}

Batch: {{ bakingSession.batch_multiplier }}x

Step {{ bakingSession.current_step }} of {{ bakingSession.total_steps }} {{ bakingSession.steps[bakingSession.current_step - 1].step_type }}

{{ bakingSession.steps[bakingSession.current_step - 1].title }}

{{ bakingSession.steps[bakingSession.current_step - 1].description }}

{{ formatBakingTimer(bakingTimerSeconds) }}

{{ bakingSession.steps[bakingSession.current_step - 1].duration_minutes }} min total

Ingredients ({{ bakingSession.batch_multiplier }}x batch)
{{ ing.item_name }} {{ (ing.quantity_required * bakingSession.batch_multiplier).toFixed(2) }} {{ ing.unit }}

{{ (user.role === 'admin' || user.role === 'manager') ? 'My Shift Today' : "Today's Baking" }}

{{ bakingMyShift.status }}
{{ formatTime12h(bakingMyShift.start_time) }} • Shift: {{ Math.floor(bakingMyShift.total_budget_minutes / 60) }}h {{ bakingMyShift.total_budget_minutes % 60 }}m • Scheduled: {{ Math.floor(bakingMyShift.total_scheduled_minutes / 60) }}h {{ bakingMyShift.total_scheduled_minutes % 60 }}m
Start: {{ formatTime12h(bakingMyShift.start_time) }} Today's Baking List
{{ bakingMyShift.items_completed }} of {{ bakingMyShift.items_total }} items done
{{ idx + 1 }}. {{ item.recipe_name }} ({{ item.batch_multiplier }}x) {{ item.actual_minutes }}m {{ item.estimated_minutes }}m est Done Baking Pending
Recipe User Started Status Multiplier
{{ s.recipe_name }} {{ s.user_name }} {{ new Date(s.started_at).toLocaleString() }} {{ s.status }} {{ s.batch_multiplier }}x

No baking sessions yet.

Inventory

Track ingredients and supplies

⚠️ Low Stock Alert

{{ parReportSummary.critical || 0 }}

Critical

{{ parReportSummary.low || 0 }}

Low

{{ parReportSummary.ok || 0 }}

OK

Item On Hand PAR Min Target Order Qty Status

{{ item.name }}

{{ item.category_tag || '' }} {{ item.supplier ? '/ ' + item.supplier : '' }}

{{ parseFloat(item.quantity_on_hand).toFixed(1) }} {{ item.unit }} {{ parseFloat(item.par_min).toFixed(1) }} {{ parseFloat(item.par_target).toFixed(1) }} {{ item.suggested_order_qty > 0 ? parseFloat(item.suggested_order_qty).toFixed(1) + ' ' + item.unit : '—' }} ({{ item.suggested_purchase_units }} {{ item.purchase_unit }}s) {{ item.status }}

Item

Supplier

On Hand Reorder Level Cost Actions

{{ item.name }} Prepared Borrowed Shared

{{ item.supplier || '—' }}

{{ item.reorder_level }} {{ item.unit }} ${{ parseFloat(item.cost_per_unit).toFixed(2) }}/{{ item.unit }}
Read-only at this stand

Customers

Customer Contact Resident Orders Visits Total Spent Actions

{{ getCustomerDisplayName(customer) }}

{{ customer.first_name }} {{ customer.last_name }}

{{ customer.email || '—' }}

{{ formatDisplayPhone(customer.phone) || '—' }}

🏠 0
{{ customer.loyalty_visits }} visits {{ customer.loyalty_rewards_lifetime }} earned

Free item earned!

{{ customer.loyalty_visits_month || 0 }} this month

${{ parseFloat(customer.total_spent || 0).toFixed(2) }}

Dashboard

Today's Revenue

${{ dashboardData?.today?.total_revenue?.toFixed(2) || '0.00' }}

Orders Today

{{ dashboardData?.today?.total_orders || 0 }}

Active Orders

{{ dashboardData?.active_orders || 0 }}

Low Stock Items

{{ dashboardData?.low_stock_count || 0 }}

Top Products Today

{{ index + 1 }} {{ product.product_name }}

{{ product.quantity }} sold

${{ parseFloat(product.revenue).toFixed(2) }}

AI Insights

AI-powered business intelligence

⚠️

API Key Required

Add your Anthropic API key in Settings to enable AI insights.

Latest Insight - {{ aiLatestInsight.insight_date }}

{{ aiLatestInsight.insight_type }} analysis | Confidence: {{ Math.round((aiLatestInsight.confidence_score || 0) * 100) }}%

{{ aiLatestInsight.tokens_used?.toLocaleString() }} tokens
${{ Number(aiLatestInsight.cost_usd).toFixed(4) }}
~${{ (aiLatestInsight.tokens_used * 0.000006).toFixed(4) }}

{{ aiLatestInsight.full_analysis?.summary || aiLatestInsight.summary }}

{{ aiLatestInsight.model_used }} In: {{ aiLatestInsight.input_tokens?.toLocaleString() }} Out: {{ aiLatestInsight.output_tokens?.toLocaleString() }} {{ (aiLatestInsight.generation_time_ms / 1000).toFixed(1) }}s

📊 Sales Analysis

{{ aiLatestInsight.full_analysis?.sales_analysis?.overview }}

Trends

  • {{ trend }}

Peak Hours

{{ h }}

🌤️ Weather Impact

{{ aiLatestInsight.full_analysis?.weather_correlation?.impact }}

  • {{ p }}

🍽️ Product Recommendations

Feature These

{{ p }}

Consider Promoting

{{ p }}

Watch List

{{ p }}

✅ Action Items

{{ item.priority }}

{{ item.action }}

{{ item.reason }}

No action items

⚡ Anomalies Detected

  • ⚠️ {{ a }}

🔮 Forecast & Preparations

{{ aiLatestInsight.full_analysis.forecasting.outlook }}

  • {{ p }}
🤖

No Insights Yet

Generate your first AI insight to get started with business intelligence.

📋 Business Notes

Notes are included as context for AI analysis (events, promotions, weather notes, etc.)

No business notes yet. Add notes to give the AI context about your business.

{{ noteTypes.find(t => t.value === note.note_type)?.icon || '📝' }}

{{ note.title }}

{{ note.note_type }}

{{ note.content }}

{{ note.note_date }} | by {{ note.author_first_name || 'System' }}

Loyalty Program

Manage rewards rules and view reports

Enrolled

{{ loyaltyReport.enrolled_customers }}

Active This Month

{{ loyaltyReport.active_this_month }}

Rewards Issued

{{ loyaltyReport.rewards_issued_this_month }}

Redeemed ($)

${{ (loyaltyReport.redemption_value_this_month || 0).toFixed(2) }}

Top Visitors

{{ v.first_name }} {{ v.last_name }} ({{ v.total_visits }} visits)
Rule Type Reward Active Actions

{{ rule.name }}

{{ rule.reward_description }}

Every {{ rule.trigger_count }} visits Surprise ({{ (rule.surprise_probability * 100).toFixed(0) }}%) Category ({{ rule.trigger_count }}) {{ rule.rule_type }} Free Item ${{ parseFloat(rule.reward_value).toFixed(2) }} off {{ rule.reward_value }}% off {{ rule.is_active ? 'Yes' : 'No' }}

No loyalty rules configured.

Settings

Business Information

POS Settings

Order Time Alerts

Order age color: green → yellow (warning) → red (urgent)

Stripe

Accept card payments via Stripe Checkout

Test Mode ACTIVE

{{ stripeConfig.test_secret_key_masked || 'Not set' }}

{{ stripeConfig.test_webhook_secret_masked || 'Not set' }}

Live Mode ACTIVE

{{ stripeConfig.live_secret_key_masked || 'Not set' }}

{{ stripeConfig.live_webhook_secret_masked || 'Not set' }}

Leave blank to auto-detect. Required if the app is behind a proxy.

Webhook URL: {{ stripeConfig.webhook_url || 'Unavailable until app has a public URL' }}

Register this URL in your Stripe dashboard → Webhooks. Events needed: checkout.session.completed

Payment Methods

Cash is always available. Stripe uses a hosted Checkout page and records completed payments back into orders and reports.

🤖 AI Insights

Required for AI insights. Get one at console.anthropic.com

Optional. Enables weather correlation analysis.

{{ step.status === 'pass' ? 'Pass' : step.status === 'skip' ? 'Skip' : 'FAIL' }} {{ key.replace(/_/g, ' ') }} {{ step.detail || step.version || '' }}{{ step.time_s ? ' (' + step.time_s + 's)' : '' }}

Locations

No locations yet.
{{ loc.name }} Inactive Kitchen Selected
/menu?loc={{ loc.slug }}{{ loc.address ? ' · ' + loc.address : '' }}

{{ locationForm.id ? 'Edit Location' : 'Add Location' }}

/menu?loc=slug

Account Settings

Change Password

Change PIN

Time Clock

{{ timeClockElapsed || '00:00:00' }}

Clocked in since {{ new Date(timeClockStatus.entry?.clock_in_at).toLocaleTimeString() }}

--:--:--

Not clocked in

This week total {{ Number(timeClockSummary.total_hours || 0).toFixed(1) }}h
Date Clock In Clock Out Hours Status
{{ new Date(entry.clock_in_at).toLocaleDateString() }} {{ new Date(entry.clock_in_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }} {{ entry.clock_out_at ? new Date(entry.clock_out_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '—' }} {{ entry.clock_out_at ? (((new Date(entry.clock_out_at) - new Date(entry.clock_in_at)) / 3600000) - (entry.break_minutes || 0) / 60).toFixed(1) + 'h' : '—' }} {{ entry.status === 'closed' ? 'Ready to Review' : entry.status }}
No entries this week
${{ myPaySummary.last_payment_amount != null ? Number(myPaySummary.last_payment_amount).toFixed(2) : '0.00' }}
Last Paid
${{ Number(myPaySummary.since_last_payroll?.estimated || 0).toFixed(2) }}
Since Last Payroll
{{ Number(myPaySummary.since_last_payroll?.hours || 0).toFixed(1) }}h
Unpaid Hours
${{ Number(myPaySummary.pay_rate || 0).toFixed(2) }}/hr
Pay Rate

{{ myPayView === 'mtd' ? 'Month to Date' : 'Year to Date' }}

{{ Number(myPaySummary[myPayView]?.hours || 0).toFixed(1) }}h
Hours Worked
${{ Number(myPaySummary[myPayView]?.paid || 0).toFixed(2) }}
Total Paid
${{ Number(myPaySummary[myPayView]?.estimated || 0).toFixed(2) }}
Total Earned (Est.)

Upcoming Payroll

Period Hours Rate Est. Gross Status
{{ item.start_date }} — {{ item.end_date }} {{ Number(item.hours_worked).toFixed(1) }}h ${{ Number(item.rate).toFixed(2) }}/hr ${{ Number(item.gross_amount).toFixed(2) }} due
Since last payroll {{ Number(myPaySummary.since_last_payroll?.hours || 0).toFixed(1) }}h ${{ Number(myPaySummary.pay_rate || 0).toFixed(2) }}/hr ${{ Number(myPaySummary.since_last_payroll?.estimated || 0).toFixed(2) }} estimate
No hours since last payroll

Payment History

Period Amount Method Reference Date
{{ row.start_date }} — {{ row.end_date }} ${{ Number(row.amount).toFixed(2) }} {{ row.method || '—' }} {{ row.reference || '—' }} {{ row.date ? new Date(row.date).toLocaleDateString() : '—' }}
No payments recorded yet
{{ timeClockFilteredEntries.length }} entries
Staff Date In Out Hours Status Actions
{{ entry.nickname || entry.first_name }} {{ new Date(entry.clock_in_at).toLocaleDateString() }} {{ new Date(entry.clock_in_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }} {{ entry.clock_out_at ? new Date(entry.clock_out_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '—' }} {{ entry.clock_out_at ? (((new Date(entry.clock_out_at) - new Date(entry.clock_in_at)) / 3600000) - (entry.break_minutes || 0) / 60).toFixed(1) + 'h' : '—' }} {{ entry.status === 'closed' ? 'Ready to Review' : entry.status }}
No entries to review

Edit Time Entry

Payroll

Period Employees Total Gross Total Paid Status Actions
{{ period.start_date }} — {{ period.end_date }} {{ period.employee_count || 0 }} ${{ Number(period.total_gross || 0).toFixed(2) }} ${{ Number(period.total_paid || 0).toFixed(2) }} {{ period.status }}
No pay periods yet

{{ payrollSelectedPeriod.start_date }} — {{ payrollSelectedPeriod.end_date }}

Created by {{ payrollSelectedPeriod.created_by_name }}

{{ payrollSelectedPeriod.status }}
Employee Hours Rate Gross Notes Status Actions
{{ item.nickname || item.first_name }} {{ item.last_name || '' }} {{ Number(item.hours_worked).toFixed(1) }}h ${{ Number(item.rate).toFixed(2) }}/hr ${{ Number(item.gross_amount).toFixed(2) }} {{ item.notes || '' }} {{ item.status }}
No items. Click Generate to create payroll.

Summary

{{ payrollSelectedPeriod.summary.total_employees }}
Employees
{{ payrollSelectedPeriod.summary.total_hours }}
Total Hours
${{ Number(payrollSelectedPeriod.summary.total_gross).toFixed(2) }}
Total Gross
${{ Number(payrollSelectedPeriod.summary.total_paid).toFixed(2) }}
Paid ({{ Number(payrollSelectedPeriod.summary.total_remaining).toFixed(2) }} remaining)

{{ payrollSelectedPeriod.status === 'processing' ? 'Employee Signature' : 'Manager Signature' }}

Verified & Locked

Manager: {{ payrollSelectedPeriod.verified_by_name }}

Finalized: {{ new Date(payrollSelectedPeriod.finalized_at).toLocaleString() }}

Employee
Manager

Activity Log

{{ new Date(entry.created_at).toLocaleString() }} {{ entry.nickname || entry.first_name || 'System' }} {{ entry.details || entry.action }}

New Pay Period

Creates period for the current week (Monday - Sunday).

Record Payment

{{ payrollPayingItem.nickname || payrollPayingItem.first_name }} — ${{ Number(payrollPayingItem.gross_amount).toFixed(2) }}

Expenses

Total

${{ Number(expenseReport.totals?.total || 0).toFixed(2) }}

Count

{{ expenseReport.totals?.count || 0 }}

{{ cat.category }}

${{ Number(cat.total).toFixed(2) }}

Date Vendor Category Amount Method Actions
{{ exp.expense_date }} {{ exp.vendor || '—' }} {{ exp.category }} ${{ Number(exp.amount).toFixed(2) }} {{ exp.payment_method }}
No expenses found

{{ editingExpense ? 'Edit' : 'Add' }} Expense

AI-scanned receipt data — review and adjust before saving

Till Count

{{ tillSelectedSession.session_date }} — {{ tillSelectedSession.shift }}

{{ tillSelectedSession.status }}

Starting Cash

${{ Number(tillSelectedSession.starting_cash || 0).toFixed(2) }}

${{ Number(tillSelectedSession.pos_cash_sales_total || 0).toFixed(2) }}
${{ Number(tillSelectedSession.cash_drop_amount || 0).toFixed(2) }}

Denomination Count

{{ d.label }}
{{ d.qty }}
${{ (d.value * d.qty).toFixed(2) }}
Total: ${{ tillDenominations.reduce((sum, d) => sum + d.value * d.qty, 0).toFixed(2) }}

Paid Outs

{{ po.time }} — {{ po.description }} ({{ po.employee_initial }})
${{ Number(po.amount).toFixed(2) }}

Receipt Paid Outs

{{ rpo.receipt_store }} — {{ rpo.description }}
${{ Number(rpo.amount).toFixed(2) }}

Notes

{{ tillSelectedSession.notes || 'No notes' }}

Summary

Starting Cash
${{ Number(tillSelectedSession.starting_cash || 0).toFixed(2) }}
+ POS Cash Sales
${{ Number(tillSelectedSession.pos_cash_sales_total || 0).toFixed(2) }}
- Cash Drops
${{ Number(tillSelectedSession.cash_drop_amount || 0).toFixed(2) }}
- Paid Outs
${{ Number(tillSelectedSession.paid_outs_total || 0).toFixed(2) }}
- Receipt Paid Outs
${{ Number(tillSelectedSession.receipt_paid_outs_total || 0).toFixed(2) }}
Expected Cash
${{ Number(tillSelectedSession.expected_cash_in_drawer || 0).toFixed(2) }}
Actual Cash (denominations)
${{ tillDenominations.reduce((sum, d) => sum + d.value * d.qty, 0).toFixed(2) }}
Over/Short {{ tillSelectedSession.over_short_status || (Number(tillSelectedSession.over_short_amount) > 0 ? 'OVER' : Number(tillSelectedSession.over_short_amount) < 0 ? 'SHORT' : 'EVEN') }}
${{ Number(tillSelectedSession.over_short_amount || 0).toFixed(2) }}

{{ tillSelectedSession.status === 'open' ? 'Employee Signature' : 'Manager Signature' }}

Employee signature Manager signature

Verified by {{ tillSelectedSession.verified_by_name || 'Manager' }}

{{ tillSelectedSession.finalized_at ? new Date(tillSelectedSession.finalized_at).toLocaleString() : (tillSelectedSession.locked_at ? new Date(tillSelectedSession.locked_at).toLocaleString() : '') }}

Employee

Employee signature

Manager

Manager signature

Awaiting Manager Verification

Submitted {{ tillSelectedSession.submitted_at ? new Date(tillSelectedSession.submitted_at).toLocaleString() : '' }}

Employee Signature

Employee signature

No active till session for today

Date Shift Opened By Over/Short Status Actions
{{ session.session_date }} {{ session.shift?.replace('_', ' ') }} {{ session.nickname || session.first_name || '—' }} ${{ Number(session.over_short_amount || 0).toFixed(2) }} {{ session.status }}
No till sessions found

New Till Session

Console

{{ user?.nickname || user?.first_name }} — {{ user?.role }}

Pick a location to see what you can do here.

No apps are enabled for your account at this location.

{{ group.label }}

Staff Management

Name Role Status Last Login Actions
{{ (member.nickname || member.first_name || 'U').charAt(0).toUpperCase() }}

{{ member.nickname || (member.first_name + ' ' + member.last_name) }}

{{ member.email }}

{{ member.role }} Active Inactive {{ timeSince(member.last_login) }}
No staff members found
Loading…
No events match this filter.
When Who Action IP
{{ formatFullTimestamp(ev.created_at) }} {{ ev.user_name || (ev.user_id ? ('User #' + ev.user_id) : '—') }} {{ loginActionLabel(ev.action) }} {{ ev.ip_address || '—' }}

Invite New Staff

Generate an invite code that new staff can use to create their account.

Share this code with the new staff member:

{{ generatedInviteCode }}

Valid for 7 days. They can use this on the login page.

Suggestion Box

No open suggestions
{{ s.nickname || s.first_name }} {{ new Date(s.created_at).toLocaleString() }}

{{ s.message }}

No reviewed suggestions
{{ s.nickname || s.first_name }} {{ new Date(s.created_at).toLocaleString() }}

{{ s.message }}

Reply by {{ s.replied_nickname || s.replied_first_name }} — {{ s.replied_at ? new Date(s.replied_at).toLocaleString() : '' }}

{{ s.admin_reply }}

Submit a Suggestion

Replies to your suggestions:

Your suggestion: {{ n.message.length > 80 ? n.message.slice(0, 80) + '...' : n.message }}

{{ n.admin_reply }}

Get Stock from Main

Pick how many of each you're taking to {{ selectedLocation?.name || 'this location' }}.

Loading…
No items are shared by Main yet. Ask an admin to flag items as "Share with other locations" from the inventory editor.
No items match "{{ borrowSearch }}"

{{ it.name }}

Main has {{ parseFloat(it.owner_quantity).toFixed(it.unit === 'each' ? 0 : 1) }} {{ it.unit }} · You have {{ parseFloat(it.borrowed_quantity).toFixed(it.unit === 'each' ? 0 : 1) }}

{{ editingStaff ? 'Edit Staff' : 'Add Staff' }}

A new PIN will be assigned and shown after saving.

A unique PIN will be auto-generated and shown after creating the staff member.

Apply everywhere
Per location
Copy from:
Select a location tab above to edit its permissions.

Complete Payment

Order #{{ payingOrder.order_number }}

Reward Available!

{{ customerRewards[0].reward_description }}

Reward Applied — ${{ loyaltyDiscountEstimate.toFixed(2) }} off

No qualifying items in order

{{ loyaltyRewardForPayment.reward_description }}

{{ customerLoyalty.visits_until_reward }} more visit{{ customerLoyalty.visits_until_reward > 1 ? 's' : '' }} until a free item!

Visit {{ customerLoyalty.total_visits % customerLoyalty.reward_trigger_count + 1 }} of {{ customerLoyalty.reward_trigger_count }}

Total Amount

${{ (payingOrder ? parseFloat(payingOrder.total) : cartTotal).toFixed(2) }}

${{ paymentTotal.toFixed(2) }}

-${{ loyaltyDiscountEstimate.toFixed(2) }} loyalty reward

Stripe opens a secure hosted checkout page and marks the order paid when the checkout finishes.

Customer pays here

Ask them to scan the QR on the counter, or share the link:

Pay link

{{ stripeQR.payUrl }}

Waiting for payment...

Order #{{ selectedOrder?.order_number }}

{{ selectedOrder?.created_at }}

👤

{{ selectedOrder.customer_name }}

🏠 Vieques Resident

No customer attached

Status

{{ selectedOrder.status }}

Payment

{{ selectedOrder.payment_status }}

Type

{{ selectedOrder.order_type?.replace('_', ' ') }}

Items

{{ item.product_name }}

{{ item.quantity }} x ${{ parseFloat(item.unit_price).toFixed(2) }}

${{ parseFloat(item.subtotal).toFixed(2) }}

Subtotal ${{ parseFloat(selectedOrder.subtotal).toFixed(2) }}
Tax ${{ parseFloat(selectedOrder.tax_amount).toFixed(2) }}
Discount -${{ parseFloat(selectedOrder.discount_amount).toFixed(2) }}
Loyalty Reward -${{ parseFloat(selectedOrder.loyalty_discount_amount).toFixed(2) }}
Total ${{ parseFloat(selectedOrder.total).toFixed(2) }}

{{ editingProduct ? 'Edit Product' : 'Add Product' }}

📷

JPG, PNG or WebP. Max 2MB.

Kitchen recipe: {{ editingProduct.recipe_name }}

Stock: {{ parseFloat(editingProduct.inventory_stock || 0).toFixed(1) }} {{ editingProduct.inventory_unit }} ({{ editingProduct.inventory_item_name }})

No stock tracked yet — use Add Inventory to set an initial count.

{{ editingCategory ? 'Edit Category' : 'Add Category' }}

{{ categoryForm.icon || '📁' }}

{{ categoryForm.name || 'Category Name' }}

{{ categoryForm.name_es || 'Nombre de Categoría' }}

Quick Add Customer

{{ editingCustomer ? 'Edit Customer' : 'Add Customer' }}

If set, this will display instead of their name

{{ emailError }}

{{ phoneError }}

{{ editingInventory ? 'Edit Inventory Item' : 'Add Inventory Item' }}

Inventory Details

Add Inventory

{{ selectedLocation ? selectedLocation.name : 'All Locations' }}

Loading products...

{{ catName }}

{{ row.product_name }}

Current: {{ parseFloat(row.current_stock).toFixed(0) }} 🍳

No additions recorded yet.

{{ entry.product_name || entry.item_name }}

{{ entry.notes }}

+{{ entry.quantity_change }}

{{ new Date(entry.created_at).toLocaleDateString() }} {{ new Date(entry.created_at).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) }}

Order History

{{ viewingCustomer?.nickname || (viewingCustomer?.first_name + ' ' + viewingCustomer?.last_name) }}

No orders found

#{{ order.order_number }}

{{ new Date(order.created_at).toLocaleDateString() }} at {{ formatTime(order.created_at) }}

{{ order.item_count }} items

${{ parseFloat(order.total).toFixed(2) }}

{{ order.status }}
Total Orders: {{ viewingCustomer?.total_orders || 0 }} Lifetime Spent: ${{ parseFloat(viewingCustomer?.total_spent || 0).toFixed(2) }}

{{ editingLoyaltyRule ? 'Edit' : 'Add' }} Loyalty Rule

{{ loyaltyRewardProduct.name }} costs ${{ parseFloat(loyaltyRewardProduct.price).toFixed(2) }} but max reward is ${{ parseFloat(loyaltyRuleForm.max_reward_value).toFixed(2) }}. Customer pays the ${{ (parseFloat(loyaltyRewardProduct.price) - parseFloat(loyaltyRuleForm.max_reward_value)).toFixed(2) }} difference. {{ loyaltyRewardProduct.name }} (${{ parseFloat(loyaltyRewardProduct.price).toFixed(2) }}) is fully covered.

{{ editingNote ? 'Edit Note' : 'Add Business Note' }}

{{ user?.first_name?.charAt(0) || 'U' }}

{{ user?.nickname || user?.first_name }} {{ user?.last_name }}

{{ user?.role }}

POS Theme

Pick a vibe for your workspace

Your PIN

New PIN generated

{{ generatedPin }}

Remember this — it won't be shown again

Your PIN is used to quickly sign in at the terminal.

Select Location

Which location are you working at?

No active locations yet.
Add or activate one in Settings → Locations.
{{ toast.message }} tap to copy copied!