No products found
Current Order
#{{ currentOrderNumber }}{{ item.name }}
${{ item.price.toFixed(2) }} each
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
Orders
#{{ order.order_number }}
👤 {{ order.customer_name }}
{{ getOrderAge(order.created_at) }}
{{ order.order_type?.replace('_', ' ') }}
| 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
| Product | Category | Price | Status | Actions | |
|---|---|---|---|---|---|
|
{{ 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.name }}
{{ category.name_es || '—' }}
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
Ingredients needed:
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.item_name }} — {{ parseFloat(entry.quantity_produced).toFixed(1) }} {{ entry.unit }} ({{ entry.recipe_name }}, x{{ entry.batch_multiplier }}) {{ entry.linked_product_name }} — {{ Math.abs(parseFloat(entry.quantity_change)).toFixed(1) }} {{ entry.unit }} ({{ entry.waste_reason }})
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 }}
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.title }}
{{ step.description }}
No steps yet. Add steps below.
Add Step
{{ bakingSession.recipe_name }}
Batch: {{ bakingSession.batch_multiplier }}x
{{ bakingSession.steps[bakingSession.current_step - 1].title }}
{{ bakingSession.steps[bakingSession.current_step - 1].description }}
{{ bakingSession.steps[bakingSession.current_step - 1].duration_minutes }} min total
Ingredients ({{ bakingSession.batch_multiplier }}x batch)
{{ (user.role === 'admin' || user.role === 'manager') ? 'My Shift Today' : "Today's Baking" }}
{{ bakingMyShift.status }}No shifts scheduled for this date
{{ shift.user_name }} ({{ shift.user_role }})
| 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.
{{ bakingSelectedShift.user_name }}'s Shift
No items in this shift.
{{ bakingEditingShift ? 'Edit Shift' : 'Create Shift' }}
Shift Items
No recipes found
No items added yet. Search recipes above to add.
Scheduled time exceeds shift duration
Inventory
Track ingredients and supplies
{{ 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 || '—' }} |
{{ parseFloat(item.borrowed_quantity_here || 0).toFixed(item.unit === 'each' ? 0 : 1) }} {{ item.unit }}
Main has {{ item.quantity_on_hand }}
{{ item.quantity_on_hand }} {{ item.unit }}
|
{{ 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
{{ 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.full_analysis?.summary || aiLatestInsight.summary }}
📊 Sales Analysis
{{ aiLatestInsight.full_analysis?.sales_analysis?.overview }}
Trends
- • {{ trend }}
Peak Hours
🌤️ Weather Impact
{{ aiLatestInsight.full_analysis?.weather_correlation?.impact }}
- • {{ p }}
🍽️ Product Recommendations
Feature These
Consider Promoting
Watch List
✅ Action Items
{{ 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.
{{ 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
| 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
{{ stripeConfig.test_secret_key_masked || 'Not set' }}
{{ stripeConfig.test_webhook_secret_masked || 'Not set' }}
{{ 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.
Locations
{{ locationForm.id ? 'Edit Location' : 'Add Location' }}
/menu?loc=slug
Account Settings
Change Password
Change PIN
Time Clock
Clocked in since {{ new Date(timeClockStatus.entry?.clock_in_at).toLocaleTimeString() }}
Not clocked in
| 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 | ||||
{{ myPayView === 'mtd' ? 'Month to Date' : 'Year to Date' }}
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 | ||||
| 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 }}
| 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.status === 'processing' ? 'Employee Signature' : 'Manager Signature' }}
Manager: {{ payrollSelectedPeriod.verified_by_name }}
Finalized: {{ new Date(payrollSelectedPeriod.finalized_at).toLocaleString() }}
Activity Log
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
Till Count
{{ tillSelectedSession.session_date }} — {{ tillSelectedSession.shift }}
{{ tillSelectedSession.status }}Starting Cash
${{ Number(tillSelectedSession.starting_cash || 0).toFixed(2) }}
Denomination Count
Paid Outs
Receipt Paid Outs
Notes
{{ tillSelectedSession.notes || 'No notes' }}
Summary
{{ tillSelectedSession.status === 'open' ? '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
Manager
Awaiting Manager Verification
Submitted {{ tillSelectedSession.submitted_at ? new Date(tillSelectedSession.submitted_at).toLocaleString() : '' }}
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) }} |
| 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
{{ s.message }}
{{ s.message }}
{{ s.admin_reply }}