New appointment

Record grooming visit

Appointment details

Demo only — changes reset on refresh.

Edit pet health & care

Review vaccine information

DEMO: Fields below are fictional example values, NOT extracted from a real file. Compare all information against the original certificate before approval.

+Number(n||0).toFixed(2); function posView(){ const eligible=appointments.filter(a=>a.status==='Completed'||a.status==='In progress'||a.status==='Checked in'); return '
✦ SIMPLE CHECKOUT

POS & Checkout

From completed groom to payment summary, without extra steps.

'+ '

New checkout · Demo

Fictional transactions only. No card processing, real invoices, or permanent records.
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ '
'+ '

Checkout summary

Taxes are illustrative, not calculated by location or product tax rules. Card numbers must never be entered here.

This session

'; } function renderTotals(){ const el=document.getElementById('pk-totals');if(!el)return; const val=id=>Math.max(0,Number(document.getElementById(id).value)||0); const base=val('pk-sale-base'),extra=val('pk-sale-extra'),discount=Math.min(base+extra,val('pk-sale-discount')),tip=val('pk-sale-tip'),tax=val('pk-sale-tax'); const subtotal=base+extra-discount,vat=subtotal*tax/100,total=subtotal+vat+tip; el.innerHTML='
Services & add-ons'+money(base+extra)+'
Discount−'+money(discount)+'
Tax (illustrative)'+money(vat)+'
Tip'+money(tip)+'
Total'+money(total)+'
'; return {base,extra,discount,tip,tax,vat,total}; } function salesHistory(){const el=document.getElementById('pk-sales-history');if(!el)return;el.innerHTML=demoCharges.length?demoCharges.slice().reverse().map(c=>'
'+esc(c.pet)+'
'+esc(c.method)+' · '+esc(c.time)+'
'+money(c.total)+'
').join(''):'

No demo checkouts recorded yet.

'} function bindPos(){ const pick=document.getElementById('pk-sale-appt'); function choose(){const a=appointments.find(x=>x.id===Number(pick.value));document.getElementById('pk-sale-base').value=a?servicePrices[a.service]||50:0;renderTotals()} pick.onchange=choose;choose(); ['pk-sale-base','pk-sale-extra','pk-sale-discount','pk-sale-tip','pk-sale-tax'].forEach(id=>document.getElementById(id).oninput=renderTotals); document.getElementById('pk-clear-sale').onclick=()=>{['pk-sale-extra','pk-sale-discount','pk-sale-tax'].forEach(id=>document.getElementById(id).value=0);document.getElementById('pk-sale-tip').value=10;choose()}; document.getElementById('pk-record').onclick=()=>{ if(!pick.value){alert('Select an appointment first.');return} const a=appointments.find(x=>x.id===Number(pick.value));const t=renderTotals();const method=document.getElementById('pk-sale-method').value; demoCharges.push({...t,pet:pet(a.pet).name,method,time:new Date().toLocaleTimeString(),appointment:a.id}); salesHistory();alert('Demo checkout recorded for this session only. No payment was charged.'); };salesHistory() } function parseCSV(input){ const rows=[],row=[];let field='',quoted=false; for(let i=0;iv.trim()))rows.push(row.slice());row.length=0;field=''}else field+=c} row.push(field);if(row.some(v=>v.trim()))rows.push(row);return rows } function importView(){return '
✦ SWITCH WITHOUT STARTING OVER

Import Customers

Review a spreadsheet before bringing customers and pets into PawKeeper.

'+ '

1 · Paste a CSV export

Safe demo: use fictional records only. Nothing is uploaded, imported, or saved. CSV text is processed in this browser session.
'+ ''+ '
'+ '
'+ '

How switching will work

① Export customer and pet data from existing software
② Match columns and check duplicates
③ Review errors and confirm before import
④ Keep a backup and verify sample records

Planned production import will require authentication, validation, secure storage, rollback, and support for linking multiple pets to one customer.

'} function bindImport(){ document.getElementById('pk-example').onclick=()=>{document.getElementById('pk-csv').value=csvExample;document.getElementById('pk-import-preview').innerHTML=''}; document.getElementById('pk-preview').onclick=()=>{ const parsed=parseCSV(document.getElementById('pk-csv').value);const out=document.getElementById('pk-import-preview'); if(parsed.length<2){out.innerHTML='
Add a header and at least one data row.
';return} importHeaders=parsed[0].map(x=>x.trim());importRows=parsed.slice(1);const keys=['Customer Name','Phone','Pet Name','Breed']; const select=(key)=>''; out.innerHTML='

2 · Match your columns

'+keys.map(select).join('')+'
'; document.getElementById('pk-check').onclick=()=>{ const m={};out.querySelectorAll('[data-map]').forEach(sel=>m[sel.dataset.map]=sel.value===''?-1:Number(sel.value)); if(m['Customer Name']<0||m['Pet Name']<0){document.getElementById('pk-checked').innerHTML='
Customer Name and Pet Name must be mapped.
';return} const seen=new Set();let dup=0,missing=0;const data=importRows.map((r,i)=>{const get=k=>m[k]<0?'':(r[m[k]]||'').trim();const name=get('Customer Name'),petname=get('Pet Name'),phone=get('Phone'),breed=get('Breed');if(!name||!petname)missing++;const sig=(name+'|'+petname).toLowerCase();if(seen.has(sig))dup++;seen.add(sig);return {name,petname,phone,breed}}); document.getElementById('pk-checked').innerHTML='
'+data.length+' records reviewed · '+dup+' possible duplicates · '+missing+' missing required fields
No records have been imported.
'+data.slice(0,12).map(r=>'').join('')+'
CustomerPhonePetBreed
'+esc(r.name)+''+esc(r.phone)+''+esc(r.petname)+''+esc(r.breed)+'

Previewing first '+Math.min(12,data.length)+' rows. Production import is not enabled in this demo.

'; }; }; } const originalRender=render; render=function(){ if(page==='checkout'||page==='import'){ document.querySelectorAll('[data-page]').forEach(b=>b.classList.toggle('active',b.dataset.page===page)); document.getElementById('content').innerHTML=page==='checkout'?posView():importView(); if(page==='checkout')bindPos();else bindImport(); return; } originalRender(); }; nav.querySelectorAll('[data-page="checkout"],[data-page="import"]').forEach(b=>b.onclick=()=>{page=b.dataset.page;filter='';render()}); })();