{
  "version": 3,
  "sources": ["/cdn/shop/t/4/assets/ims-core.js?bust=20260708"],
  "sourcesContent": ["(function () {\r\n  'use strict';\r\n\r\n  // \u2500\u2500\u2500 CONFIG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const IMS = {\r\n    version: '2.1.0',\r\n    appName: 'StockMaestro',\r\n    developer: 'ABSA Digital',\r\n    delayedDays: 5,\r\n    claimAlertDays: 14,\r\n    claimEscalateDays: 30,\r\n  };\r\n\r\n  // \u2500\u2500\u2500 SUPABASE CLIENT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  let _sb = null;\r\n\r\n  function sb() {\r\n    if (_sb) return _sb;\r\n    const url = window.IMS_SUPABASE_URL;\r\n    const key = window.IMS_SUPABASE_ANON_KEY;\r\n    if (!url || !key) {\r\n      console.error('[StockMaestro] Supabase credentials missing');\r\n      return null;\r\n    }\r\n    try {\r\n      _sb = window.supabase.createClient(url, key, {\r\n        auth: { persistSession: true, autoRefreshToken: true }\r\n      });\r\n    } catch (e) {\r\n      console.error('[StockMaestro] Failed to create Supabase client:', e);\r\n      return null;\r\n    }\r\n    return _sb;\r\n  }\r\n\r\n  // \u2500\u2500\u2500 AUTH \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const AUTH = {\r\n    user: null,\r\n    company: null,\r\n    session: null,\r\n\r\n    async signIn(email, password) {\r\n      const client = sb();\r\n      if (!client) return { error: 'Connection error \u2014 check Supabase credentials' };\r\n      const { data, error } = await client.auth.signInWithPassword({ email, password });\r\n      if (error) return { error: error.message };\r\n      this.session = data.session;\r\n      const ok = await this.loadProfile(data.user.id);\r\n      if (!ok) return { error: 'Account not found. Please contact your administrator.' };\r\n      return { success: true };\r\n    },\r\n\r\n    async signOut() {\r\n      const client = sb();\r\n      // Clear the signed-in user BEFORE telling Supabase, so the session\r\n      // keeper (below) sees this as a normal sign out and does not show\r\n      // \"Your session has ended\".\r\n      this.user = null;\r\n      this.company = null;\r\n      this.session = null;\r\n      if (client) {\r\n        // 'local' signs out THIS browser only. The library's default\r\n        // ('global') ended every session for the user on every device, so\r\n        // signing out on one computer also signed out the phone and others.\r\n        const { error } = await client.auth.signOut({ scope: 'local' });\r\n        if (error) console.warn('[StockMaestro] Sign out: Supabase did not confirm (' + (error.message || error) + '). Clearing this browser anyway.');\r\n        // If Supabase refuses the sign out (for example \"Session not found\"\r\n        // because the session was already ended elsewhere), the library\r\n        // keeps the login stored in the browser, and the next visit signed\r\n        // straight back in. So always remove it here.\r\n        try {\r\n          const key = client.auth.storageKey;\r\n          if (key) {\r\n            localStorage.removeItem(key);\r\n            localStorage.removeItem(key + '-code-verifier');\r\n            localStorage.removeItem(key + '-user');\r\n          }\r\n        } catch (e) { /* storage not available */ }\r\n      }\r\n      UI.showLogin();\r\n    },\r\n\r\n    async loadProfile(authId) {\r\n      const client = sb();\r\n      if (!client) return false;\r\n      let { data, error } = await client\r\n        .from('users')\r\n        .select('*, companies(*)')\r\n        .eq('auth_id', authId)\r\n        .eq('is_active', true)\r\n        .maybeSingle();\r\n      if (error) {\r\n        console.error('[StockMaestro] Profile load error:', error.message);\r\n        return false;\r\n      }\r\n      if (!data) {\r\n        // The previous client-side email-fallback lookup here always\r\n        // failed silently under RLS: users_select only allows\r\n        // (auth_id = auth.uid()) OR (company_id = get_user_company_id()),\r\n        // and get_user_company_id() itself resolves via a users lookup\r\n        // keyed on auth_id \u2014 so for a brand-new unlinked row (auth_id IS\r\n        // NULL), BOTH halves of that OR return null/false from the\r\n        // client's own session. No client-side query could ever see this\r\n        // row, regardless of how it was written. link_my_account() is a\r\n        // SECURITY DEFINER function that performs this one linking step\r\n        // server-side, safely bypassing that chicken-and-egg RLS gap for\r\n        // exactly this narrow bootstrap purpose only.\r\n        const { data: linkedUserId, error: rpcErr } = await client.rpc('link_my_account');\r\n        if (!rpcErr && linkedUserId) {\r\n          const { data: relinked } = await client\r\n            .from('users')\r\n            .select('*, companies(*)')\r\n            .eq('auth_id', authId)\r\n            .eq('is_active', true)\r\n            .maybeSingle();\r\n          if (relinked) data = relinked;\r\n        }\r\n      }\r\n      if (!data) {\r\n        console.warn('[StockMaestro] No active user found for auth_id:', authId);\r\n        return false;\r\n      }\r\n      this.user = data;\r\n      this.company = data.companies;\r\n      client.from('users').update({ last_login: new Date().toISOString() }).eq('id', data.id).then(({error})=>{ if(error) console.warn('[StockMaestro] last_login update failed:',error.message); });\r\n      return true;\r\n    },\r\n\r\n    async checkSession() {\r\n      const client = sb();\r\n      if (!client) return false;\r\n      const { data: { session }, error } = await client.auth.getSession();\r\n      if (error || !session) return false;\r\n      this.session = session;\r\n      return await this.loadProfile(session.user.id);\r\n    },\r\n\r\n    hasRole(...roles) {\r\n      return roles.includes(this.user?.role);\r\n    },\r\n\r\n    isAdmin() {\r\n      return ['super_admin', 'admin'].includes(this.user?.role);\r\n    },\r\n\r\n    isSuperAdmin() {\r\n      return this.user?.role === 'super_admin';\r\n    },\r\n\r\n    canSeeCost() {\r\n      return ['super_admin', 'admin', 'imports_manager'].includes(this.user?.role);\r\n    },\r\n\r\n    // Adding new customers: Super Admin and Admin always; anyone else only\r\n    // if a Super Admin has ticked \"Can add new customers\" for them (stored\r\n    // in users.extra_permissions). The database enforces the same rule.\r\n    canAddCustomers() {\r\n      if (this.isAdmin()) return true;\r\n      return (this.user?.extra_permissions || []).includes('add_customers');\r\n    },\r\n\r\n    canAccess(module) {\r\n      const role = this.user?.role;\r\n      if (!role) return false;\r\n      if (['super_admin', 'admin'].includes(role)) return true;\r\n      if (role === 'custom') {\r\n        // Per-user granular access \u2014 any module can be individually\r\n        // granted here, including admin-area modules (Company/Backup/\r\n        // Users), per explicit design decision. Destructive/admin-only\r\n        // actions elsewhere in the app (gated by isAdmin(), not this\r\n        // function) remain blocked for this role regardless of what's\r\n        // checked here, since isAdmin() only ever matches\r\n        // 'super_admin'/'admin' \u2014 unaffected by this change.\r\n        return (this.user?.custom_permissions || []).includes(module);\r\n      }\r\n      const map = {\r\n        imports_manager:   ['imports'],\r\n        warehouse_manager: ['inventory', 'warehouse', 'logistics', 'purchases'],\r\n        warehouse_staff:   ['inventory', 'warehouse'],\r\n        order_management:  ['orders', 'customers', 'logistics'],\r\n        logistics_staff:   ['logistics', 'warehouse'],\r\n        accounts_staff:    ['accounts', 'reports'],\r\n        viewer:            ['dashboard', 'reports'],\r\n      };\r\n      return (map[role] || []).includes(module);\r\n    }\r\n  };\r\n\r\n  // \u2500\u2500\u2500 LICENCE CHECK \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // Called after profile loaded. Returns: 'active' | 'grace' | 'suspended' | 'expired'\r\n  // Shows lockout screen for suspended/expired, warning banner for grace period.\r\n  async function checkLicence() {\r\n    const client = sb();\r\n    if (!client || !AUTH.company?.id) return 'active'; // fail open if no connection\r\n\r\n    const { data: lic, error } = await client\r\n      .from('licences')\r\n      .select('*')\r\n      .eq('company_id', AUTH.company.id)\r\n      .maybeSingle();\r\n\r\n    // No licence record = allow during development, warn in console\r\n    if (error || !lic) {\r\n      console.warn('[StockMaestro] No licence found for company \u2014 allowing access');\r\n      return 'active';\r\n    }\r\n\r\n    const today = new Date();\r\n    const expiresAt = new Date(lic.expires_at);\r\n    const graceDays = lic.grace_days || 7;\r\n    const graceEnd = new Date(expiresAt);\r\n    graceEnd.setDate(graceEnd.getDate() + graceDays);\r\n\r\n    // Suspended \u2014 hard lock regardless of dates\r\n    if (lic.status === 'suspended') {\r\n      showLockoutScreen('suspended', lic);\r\n      return 'suspended';\r\n    }\r\n\r\n    // Explicitly expired in DB\r\n    if (lic.status === 'expired') {\r\n      showLockoutScreen('expired', lic);\r\n      return 'expired';\r\n    }\r\n\r\n    // Past grace period \u2014 hard lock\r\n    if (today > graceEnd) {\r\n      showLockoutScreen('expired', lic);\r\n      return 'expired';\r\n    }\r\n\r\n    // Within grace period \u2014 allow but show warning banner\r\n    if (today > expiresAt) {\r\n      const daysLeft = Math.ceil((graceEnd - today) / 86400000);\r\n      showGraceBanner(daysLeft, lic);\r\n      return 'grace';\r\n    }\r\n\r\n    // Check expiry warning (14 days before)\r\n    const warningDate = new Date(expiresAt);\r\n    warningDate.setDate(warningDate.getDate() - 14);\r\n    if (today >= warningDate) {\r\n      const daysLeft = Math.ceil((expiresAt - today) / 86400000);\r\n      showExpiryWarning(daysLeft);\r\n    }\r\n\r\n    return 'active';\r\n  }\r\n\r\n  function showLockoutScreen(reason, lic) {\r\n    // Hide app and login, show lockout\r\n    document.getElementById('ims-login-screen').style.display = 'none';\r\n    document.getElementById('ims-app').style.display = 'none';\r\n\r\n    let existing = document.getElementById('ims-lockout-screen');\r\n    if (!existing) {\r\n      existing = document.createElement('div');\r\n      existing.id = 'ims-lockout-screen';\r\n      document.body.appendChild(existing);\r\n    }\r\n\r\n    const isSuspended = reason === 'suspended';\r\n    const title = isSuspended ? 'Account Suspended' : 'Subscription Expired';\r\n    const icon  = isSuspended ? 'ti-lock' : 'ti-clock-off';\r\n    const msg   = isSuspended\r\n      ? (lic.suspension_reason || 'Your StockMaestro account has been suspended.')\r\n      : 'Your StockMaestro subscription has expired. Please renew to continue.';\r\n\r\n    existing.innerHTML = `\r\n      <style>\r\n        #ims-lockout-screen{\r\n          position:fixed;inset:0;background:#1B3A6B;\r\n          display:flex;align-items:center;justify-content:center;\r\n          z-index:999999;font-family:'Inter',system-ui,sans-serif;padding:20px\r\n        }\r\n        .ims-lock-card{\r\n          background:#fff;border-radius:16px;padding:48px 40px;\r\n          max-width:460px;width:100%;text-align:center;\r\n          box-shadow:0 20px 60px rgba(0,0,0,.4)\r\n        }\r\n        .ims-lock-icon{font-size:56px;color:#DC2626;margin-bottom:16px}\r\n        .ims-lock-title{font-size:22px;font-weight:700;color:#111;margin-bottom:8px}\r\n        .ims-lock-msg{font-size:14px;color:#6B7280;line-height:1.6;margin-bottom:24px}\r\n        .ims-lock-contact{\r\n          background:#FEF2F2;border:1px solid #FECACA;\r\n          border-radius:8px;padding:14px;font-size:13px;color:#DC2626;margin-bottom:20px\r\n        }\r\n        .ims-lock-brand{font-size:11px;color:#9CA3AF;margin-top:20px}\r\n        .ims-lock-brand strong{color:#1B3A6B}\r\n      </style>\r\n      <div class=\"ims-lock-card\">\r\n        <div class=\"ims-lock-icon\"><i class=\"ti ${icon}\"></i></div>\r\n        <div class=\"ims-lock-title\">${title}</div>\r\n        <div class=\"ims-lock-msg\">${msg}</div>\r\n        <div class=\"ims-lock-contact\">\r\n          <strong>To restore access, contact:</strong><br>\r\n          ABSA Digital &nbsp;|&nbsp; hafimohsin.me@gmail.com<br>\r\n          +44 774 290 712\r\n        </div>\r\n        <div class=\"ims-lock-brand\">\r\n          <strong>StockMaestro</strong> &copy; 2026 ABSA Digital\r\n        </div>\r\n      </div>`;\r\n  }\r\n\r\n  function showGraceBanner(daysLeft, lic) {\r\n    // Show app normally but inject a prominent warning banner\r\n    const banner = document.createElement('div');\r\n    banner.id = 'ims-grace-banner';\r\n    banner.style.cssText = [\r\n      'position:fixed','top:0','left:0','right:0','z-index:999999',\r\n      'background:#DC2626','color:#fff','font-size:13px','font-weight:500',\r\n      'padding:10px 20px','display:flex','align-items:center','gap:10px',\r\n      'justify-content:center','font-family:Inter,system-ui,sans-serif'\r\n    ].join(';');\r\n    banner.innerHTML = `\r\n      <i class=\"ti ti-alert-triangle\" style=\"font-size:16px\"></i>\r\n      <span>Your StockMaestro subscription expired. You have <strong>${daysLeft} day${daysLeft!==1?'s':''}</strong> of grace access remaining. Contact ABSA Digital to renew.</span>\r\n      <a href=\"mailto:hafimohsin.me@gmail.com\" style=\"color:#fff;font-weight:700;margin-left:8px;text-decoration:underline\">Renew now</a>\r\n      <button onclick=\"this.parentElement.remove()\" style=\"margin-left:auto;background:none;border:none;color:#fff;font-size:18px;cursor:pointer;line-height:1\">&times;</button>`;\r\n    document.body.prepend(banner);\r\n\r\n    // Push app down so banner doesn't overlap\r\n    const app = document.getElementById('ims-app');\r\n    if (app) app.style.paddingTop = '48px';\r\n    const shopifyBar = document.querySelector('.shopify-section-header-sticky, #shopify-section-header, .site-header, .header-section');\r\n    if(shopifyBar) banner.style.top = shopifyBar.offsetHeight + 'px';\r\n  }\r\n\r\n  function showExpiryWarning(daysLeft) {\r\n    // Subtle warning in the app \u2014 dismissible\r\n    setTimeout(() => {\r\n      UI.toast(\r\n        `Your StockMaestro licence expires in ${daysLeft} day${daysLeft!==1?'s':''}. Contact ABSA Digital to renew.`,\r\n        'warning',\r\n        8000\r\n      );\r\n    }, 2000);\r\n  }\r\n\r\n  // \u2500\u2500\u2500 DATABASE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const DB = {\r\n    async select(table, opts = {}) {\r\n      const client = sb();\r\n      if (!client) return { data: null, error: 'No connection' };\r\n      let q = client.from(table).select(opts.select || '*');\r\n      if (!opts.noCompanyFilter && AUTH.company?.id) {\r\n        q = q.eq('company_id', AUTH.company.id);\r\n      }\r\n      if (opts.eq) {\r\n        Object.entries(opts.eq).forEach(([k, v]) => { q = q.eq(k, v); });\r\n      }\r\n      if (opts.neq) {\r\n        Object.entries(opts.neq).forEach(([k, v]) => { q = q.neq(k, v); });\r\n      }\r\n      if (opts.in) {\r\n        Object.entries(opts.in).forEach(([k, v]) => { q = q.in(k, v); });\r\n      }\r\n      if (opts.ilike) {\r\n        Object.entries(opts.ilike).forEach(([k, v]) => { q = q.ilike(k, `%${v}%`); });\r\n      }\r\n      if (opts.gte) {\r\n        Object.entries(opts.gte).forEach(([k, v]) => { q = q.gte(k, v); });\r\n      }\r\n      if (opts.lte) {\r\n        Object.entries(opts.lte).forEach(([k, v]) => { q = q.lte(k, v); });\r\n      }\r\n      if (opts.is) {\r\n        Object.entries(opts.is).forEach(([k, v]) => { q = q.is(k, v); });\r\n      }\r\n      if (opts.order) {\r\n        const orders = Array.isArray(opts.order) ? opts.order : [opts.order];\r\n        orders.forEach(o => { q = q.order(o.col, { ascending: o.asc !== false }); });\r\n      }\r\n      if (opts.limit) q = q.limit(opts.limit);\r\n      if (opts.single) q = q.single();\r\n      if (opts.maybeSingle) q = q.maybeSingle();\r\n      return q;\r\n    },\r\n\r\n    async insert(table, data) {\r\n      const client = sb();\r\n      if (!client) return { data: null, error: 'No connection' };\r\n      const payload = AUTH.company?.id ? { ...data, company_id: AUTH.company.id } : data;\r\n      return client.from(table).insert(payload).select().single();\r\n    },\r\n\r\n    async insertMany(table, rows) {\r\n      const client = sb();\r\n      if (!client) return { data: null, error: 'No connection' };\r\n      const payload = AUTH.company?.id\r\n        ? rows.map(r => ({ ...r, company_id: AUTH.company.id }))\r\n        : rows;\r\n      return client.from(table).insert(payload).select();\r\n    },\r\n\r\n    async update(table, id, data) {\r\n      const client = sb();\r\n      if (!client) return { data: null, error: 'No connection' };\r\n      return client.from(table).update(data).eq('id', id).select().single();\r\n    },\r\n\r\n    async updateWhere(table, filters, data) {\r\n      const client = sb();\r\n      if (!client) return { data: null, error: 'No connection' };\r\n      let q = client.from(table).update(data);\r\n      Object.entries(filters).forEach(([k, v]) => { q = q.eq(k, v); });\r\n      return q.select();\r\n    },\r\n\r\n    async remove(table, id) {\r\n      const client = sb();\r\n      if (!client) return { error: 'No connection' };\r\n      return client.from(table).delete().eq('id', id);\r\n    },\r\n\r\n    async upsert(table, data, onConflict) {\r\n      const client = sb();\r\n      if (!client) return { data: null, error: 'No connection' };\r\n      return client.from(table).upsert(data, { onConflict }).select();\r\n    },\r\n\r\n    async getProductPrice(productId, priceType, platformId = null) {\r\n      const client = sb();\r\n      if (!client) return null;\r\n      let q = client.from('product_prices')\r\n        .select('price')\r\n        .eq('product_id', productId)\r\n        .eq('price_type', priceType);\r\n      if (platformId) {\r\n        q = q.eq('platform_id', platformId);\r\n      } else {\r\n        q = q.is('platform_id', null);\r\n      }\r\n      const { data } = await q.maybeSingle();\r\n      return data?.price ?? null;\r\n    },\r\n\r\n    async getProductPrices(productId) {\r\n      const client = sb();\r\n      if (!client) return [];\r\n      const { data } = await client\r\n        .from('product_prices')\r\n        .select('*, platforms(name)')\r\n        .eq('product_id', productId);\r\n      return data || [];\r\n    },\r\n\r\n    async getAvailableStock(productId, warehouseId = null) {\r\n      let q = { select: 'quantity,reserved_qty', eq: { product_id: productId }, noCompanyFilter: true };\r\n      if (warehouseId) q.eq.warehouse_id = warehouseId;\r\n      const { data } = await this.select('stock_levels', q);\r\n      if (!data) return 0;\r\n      const levels = Array.isArray(data) ? data : [data];\r\n      return levels.reduce((sum, l) => sum + ((l.quantity || 0) - (l.reserved_qty || 0)), 0);\r\n    },\r\n\r\n    async getPlatformTerms(platformId) {\r\n      const client = sb();\r\n      if (!client) return null;\r\n      const { data } = await client\r\n        .from('platform_terms')\r\n        .select('*')\r\n        .eq('platform_id', platformId)\r\n        .maybeSingle();\r\n      return data;\r\n    }\r\n  };\r\n\r\n  // \u2500\u2500\u2500 UI CONTROLLER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const UI = {\r\n    activePanel: 'dashboard',\r\n\r\n    init() {\r\n      this.bindNav();\r\n      this.bindMobileToggle();\r\n      this.bindSidebarCollapse();\r\n      this.bindGlobalSearch();\r\n      this.bindModal();\r\n      this.bindThemeToggle();\r\n      // showPanel() itself changes the address to #<screen>, which fires\r\n      // this listener. Opening the screen again here meant every screen\r\n      // loaded twice on every visit. So only act when the address points\r\n      // at a screen that is NOT already open, which is what the browser's\r\n      // Back and Forward buttons do.\r\n      window.addEventListener('hashchange', () => {\r\n        const hash = location.hash.replace('#', '');\r\n        if (hash && hash !== this.activePanel) this.showPanel(hash);\r\n      });\r\n    },\r\n\r\n    showLogin() {\r\n      const drv = document.getElementById('ims-driver');\r\n      if (drv) drv.style.display = 'none';   // leaving Driver mode\r\n      document.getElementById('ims-login-screen').style.display = 'flex';\r\n      document.getElementById('ims-setpw-screen').style.display = 'none';\r\n      document.getElementById('ims-app').style.display = 'none';\r\n    },\r\n\r\n    showSetPassword() {\r\n      document.getElementById('ims-login-screen').style.display = 'none';\r\n      document.getElementById('ims-app').style.display = 'none';\r\n      document.getElementById('ims-setpw-screen').style.display = 'flex';\r\n    },\r\n\r\n    showApp() {\r\n      document.getElementById('ims-login-screen').style.display = 'none';\r\n      document.getElementById('ims-setpw-screen').style.display = 'none';\r\n      document.getElementById('ims-app').style.display = 'flex';\r\n      this.applyTheme(AUTH.user?.theme_pref || 'light');\r\n      this.updateUserDisplay();\r\n      this.filterNavByRole();\r\n    },\r\n\r\n    // \u2500\u2500\u2500 SCREEN ACCESS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    // A screen may be opened only if its sidebar item would be shown to\r\n    // this user: the same rule the sidebar uses (the item's data-module,\r\n    // checked with AUTH.canAccess). Before, the sidebar only hid the link;\r\n    // the top-bar \"+ New order\" button, typed web addresses and links from\r\n    // other screens still opened any screen for any role.\r\n    canOpenPanel(panelId) {\r\n      if (!AUTH.user) return true;\r\n      const item = document.querySelector(`.ims__nav-item[data-panel=\"${panelId}\"]`);\r\n      if (!item) return true;   // not a sidebar screen, nothing to check\r\n      return AUTH.canAccess(item.dataset.module || panelId);\r\n    },\r\n\r\n    // The first screen in sidebar order this user may open.\r\n    firstAllowedPanel() {\r\n      const items = [...document.querySelectorAll('.ims__nav-item[data-panel]')];\r\n      const first = items.find(i => AUTH.canAccess(i.dataset.module || i.dataset.panel));\r\n      return first ? first.dataset.panel : null;\r\n    },\r\n\r\n    showPanel(panelId) {\r\n      if (!this.canOpenPanel(panelId)) {\r\n        const navItem = document.querySelector(`.ims__nav-item[data-panel=\"${panelId}\"]`);\r\n        const label = navItem?.querySelector('span:not(.ims__nav-badge)')?.textContent?.trim() || panelId;\r\n        this.toast(`You don't have access to ${label}. Ask a Super Admin if you need it.`, 'warning', 6000);\r\n        // Stay on the current screen if there is one; otherwise open the\r\n        // first screen this user is allowed.\r\n        if (this.activePanel && this.canOpenPanel(this.activePanel)) {\r\n          if (location.hash.replace('#', '') !== this.activePanel) location.hash = this.activePanel;\r\n          return;\r\n        }\r\n        const fallback = this.firstAllowedPanel();\r\n        if (!fallback) return;\r\n        panelId = fallback;\r\n      }\r\n      document.querySelectorAll('.ims__panel').forEach(p => p.classList.remove('ims__panel--active'));\r\n      const target = document.getElementById('ims-panel-' + panelId);\r\n      if (!target) return;\r\n      target.classList.add('ims__panel--active');\r\n      this.activePanel = panelId;\r\n      location.hash = panelId;\r\n      this.updateNavActive(panelId);\r\n      this.closeSidebar();\r\n      const titles = {\r\n        dashboard: 'Dashboard', orders: 'Order management',\r\n        inventory: 'Inventory & stock', imports: 'Imports',\r\n        customers: 'Customer management', purchases: 'Purchase orders',\r\n        warehouse: 'Warehouse', logistics: 'Logistics',\r\n        accounts: 'Accounts', reports: 'Reports',\r\n        company: 'Company setup', users: 'Users',\r\n        backup: 'Backup & restore', platforms: 'Platforms',\r\n        stores: 'Store connections', warranty: 'Warranty'\r\n      };\r\n      const titleEl = document.getElementById('ims-page-title');\r\n      if (titleEl) titleEl.textContent = titles[panelId] || panelId;\r\n      window.dispatchEvent(new CustomEvent('ims:panel', { detail: { panel: panelId } }));\r\n    },\r\n\r\n    updateNavActive(panelId) {\r\n      document.querySelectorAll('.ims__nav-item').forEach(item => {\r\n        item.classList.toggle('ims__nav-item--active', item.dataset.panel === panelId);\r\n      });\r\n    },\r\n\r\n    bindNav() {\r\n      document.querySelectorAll('.ims__nav-item[data-panel]').forEach(item => {\r\n        item.addEventListener('click', () => this.showPanel(item.dataset.panel));\r\n        item.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') this.showPanel(item.dataset.panel); });\r\n      });\r\n    },\r\n\r\n    bindMobileToggle() {\r\n      const btn = document.getElementById('ims-hamburger');\r\n      const overlay = document.getElementById('ims-overlay');\r\n      if (btn) btn.addEventListener('click', () => this.toggleSidebar());\r\n      if (overlay) overlay.addEventListener('click', () => this.closeSidebar());\r\n    },\r\n\r\n    bindSidebarCollapse() {\r\n      const btn = document.getElementById('ims-sidebar-collapse-btn');\r\n      const sidebar = document.getElementById('ims-sidebar');\r\n      const icon = document.getElementById('ims-collapse-icon');\r\n      if (!btn || !sidebar) return;\r\n      // Restore the last-used collapsed state so it persists across\r\n      // page reloads, matching how a premium desktop app remembers\r\n      // its layout rather than resetting every visit.\r\n      if (localStorage.getItem('ims-sidebar-collapsed') === '1') {\r\n        sidebar.classList.add('ims__sidebar--collapsed');\r\n        if (icon) icon.className = 'ti ti-chevron-right';\r\n      }\r\n      btn.addEventListener('click', () => {\r\n        const collapsed = sidebar.classList.toggle('ims__sidebar--collapsed');\r\n        localStorage.setItem('ims-sidebar-collapsed', collapsed ? '1' : '0');\r\n        if (icon) icon.className = collapsed ? 'ti ti-chevron-right' : 'ti ti-chevron-left';\r\n      });\r\n    },\r\n\r\n    // \u2500\u2500\u2500 GLOBAL HEADER SEARCH \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    // Searches Orders (by order number or customer name), Manifests (van\r\n    // runs, by manifest number), Customers, Dropship customers, and\r\n    // Products/SKUs in one unified dropdown,\r\n    // grouped by category. Each result navigates to the correct panel\r\n    // (and, where relevant, tab) before opening the specific record \u2014\r\n    // matching the same showPanel()+setTimeout() cross-module pattern\r\n    // already used throughout the app (e.g. dashboard stat-card clicks).\r\n    bindGlobalSearch() {\r\n      const input = document.getElementById('ims-global-search');\r\n      const drop = document.getElementById('ims-global-search-drop');\r\n      if (!input || !drop) return;\r\n      // Set here so ims-system.liquid needs no change when the list grows.\r\n      input.placeholder = 'Search orders, manifests, customers, SKUs...';\r\n\r\n      // Counts searches so that a slow, older search can never overwrite\r\n      // the results of a newer one the user has typed since.\r\n      let searchSeq = 0;\r\n\r\n      const search = UTIL.debounce(async (val) => {\r\n        const q = val.trim();\r\n        if (q.length < 2) { drop.style.display = 'none'; return; }\r\n        const mySeq = ++searchSeq;\r\n\r\n        // A manifest number can be typed as \"MF-00082\", \"mf82\", \"MF 82\" or\r\n        // \"82\": the digits are searched for. Manifests are only looked up\r\n        // for users who may open Logistics.\r\n        const mfDigits = q.match(/^(?:mf)?[\\s-]*0*(\\d+)$/i);\r\n        const mfTerm = mfDigits ? mfDigits[1] : q;\r\n        const canSeeLogistics = this.canOpenPanel('logistics');\r\n\r\n        // Each query has a NAME, and results are matched back by name. The\r\n        // previous version matched them by position: when the tracking\r\n        // number query was added in the middle, every later result slid\r\n        // into the wrong group (shipments shown as customers, customers as\r\n        // dropship customers, dropship customers as products, and products\r\n        // dropped completely).\r\n        const queries = {\r\n          ordersByNum:    DB.select('orders', { ilike: { order_number: q }, select: 'id,order_number,customer_name,total_value', limit: 5 }),\r\n          ordersByCust:   DB.select('orders', { ilike: { customer_name: q }, select: 'id,order_number,customer_name,total_value', limit: 5 }),\r\n          // The number the customer knows: a website order number (#1034) or a\r\n          // dropship partner's own reference. \"#1034\" and \"1034\" both work.\r\n          ordersByRef:    DB.select('orders', { ilike: { external_ref: q.replace(/^#/, '') }, select: 'id,order_number,customer_name,total_value,external_ref', limit: 5 }),\r\n          byTracking:     DB.select('shipments', { ilike: { tracking_number: q }, select: 'tracking_number,orders(id,order_number,customer_name,total_value)', limit: 5 }),\r\n          manifests:      canSeeLogistics\r\n            ? DB.select('manifests', { ilike: { manifest_number: mfTerm }, select: 'id,manifest_number,dispatched_at,departed_at,drivers(name)', order: { col: 'created_at', asc: false }, limit: 5 })\r\n            : Promise.resolve({ data: [] }),\r\n          // The exact manifest (\"82\" -> MF-00082), always listed first: \"82\"\r\n          // also matches MF-00182 or MF-00820, which could push it out.\r\n          manifestExact:  canSeeLogistics && mfDigits\r\n            ? DB.select('manifests', { eq: { manifest_number: `MF-${mfDigits[1].padStart(5, '0')}` }, select: 'id,manifest_number,dispatched_at,departed_at,drivers(name)', limit: 1 })\r\n            : Promise.resolve({ data: [] }),\r\n          customers:      DB.select('customers', { ilike: { business_name: q }, select: 'id,business_name,is_active', limit: 5 }),\r\n          dsCustomers:    DB.select('dropship_customers', { ilike: { business_name: q }, select: 'id,business_name', limit: 5 }),\r\n          productsByName: DB.select('products', { ilike: { name: q }, select: 'id,name,sku', limit: 5 }),\r\n          productsBySku:  DB.select('products', { ilike: { sku: q }, select: 'id,name,sku', limit: 5 })\r\n        };\r\n        const names = Object.keys(queries);\r\n        const settled = await Promise.all(Object.values(queries));\r\n        const r = {};\r\n        names.forEach((name, i) => { r[name] = settled[i]?.data || []; });\r\n\r\n        if (mySeq !== searchSeq) return;\r\n\r\n        const uniqueById = (list) => list.filter((x, i, arr) => x && arr.findIndex(y => y && y.id === x.id) === i);\r\n\r\n        // A tracking number match points at its order, so it is shown in\r\n        // the Orders group with the tracking number underneath.\r\n        const trackingOrders = r.byTracking\r\n          .filter(s => s.orders)\r\n          .map(s => ({ ...s.orders, _tracking: s.tracking_number }));\r\n        const orders = uniqueById([...r.ordersByNum, ...r.ordersByRef, ...trackingOrders, ...r.ordersByCust]).slice(0, 5);\r\n        const mfResults = uniqueById([...r.manifestExact, ...r.manifests]).slice(0, 5);\r\n        const custResults = r.customers;\r\n        const dsResults = r.dsCustomers;\r\n        const productResults = uniqueById([...r.productsByName, ...r.productsBySku]).slice(0, 5);\r\n\r\n        if (!orders.length && !mfResults.length && !custResults.length && !dsResults.length && !productResults.length) {\r\n          drop.innerHTML = '<div class=\"ims__ac-item\" style=\"cursor:default;color:var(--ims-text-muted)\">No results found</div>';\r\n          drop.style.display = 'block';\r\n          return;\r\n        }\r\n\r\n        let html = '';\r\n        if (orders.length) {\r\n          html += '<div class=\"ims__gs-group\">Orders</div>';\r\n          html += orders.map(o => `\r\n            <div class=\"ims__ac-item\" onmousedown=\"window.SM.UI.goToSearchResult('order','${o.id}')\">\r\n              <div>${UTIL.sanitize(o.order_number)}${o.external_ref && o.external_ref !== o.order_number ? ` <span class=\"ims__gs-sub\">(ref ${UTIL.sanitize(o.external_ref)})</span>` : ''}</div>\r\n              <div class=\"ims__gs-sub\">${o.customer_name ? UTIL.sanitize(o.customer_name) : '&mdash;'} &middot; ${UTIL.formatCurrency(o.total_value)}${o._tracking ? ` &middot; Tracking ${UTIL.sanitize(o._tracking)}` : ''}</div>\r\n            </div>`).join('');\r\n        }\r\n        if (mfResults.length) {\r\n          html += '<div class=\"ims__gs-group\">Manifests</div>';\r\n          html += mfResults.map(m => `\r\n            <div class=\"ims__ac-item\" onmousedown=\"window.SM.UI.goToSearchResult('manifest','${m.id}')\">\r\n              <div>${UTIL.sanitize(m.manifest_number)}</div>\r\n              <div class=\"ims__gs-sub\">${m.drivers?.name ? UTIL.sanitize(m.drivers.name) : 'No driver'}${m.dispatched_at ? ` &middot; ${UTIL.formatDate(m.dispatched_at)}` : ''}${m.departed_at ? ' &middot; Van departed' : ''}</div>\r\n            </div>`).join('');\r\n        }\r\n        if (custResults.length) {\r\n          html += '<div class=\"ims__gs-group\">Customers</div>';\r\n          html += custResults.map(c => `<div class=\"ims__ac-item\" onmousedown=\"window.SM.UI.goToSearchResult('customer','${c.id}')\">${UTIL.sanitize(c.business_name)}${c.is_active === false ? ' <span class=\"ims__gs-sub\">(inactive)</span>' : ''}</div>`).join('');\r\n        }\r\n        if (dsResults.length) {\r\n          html += '<div class=\"ims__gs-group\">Dropship customers</div>';\r\n          html += dsResults.map(c => `<div class=\"ims__ac-item\" onmousedown=\"window.SM.UI.goToSearchResult('dsCustomer','${c.id}')\">${UTIL.sanitize(c.business_name)}</div>`).join('');\r\n        }\r\n        if (productResults.length) {\r\n          html += '<div class=\"ims__gs-group\">Products</div>';\r\n          html += productResults.map(p => `\r\n            <div class=\"ims__ac-item\" onmousedown=\"window.SM.UI.goToSearchResult('product','${p.id}')\">\r\n              <div>${UTIL.sanitize(p.name)}</div>\r\n              <div class=\"ims__gs-sub\">${UTIL.sanitize(p.sku || '')}</div>\r\n            </div>`).join('');\r\n        }\r\n        drop.innerHTML = html;\r\n        drop.style.display = 'block';\r\n      }, 300);\r\n\r\n      input.addEventListener('input', e => search(e.target.value));\r\n      input.addEventListener('blur', () => setTimeout(() => { drop.style.display = 'none'; }, 200));\r\n    },\r\n\r\n    goToSearchResult(type, id) {\r\n      const drop = document.getElementById('ims-global-search-drop');\r\n      const input = document.getElementById('ims-global-search');\r\n      if (drop) drop.style.display = 'none';\r\n      if (input) input.value = '';\r\n\r\n      if (type === 'order') {\r\n        this.showPanel('orders');\r\n        setTimeout(() => window.IMS_ORDERS?.openOrder(id), 400);\r\n      } else if (type === 'manifest') {\r\n        // Logistics opens straight on the Manifests tab and opens the manifest.\r\n        if (window.IMS_LOGISTICS?.goToManifest) window.IMS_LOGISTICS.goToManifest(id);\r\n        else this.showPanel('logistics');\r\n      } else if (type === 'customer') {\r\n        this.showPanel('customers');\r\n        setTimeout(() => window.IMS_CUSTOMERS?.openCustomer(id), 400);\r\n      } else if (type === 'dsCustomer') {\r\n        this.showPanel('dropship');\r\n        setTimeout(() => {\r\n          const tabs = document.getElementById('ims-ds-tabs');\r\n          if (tabs) {\r\n            tabs.querySelectorAll('.ims__tab').forEach(t => t.classList.remove('ims__tab--active'));\r\n            tabs.querySelector('[data-tab=\"customers\"]')?.classList.add('ims__tab--active');\r\n          }\r\n          window.IMS_DROPSHIP?.loadCustomers?.().then(() => {\r\n            setTimeout(() => window.IMS_DROPSHIP?.openCustomer(id), 300);\r\n          });\r\n        }, 400);\r\n      } else if (type === 'product') {\r\n        this.showPanel('inventory');\r\n        setTimeout(() => {\r\n          const tabs = document.getElementById('ims-inv-tabs');\r\n          if (tabs) {\r\n            tabs.querySelectorAll('.ims__tab').forEach(t => t.classList.remove('ims__tab--active'));\r\n            tabs.querySelector('[data-tab=\"catalogue\"]')?.classList.add('ims__tab--active');\r\n          }\r\n          window.IMS_INVENTORY?.loadCatalogue?.().then(() => {\r\n            setTimeout(() => window.IMS_INVENTORY?.showSKUForm?.(id), 300);\r\n          });\r\n        }, 400);\r\n      }\r\n    },\r\n\r\n    toggleSidebar() {\r\n      document.getElementById('ims-sidebar')?.classList.toggle('ims__sidebar--open');\r\n      document.getElementById('ims-overlay')?.classList.toggle('ims__overlay--show');\r\n    },\r\n\r\n    closeSidebar() {\r\n      document.getElementById('ims-sidebar')?.classList.remove('ims__sidebar--open');\r\n      document.getElementById('ims-overlay')?.classList.remove('ims__overlay--show');\r\n    },\r\n\r\n    // Set by openModal(). A modal containing any field the user could have\r\n    // typed into is \"locked\" \u2014 it can only be dismissed via the X or a\r\n    // footer button. Losing a half-completed order to a stray click outside\r\n    // the dialog, or an accidental Escape, is not a fair trade for the\r\n    // convenience of quick dismissal. Plain confirm/info dialogs have no\r\n    // data at stake, so they stay dismissible as before.\r\n    _modalLocked: false,\r\n\r\n    bindModal() {\r\n      document.getElementById('ims-modal-close')?.addEventListener('click', () => this.closeModal());\r\n      document.getElementById('ims-modal-maximize')?.addEventListener('click', () => this.toggleModalMaximize());\r\n      document.getElementById('ims-modal-overlay')?.addEventListener('click', e => {\r\n        if (e.target.id === 'ims-modal-overlay' && !this._modalLocked) this.closeModal();\r\n      });\r\n      document.addEventListener('keydown', e => {\r\n        if (e.key === 'Escape' && !this._modalLocked) this.closeModal();\r\n      });\r\n    },\r\n\r\n    // Applies to every modal across every module \u2014 Orders, Dropship,\r\n    // Accounts, Logistics, Customers, etc. \u2014 since they all open through\r\n    // this single shared component rather than each building their own.\r\n    toggleModalMaximize() {\r\n      const el = document.getElementById('ims-modal');\r\n      if (!el) return;\r\n      const icon = document.querySelector('#ims-modal-maximize i');\r\n      const isMax = el.classList.toggle('ims__modal--maximized');\r\n      if (icon) icon.className = isMax ? 'ti ti-minimize' : 'ti ti-maximize';\r\n    },\r\n\r\n    openModal(title, bodyHtml, footerHtml = '', size = '') {\r\n      const el = document.getElementById('ims-modal');\r\n      const overlay = document.getElementById('ims-modal-overlay');\r\n      if (!el || !overlay) return;\r\n      document.getElementById('ims-modal-title').textContent = title;\r\n      document.getElementById('ims-modal-body').innerHTML = bodyHtml;\r\n      document.getElementById('ims-modal-footer').innerHTML = footerHtml;\r\n      el.className = 'ims__modal' + (size ? ' ims__modal--' + size : '');\r\n      // el.className above already fully resets any prior maximized state\r\n      // (it replaces the whole class string) \u2014 but the maximize button's\r\n      // icon lives in the always-present header, not the per-modal body,\r\n      // so it needs its own explicit reset back to \"maximize\" each time.\r\n      const maxIcon = document.querySelector('#ims-modal-maximize i');\r\n      if (maxIcon) maxIcon.className = 'ti ti-maximize';\r\n      overlay.classList.add('ims__modal-overlay--open');\r\n      this._modalLocked = !!document.getElementById('ims-modal-body')\r\n        ?.querySelector('input,select,textarea');\r\n      setTimeout(() => el.querySelector('input,select,textarea')?.focus(), 100);\r\n    },\r\n\r\n    closeModal() {\r\n      this._modalLocked = false;\r\n      document.getElementById('ims-modal-overlay')?.classList.remove('ims__modal-overlay--open');\r\n    },\r\n\r\n    confirm(message, onConfirm, confirmLabel = 'Confirm', danger = false) {\r\n      this.openModal('Confirm', `<p style=\"font-size:14px;color:var(--ims-text-secondary)\">${message}</p>`,\r\n        `<button class=\"ims__btn ims__btn--ghost\" onclick=\"window.SM.UI.closeModal()\">Cancel</button>\r\n         <button class=\"ims__btn ims__btn--${danger ? 'danger' : 'primary'}\" id=\"ims-confirm-btn\">${confirmLabel}</button>`,\r\n        'sm'\r\n      );\r\n      document.getElementById('ims-confirm-btn')?.addEventListener('click', () => {\r\n        this.closeModal();\r\n        onConfirm();\r\n      });\r\n    },\r\n\r\n    toast(message, type = 'info', duration = 3500) {\r\n      const container = document.getElementById('ims-toasts');\r\n      if (!container) return;\r\n      const el = document.createElement('div');\r\n      el.className = `ims__toast ims__toast--${type}`;\r\n      const icons = { success: 'check-circle', error: 'alert-circle', warning: 'alert-triangle', info: 'info-circle' };\r\n      el.innerHTML = `<i class=\"ti ti-${icons[type] || 'info-circle'}\" aria-hidden=\"true\"></i><span>${UTIL.sanitize(message)}</span>\r\n        <button class=\"ims__toast-close\" onclick=\"this.parentElement.remove()\" aria-label=\"Close\">&times;</button>`;\r\n      container.appendChild(el);\r\n      requestAnimationFrame(() => el.classList.add('ims__toast--show'));\r\n      setTimeout(() => { el.classList.remove('ims__toast--show'); setTimeout(() => el.remove(), 300); }, duration);\r\n    },\r\n\r\n    loading(containerId, msg = 'Loading...') {\r\n      const el = document.getElementById(containerId);\r\n      if (el) el.innerHTML = `<div class=\"ims__loading\"><i class=\"ti ti-loader-2 ims__spin\" aria-hidden=\"true\"></i><span>${UTIL.sanitize(msg)}</span></div>`;\r\n    },\r\n\r\n    empty(containerId, msg = 'No records found', icon = 'inbox') {\r\n      const el = document.getElementById(containerId);\r\n      if (el) el.innerHTML = `<div class=\"ims__empty\"><i class=\"ti ti-${icon}\" aria-hidden=\"true\"></i><p>${UTIL.sanitize(msg)}</p></div>`;\r\n    },\r\n\r\n    updateUserDisplay() {\r\n      const u = AUTH.user;\r\n      const c = AUTH.company;\r\n      if (!u) return;\r\n      const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };\r\n      set('ims-user-name', u.name || u.email);\r\n      set('ims-user-role', UTIL.formatRole(u.role));\r\n      set('ims-user-avatar', UTIL.initials(u.name || u.email));\r\n      set('ims-company-name', c?.name || IMS.appName);\r\n      set('ims-topbar-date', new Intl.DateTimeFormat('en-GB', {\r\n        weekday: 'short', day: '2-digit', month: 'short', year: 'numeric'\r\n      }).format(new Date()));\r\n    },\r\n\r\n    filterNavByRole() {\r\n      document.querySelectorAll('.ims__nav-item[data-module]').forEach(item => {\r\n        const mod = item.dataset.module;\r\n        item.style.display = AUTH.canAccess(mod) ? '' : 'none';\r\n      });\r\n      // Buttons in the page that open a screen (for example the top-bar\r\n      // \"+ New order\") are hidden too if this user cannot use that screen.\r\n      document.querySelectorAll('[onclick]').forEach(el => {\r\n        const m = /^window\\.SM\\.UI\\.showPanel\\('([a-z_-]+)'\\)$/.exec((el.getAttribute('onclick') || '').trim());\r\n        if (m) el.style.display = this.canOpenPanel(m[1]) ? '' : 'none';\r\n      });\r\n    },\r\n\r\n    updateBadge(id, count) {\r\n      const el = document.getElementById(id);\r\n      if (!el) return;\r\n      if (count > 0) { el.textContent = count > 99 ? '99+' : count; el.style.display = 'flex'; }\r\n      else { el.style.display = 'none'; }\r\n    },\r\n\r\n    applyTheme(theme) {\r\n      document.getElementById('ims-app')?.setAttribute('data-theme', theme);\r\n      const btn = document.getElementById('ims-theme-toggle');\r\n      if (btn) {\r\n        btn.querySelector('i').className = theme === 'dark' ? 'ti ti-sun' : 'ti ti-moon';\r\n        btn.setAttribute('aria-label', theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode');\r\n      }\r\n    },\r\n\r\n    bindThemeToggle() {\r\n      document.getElementById('ims-theme-toggle')?.addEventListener('click', async () => {\r\n        const current = document.getElementById('ims-app')?.getAttribute('data-theme') || 'light';\r\n        const next = current === 'light' ? 'dark' : 'light';\r\n        this.applyTheme(next);\r\n        if (AUTH.user?.id) {\r\n          await DB.update('users', AUTH.user.id, { theme_pref: next });\r\n          AUTH.user.theme_pref = next;\r\n        }\r\n      });\r\n    },\r\n\r\n    async loadNotifications() {\r\n      const { data: notes } = await DB.select('notifications', {\r\n        eq: { is_read: false },\r\n        order: { col: 'created_at', asc: false },\r\n        limit: 20\r\n      });\r\n      const count = notes?.length || 0;\r\n      this.updateBadge('ims-notif-badge', count);\r\n      const dot = document.getElementById('ims-notif-dot');\r\n      if (dot) dot.style.display = count > 0 ? 'block' : 'none';\r\n      this._lastNotifications = notes || [];\r\n      return notes || [];\r\n    },\r\n\r\n    renderNotifPanel() {\r\n      const panel = document.getElementById('ims-notif-panel');\r\n      if (!panel) return;\r\n      const notes = this._lastNotifications || [];\r\n      const rows = notes.length\r\n        ? notes.map(n => {\r\n            const when = n.created_at ? UTIL.formatDate(n.created_at) : '';\r\n            const rid  = n.reference_id ? String(n.reference_id).replace(/['\"\\\\]/g,'') : '';\r\n            return `<div class=\"ims__notif-item\" onclick=\"window.SM.UI.handleNotifClick('${n.id}','${rid}')\">\r\n              <div class=\"ims__notif-item-title\">${UTIL.sanitize(n.title || 'Notification')}</div>\r\n              ${n.message ? `<div class=\"ims__notif-item-msg\">${UTIL.sanitize(n.message)}</div>` : ''}\r\n              ${when ? `<div class=\"ims__notif-item-time\">${when}</div>` : ''}\r\n            </div>`;\r\n          }).join('')\r\n        : `<div class=\"ims__notif-empty\"><i class=\"ti ti-check\" style=\"font-size:22px;display:block;margin-bottom:6px\"></i>You're all caught up</div>`;\r\n      panel.innerHTML = `\r\n        <div class=\"ims__notif-head\">\r\n          <strong>Notifications${notes.length ? ` (${notes.length})` : ''}</strong>\r\n          ${notes.length ? `<button class=\"ims__btn ims__btn--ghost ims__btn--sm\" onclick=\"window.SM.UI.markAllNotifsRead()\">Mark all read</button>` : ''}\r\n        </div>\r\n        ${rows}`;\r\n    },\r\n\r\n    toggleNotifPanel() {\r\n      const panel = document.getElementById('ims-notif-panel');\r\n      if (!panel) return;\r\n      const isOpen = panel.classList.contains('ims__notif-panel--open');\r\n      if (isOpen) { panel.classList.remove('ims__notif-panel--open'); return; }\r\n      this.renderNotifPanel();\r\n      panel.classList.add('ims__notif-panel--open');\r\n    },\r\n\r\n    async handleNotifClick(notifId, referenceId) {\r\n      // Mark this one read, then route to the relevant place. Claims-related\r\n      // notifications live in Logistics lost orders; fall back to just\r\n      // closing if we cannot infer a destination.\r\n      try {\r\n        const client = sb();\r\n        if (client && notifId) await client.from('notifications').update({ is_read: true }).eq('id', notifId);\r\n      } catch (e) { /* non-fatal */ }\r\n      document.getElementById('ims-notif-panel')?.classList.remove('ims__notif-panel--open');\r\n      await this.loadNotifications();\r\n      this.showPanel('logistics');\r\n    },\r\n\r\n    async markAllNotifsRead() {\r\n      try {\r\n        const client = sb();\r\n        if (client && AUTH.company?.id) {\r\n          await client.from('notifications').update({ is_read: true }).eq('is_read', false);\r\n        }\r\n      } catch (e) { /* non-fatal */ }\r\n      await this.loadNotifications();\r\n      this.renderNotifPanel();\r\n    },\r\n\r\n    autocomplete(inputEl, fetchFn, onSelect) {\r\n      if (!inputEl) return;\r\n      const parent = inputEl.parentElement;\r\n      let dropdown = parent.querySelector('.ims__ac-dropdown');\r\n      if (!dropdown) {\r\n        dropdown = document.createElement('div');\r\n        dropdown.className = 'ims__ac-dropdown';\r\n        parent.style.position = 'relative';\r\n        parent.appendChild(dropdown);\r\n      }\r\n      const search = UTIL.debounce(async (val) => {\r\n        if (!val || val.length < 2) { dropdown.style.display = 'none'; return; }\r\n        const results = await fetchFn(val);\r\n        if (!results?.length) { dropdown.style.display = 'none'; return; }\r\n        dropdown.innerHTML = results.map((r, i) =>\r\n          `<div class=\"ims__ac-item\" data-i=\"${i}\">${UTIL.sanitize(r.label)}</div>`\r\n        ).join('');\r\n        dropdown.style.display = 'block';\r\n        dropdown.querySelectorAll('.ims__ac-item').forEach((item, i) => {\r\n          item.addEventListener('mousedown', e => {\r\n            e.preventDefault();\r\n            inputEl.value = results[i].label;\r\n            dropdown.style.display = 'none';\r\n            onSelect(results[i]);\r\n          });\r\n        });\r\n      }, 280);\r\n      inputEl.addEventListener('input', e => search(e.target.value));\r\n      inputEl.addEventListener('blur', () => setTimeout(() => { dropdown.style.display = 'none'; }, 200));\r\n      document.addEventListener('click', e => { if (!parent.contains(e.target)) dropdown.style.display = 'none'; });\r\n    }\r\n  };\r\n\r\n  // \u2500\u2500\u2500 UTILITIES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const UTIL = {\r\n    formatCurrency(amount, currency = 'GBP') {\r\n      return new Intl.NumberFormat('en-GB', {\r\n        style: 'currency', currency, minimumFractionDigits: 2\r\n      }).format(Number(amount) || 0);\r\n    },\r\n\r\n    // Two-step receivable logic, shared by every delivery-confirmation\r\n    // path that might create a wholesale accounts_receivable:\r\n    //  1. Check how the order was actually processed \u2014 if it's already\r\n    //     paid (e.g. genuine COD cash collected on delivery), no\r\n    //     receivable is needed at all, regardless of what the customer's\r\n    //     default credit terms say.\r\n    //  2. Only if the order is genuinely unpaid, fall back to the\r\n    //     customer's own configured credit terms to work out the real\r\n    //     due date \u2014 not a flat company-wide default that ignores who\r\n    //     the customer actually is.\r\n    // Returns null when no receivable should be created (order already\r\n    // paid), otherwise an ISO date string for due_date.\r\n    resolveReceivableDueDate(orderPaymentStatus, customerCreditTerms) {\r\n      if (orderPaymentStatus === 'paid') return null;\r\n      const daysByTerm = {\r\n        'Advance payment':  0,\r\n        'Cash on delivery': 0,\r\n        '7 days net':       7,\r\n        '14 days net':      14,\r\n        '30 days net':      30,\r\n        '60 days net':      60,\r\n      };\r\n      const days = daysByTerm[customerCreditTerms] ?? 14;\r\n      return new Date(Date.now() + days * 86400000).toISOString().split('T')[0];\r\n    },\r\n\r\n    // Marketplace commission on an order. Rate comes from platform_commissions\r\n    // ({commission_percent, commission_fixed}). Commission is charged on the\r\n    // full order value (total_value already includes delivery, per the\r\n    // business setup), plus any fixed per-order fee. Returns zeros when there\r\n    // is no rate \u2014 i.e. direct sales (website/wholesale/retail).\r\n    calcCommission(totalValue, rate) {\r\n      const gross = Number(totalValue) || 0;\r\n      const pct   = Number(rate?.commission_percent) || 0;\r\n      const fixed = Number(rate?.commission_fixed) || 0;\r\n      if (pct <= 0 && fixed <= 0) return { gross, commission: 0, net: gross, hasCommission: false };\r\n      const commission = +(gross * (pct / 100) + fixed).toFixed(2);\r\n      return { gross, commission, net: +(gross - commission).toFixed(2), hasCommission: true };\r\n    },\r\n\r\n    // Fetch a company's commission rates keyed by platform_id. Cached on the\r\n    // module so repeated calls (order list, detail, history) don't re-query.\r\n    async getCommissionRates() {\r\n      if (this._commRatesCache) return this._commRatesCache;\r\n      try {\r\n        const client = window.supabase.createClient(window.IMS_SUPABASE_URL, window.IMS_SUPABASE_ANON_KEY);\r\n        const { data } = await client.from('platform_commissions')\r\n          .select('platform_id,commission_percent,commission_fixed')\r\n          .eq('company_id', window.SM.AUTH.company?.id);\r\n        const map = {};\r\n        (data || []).forEach(r => { map[r.platform_id] = r; });\r\n        this._commRatesCache = map;\r\n        return map;\r\n      } catch (e) { return {}; }\r\n    },\r\n    _commRatesCache: null,\r\n\r\n    formatDate(dateStr) {\r\n      if (!dateStr) return '\u2014';\r\n      try { return new Intl.DateTimeFormat('en-GB').format(new Date(dateStr)); }\r\n      catch (e) { return '\u2014'; }\r\n    },\r\n\r\n    formatDateTime(dateStr) {\r\n      if (!dateStr) return '\u2014';\r\n      try {\r\n        return new Intl.DateTimeFormat('en-GB', {\r\n          day: '2-digit', month: '2-digit', year: 'numeric',\r\n          hour: '2-digit', minute: '2-digit'\r\n        }).format(new Date(dateStr));\r\n      } catch (e) { return '\u2014'; }\r\n    },\r\n\r\n    formatRole(role) {\r\n      const map = {\r\n        super_admin: 'Super Admin', admin: 'Admin',\r\n        imports_manager: 'Imports Manager', warehouse_manager: 'Warehouse Manager',\r\n        warehouse_staff: 'Warehouse Staff', order_management: 'Order Management',\r\n        logistics_staff: 'Logistics Staff', accounts_staff: 'Accounts Staff', viewer: 'Viewer',\r\n        custom: 'Custom', driver: 'Driver'\r\n      };\r\n      return map[role] || role || '\u2014';\r\n    },\r\n\r\n    initials(name) {\r\n      if (!name) return '??';\r\n      return name.trim().split(/\\s+/).map(w => w[0]).join('').toUpperCase().slice(0, 2);\r\n    },\r\n\r\n    generateRef(prefix, lastRef) {\r\n      const num = lastRef ? (parseInt(lastRef.split('-').pop()) || 0) + 1 : 1;\r\n      return `${prefix}-${String(num).padStart(5, '0')}`;\r\n    },\r\n\r\n    daysSince(dateStr) {\r\n      if (!dateStr) return 0;\r\n      return Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);\r\n    },\r\n\r\n    isDelayed(order) {\r\n      return order.status === 'dispatched' && this.daysSince(order.dispatched_at) >= IMS.delayedDays;\r\n    },\r\n\r\n    sanitize(str) {\r\n      if (str === null || str === undefined) return '';\r\n      const el = document.createElement('div');\r\n      el.textContent = String(str);\r\n      // innerHTML escapes & < > but not quote marks. They are escaped too,\r\n      // so text placed inside an attribute (value=\"...\", title=\"...\") can\r\n      // never end the attribute early or add code to the page. On screen\r\n      // they still show as ' and \".\r\n      return el.innerHTML.replace(/\"/g, '&quot;').replace(/'/g, '&#39;');\r\n    },\r\n\r\n    // For text placed inside a button's action, for example\r\n    // onclick=\"window.IMS_X.f('${util().jsArg(name)}')\". Escaped first for\r\n    // the JavaScript string, then for the HTML attribute, so names such as\r\n    // Bob's Beds or Sofa 72\" Wide reach the function exactly as they are,\r\n    // and nothing in them can end the string or the attribute. (sanitize()\r\n    // is not enough there: the browser turns &#39; back into ' before the\r\n    // action runs, so the apostrophe ended the string and the button did\r\n    // nothing.)\r\n    jsArg(str) {\r\n      if (str === null || str === undefined) return '';\r\n      return String(str)\r\n        .replace(/\\\\/g, '\\\\\\\\')\r\n        .replace(/'/g, \"\\\\'\")\r\n        .replace(/\"/g, '\\\\\"')\r\n        .replace(/\\r?\\n/g, '\\\\n')\r\n        .replace(/&/g, '&amp;')\r\n        .replace(/</g, '&lt;')\r\n        .replace(/>/g, '&gt;')\r\n        .replace(/\"/g, '&quot;');\r\n    },\r\n\r\n    debounce(fn, delay = 300) {\r\n      let timer;\r\n      return function (...args) {\r\n        clearTimeout(timer);\r\n        timer = setTimeout(() => fn.apply(this, args), delay);\r\n      };\r\n    },\r\n\r\n    pill(status) {\r\n      const map = {\r\n        draft:               ['Draft',             'neutral'],\r\n        confirmed:           ['Confirmed',         'warning'],\r\n        picking:             ['Picking',           'info'],\r\n        packed:              ['Packed',            'accent'],\r\n        dispatched:          ['Dispatched',        'purple'],\r\n        delivered:           ['Delivered',         'success'],\r\n        failed:              ['Failed',            'danger'],\r\n        lost:                ['Lost',              'danger'],\r\n        cancelled:           ['Cancelled',         'neutral'],\r\n        delayed:             ['Delayed',           'danger'],\r\n        received:            ['Received',          'warning'],\r\n        collected:           ['Collected',         'success'],\r\n        return_requested:    ['Return Requested',  'warning'],\r\n        return_received:     ['Return Received',   'info'],\r\n        refunded:            ['Refunded',          'neutral'],\r\n        unpaid:              ['Unpaid',            'danger'],\r\n        partial:             ['Partial',           'warning'],\r\n        paid:                ['Paid',              'success'],\r\n        overdue:             ['Overdue',           'danger'],\r\n        disputed:            ['Disputed',          'warning'],\r\n        refunded_payment:    ['Refunded',          'neutral'],\r\n        ordered:             ['Ordered',           'neutral'],\r\n        in_transit:          ['In Transit',        'warning'],\r\n        customs:             ['Customs',           'warning'],\r\n        cleared:             ['Cleared',           'success'],\r\n        grn_pending:         ['GRN Pending',       'warning'],\r\n        in_warehouse:        ['In Warehouse',      'success'],\r\n        approved:            ['Approved',          'info'],\r\n        sent:                ['Sent',              'info'],\r\n        partially_received:  ['Part Received',     'warning'],\r\n        completed:           ['Completed',         'success'],\r\n        raised:              ['Raised',            'warning'],\r\n        acknowledged:        ['Acknowledged',      'info'],\r\n        settled:             ['Settled',           'success'],\r\n        rejected:            ['Rejected',          'danger'],\r\n        escalated_14:        ['14d Escalated',     'warning'],\r\n        overdue_30:          ['30d Overdue',       'danger'],\r\n        active:              ['Active',            'success'],\r\n        inactive:            ['Inactive',          'neutral'],\r\n        good:                ['Good',              'success'],\r\n        damaged:             ['Damaged',           'danger'],\r\n        missing:             ['Missing',           'danger'],\r\n        // Invoice statuses\r\n        voided:              ['Voided',            'neutral'],\r\n        partially_paid:      ['Part Paid',         'warning'],\r\n      };\r\n      const [label, type] = map[status] || [status || '\u2014', 'neutral'];\r\n      if (type === 'purple') {\r\n        return `<span class=\"ims__pill\" style=\"background:#ede9fe;color:#6d28d9\">${label}</span>`;\r\n      }\r\n      return `<span class=\"ims__pill ims__pill--${type}\">${label}</span>`;\r\n    },\r\n\r\n    buildTrackingUrl(courier, trackingNumber, postcode) {\r\n      if (!courier?.tracking_url_base || !trackingNumber) return null;\r\n      // Some couriers (e.g. V1 Distribution) validate tracking number AND\r\n      // postcode through a POST form, then generate a server-side token for\r\n      // the result page. That token cannot be derived from anything we hold,\r\n      // so the only linkable page is the form itself.\r\n      if (courier.tracking_form_only) return courier.tracking_url_base;\r\n      let url = courier.tracking_url_base + encodeURIComponent(trackingNumber.trim());\r\n      if (courier.requires_postcode && postcode) {\r\n        const pc = postcode.replace(/\\s/g, '').toUpperCase();\r\n        const param = courier.postcode_param || '&postcode=';\r\n        url += param + encodeURIComponent(pc);\r\n      }\r\n      return url;\r\n    },\r\n\r\n    // Opens a form-only courier's tracking page and puts the tracking number\r\n    // on the clipboard, so staff paste rather than retype it. The postcode is\r\n    // short enough to read off the toast.\r\n    async copyAndTrack(trackingNumber, postcode, url) {\r\n      const tn = String(trackingNumber || '').trim();\r\n      // Copy BEFORE opening the tab. window.open() moves focus away, and\r\n      // navigator.clipboard.writeText() is rejected on an unfocused\r\n      // document - so the copy silently never happened.\r\n      let copied = false;\r\n      if (tn) {\r\n        try {\r\n          await navigator.clipboard.writeText(tn);\r\n          copied = true;\r\n        } catch (e) {\r\n          // Fallback for contexts where the async clipboard API is blocked.\r\n          try {\r\n            const ta = document.createElement('textarea');\r\n            ta.value = tn;\r\n            ta.style.position = 'fixed';\r\n            ta.style.opacity = '0';\r\n            document.body.appendChild(ta);\r\n            ta.select();\r\n            copied = document.execCommand('copy');\r\n            document.body.removeChild(ta);\r\n          } catch (e2) { copied = false; }\r\n        }\r\n      }\r\n      UI.toast(\r\n        copied\r\n          ? `Tracking number copied${postcode ? ' - postcode is ' + postcode : ''}. Paste it into the courier's form.`\r\n          : `Could not copy. Tracking: ${tn}${postcode ? ' | Postcode: ' + postcode : ''}`,\r\n        copied ? 'success' : 'warning',\r\n        9000\r\n      );\r\n      window.open(url, '_blank');\r\n    },\r\n\r\n    // Returns a tracking link, or a copy-and-open button for form-only\r\n    // couriers. Falls back to plain text when there is nothing to link to.\r\n    trackingLink(courier, trackingNumber, postcode, innerHtml, className) {\r\n      const url = this.buildTrackingUrl(courier, trackingNumber, postcode);\r\n      if (!url) return innerHtml;\r\n      const cls = className || 'ims__link';\r\n      if (courier?.tracking_form_only) {\r\n        const tn = String(trackingNumber || '').replace(/['\"\\\\]/g, '');\r\n        const pc = String(postcode || '').replace(/['\"\\\\]/g, '');\r\n        const style = cls.includes('ims__btn')\r\n          ? 'cursor:pointer'\r\n          : 'background:none;border:none;padding:0;font:inherit;color:inherit;cursor:pointer';\r\n        return `<button type=\"button\" class=\"${cls}\" style=\"${style}\" onclick=\"window.SM.UTIL.copyAndTrack('${tn}','${pc}','${url}')\" title=\"Copies the tracking number, then opens the courier's tracking form\">${innerHtml}</button>`;\r\n      }\r\n      return `<a href=\"${url}\" target=\"_blank\" class=\"${cls}\" title=\"Track on courier website\">${innerHtml}</a>`;\r\n    },\r\n\r\n    // \u2500\u2500\u2500 BOX SUMMARY HELPER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    async getOrderBoxSummary(orderItems) {\r\n      const empty = { totalBoxes: 0, totalWeightKg: 0, totalVolWtKg: 0, hasData: false, lines: [] };\r\n      if (!orderItems || !orderItems.length) return empty;\r\n\r\n      const productIds = [...new Set(orderItems.map(i => i.product_id).filter(Boolean))];\r\n      if (!productIds.length) return empty;\r\n\r\n      const client = window.supabase.createClient(window.IMS_SUPABASE_URL, window.IMS_SUPABASE_ANON_KEY);\r\n      const { data: allBoxes, error } = await client\r\n        .from('product_boxes')\r\n        .select('product_id, box_number, label, length_cm, width_cm, height_cm, weight_kg')\r\n        .in('product_id', productIds)\r\n        .order('product_id')\r\n        .order('box_number');\r\n\r\n      if (error || !allBoxes || !allBoxes.length) return empty;\r\n\r\n      const boxMap = {};\r\n      allBoxes.forEach(b => {\r\n        if (!boxMap[b.product_id]) boxMap[b.product_id] = [];\r\n        boxMap[b.product_id].push(b);\r\n      });\r\n\r\n      let totalBoxes = 0, totalWeightKg = 0, totalVolWtKg = 0, hasData = false;\r\n\r\n      const lines = orderItems.map(item => {\r\n        const productBoxes = boxMap[item.product_id] || [];\r\n        const qty = item.quantity || 1;\r\n\r\n        if (!productBoxes.length) {\r\n          return {\r\n            productId: item.product_id,\r\n            productName: item.product_name || item.products?.name || '\u2014',\r\n            sku: item.sku || item.products?.sku || '\u2014',\r\n            quantity: qty, boxes: [],\r\n            lineBoxes: 0, lineWeightKg: 0, lineVolWtKg: 0\r\n          };\r\n        }\r\n\r\n        hasData = true;\r\n        const lineBoxes = productBoxes.length * qty;\r\n        const lineWeightKg = productBoxes.reduce((s, b) => s + (b.weight_kg || 0), 0) * qty;\r\n        const lineVolWtKg = productBoxes.reduce((s, b) => {\r\n          return s + (((b.length_cm || 0) * (b.width_cm || 0) * (b.height_cm || 0)) / 5000);\r\n        }, 0) * qty;\r\n\r\n        totalBoxes    += lineBoxes;\r\n        totalWeightKg += lineWeightKg;\r\n        totalVolWtKg  += lineVolWtKg;\r\n\r\n        return {\r\n          productId: item.product_id,\r\n          productName: item.product_name || item.products?.name || '\u2014',\r\n          sku: item.sku || item.products?.sku || '\u2014',\r\n          quantity: qty, boxes: productBoxes,\r\n          lineBoxes,\r\n          lineWeightKg: parseFloat(lineWeightKg.toFixed(2)),\r\n          lineVolWtKg:  parseFloat(lineVolWtKg.toFixed(2))\r\n        };\r\n      });\r\n\r\n      return {\r\n        totalBoxes,\r\n        totalWeightKg: parseFloat(totalWeightKg.toFixed(2)),\r\n        totalVolWtKg:  parseFloat(totalVolWtKg.toFixed(2)),\r\n        hasData, lines\r\n      };\r\n    },\r\n\r\n    // \u2500\u2500\u2500 VAT HELPERS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    // Calculate net amount from gross (VAT-inclusive)\r\n    grossToNet(gross, vatRate = 20) {\r\n      return parseFloat((gross / (1 + vatRate / 100)).toFixed(2));\r\n    },\r\n    // Calculate VAT amount from net\r\n    netToVat(net, vatRate = 20) {\r\n      return parseFloat((net * vatRate / 100).toFixed(2));\r\n    },\r\n    // Calculate gross from net\r\n    netToGross(net, vatRate = 20) {\r\n      return parseFloat((net * (1 + vatRate / 100)).toFixed(2));\r\n    },\r\n    // Format VAT rate for display\r\n    formatVatRate(rate) {\r\n      return rate === 0 ? 'Zero rated' : rate === 20 ? 'Standard 20%' : `${rate}%`;\r\n    },\r\n\r\n    // opts.landscape: print this one on landscape A4 (default portrait).\r\n    print(html, title, opts = {}) {\r\n      const c = AUTH.company;\r\n      const win = window.open('', '_blank', 'width=900,height=700');\r\n      if (!win) { UI.toast('Pop-up blocked. Please allow pop-ups for this site.', 'warning'); return; }\r\n      const isCustomLayout = html.includes('<style>');\r\n      if (isCustomLayout) {\r\n        win.document.write(`<!DOCTYPE html><html lang=\"en\"><head>\r\n          <meta charset=\"UTF-8\"><title>${this.sanitize(title)}</title>\r\n        </head><body>${html}</body></html>`);\r\n      } else {\r\n        win.document.write(`<!DOCTYPE html><html lang=\"en\"><head>\r\n          <meta charset=\"UTF-8\"><title>${this.sanitize(title)} | StockMaestro</title>\r\n          <style>\r\n            *{margin:0;padding:0;box-sizing:border-box}\r\n            body{font-family:Arial,sans-serif;font-size:12px;color:#111;padding:20px;line-height:1.4}\r\n            .ph{display:flex;justify-content:space-between;align-items:flex-start;padding-bottom:12px;border-bottom:2px solid #1B3A6B;margin-bottom:18px}\r\n            .ph-name{font-size:20px;font-weight:700;color:#1B3A6B}\r\n            .ph-co{font-size:11px;color:#555;margin-top:2px}\r\n            .ph-right{text-align:right;font-size:11px;color:#555}\r\n            h2{color:#1B3A6B;font-size:14px;margin:14px 0 8px}\r\n            table{width:100%;border-collapse:collapse;margin:8px 0;font-size:11px}\r\n            th{background:#1B3A6B;color:#fff;padding:5px 8px;text-align:left}\r\n            td{padding:4px 8px;border-bottom:1px solid #eee}\r\n            tr:nth-child(even) td{background:#f9f9f9}\r\n            .pf{margin-top:20px;padding-top:8px;border-top:1px solid #ccc;font-size:10px;color:#999;text-align:center}\r\n            .pill{display:inline-block;padding:1px 7px;border-radius:999px;font-size:10px;background:#e5e7eb;color:#374151}\r\n            /* The browser prints its own lines (page title, web address, date,\r\n               page numbers) in the page margin. With no page margin there is\r\n               nowhere for them, so Chrome and Edge leave them off. The white\r\n               space comes back through the frame below: its top and bottom\r\n               rows repeat on every printed page. */\r\n            @page{size:A4${opts && opts.landscape ? ' landscape' : ''};margin:0}\r\n            table.pw{width:100%;border-collapse:collapse;margin:0;font-size:12px}\r\n            table.pw>thead>tr>td,table.pw>tfoot>tr>td,table.pw>tbody>tr>td{padding:0;border:none;background:none}\r\n            td.pw-gap{height:12mm}\r\n            @media print{body{padding:0 12mm}.no-print{display:none}}\r\n          </style></head><body>\r\n          <table class=\"pw\"><thead><tr><td class=\"pw-gap\"></td></tr></thead><tfoot><tr><td class=\"pw-gap\"></td></tr></tfoot><tbody><tr><td>\r\n          <div class=\"ph\">\r\n            <div>\r\n              <div class=\"ph-name\">StockMaestro</div>\r\n              <div class=\"ph-co\">${this.sanitize(c?.name || '')}${c?.vat_number ? ' | VAT: ' + c.vat_number : ''}</div>\r\n              ${c?.address ? `<div class=\"ph-co\">${this.sanitize(c.address)}</div>` : ''}\r\n            </div>\r\n            <div class=\"ph-right\">\r\n              <strong>${this.sanitize(title)}</strong><br>\r\n              ${new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' }).format(new Date())}\r\n            </div>\r\n          </div>\r\n          ${html}\r\n          <div class=\"pf\">StockMaestro &copy; 2026 ABSA Digital. All rights reserved. | ${this.sanitize(title)}</div>\r\n          </td></tr></tbody></table>\r\n          </body></html>`);\r\n      }\r\n      win.document.close();\r\n      setTimeout(() => win.print(), 500);\r\n    },\r\n\r\n    exportCSV(headers, rows, filename) {\r\n      const lines = [\r\n        headers.map(h => `\"${h}\"`).join(','),\r\n        ...rows.map(row => row.map(c => `\"${String(c ?? '').replace(/\"/g, '\"\"')}\"`).join(','))\r\n      ];\r\n      // The leading \\uFEFF is an invisible \"this is UTF-8\" marker. Without\r\n      // it, Excel on Windows misreads \u00A3 as \"\u00C2\u00A3\" (and garbles accents).\r\n      const blob = new Blob(['\\uFEFF' + lines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\r\n      const a = document.createElement('a');\r\n      a.href = URL.createObjectURL(blob);\r\n      a.download = `stockmaestro_${filename}_${new Date().toLocaleDateString('en-GB').replace(/\\//g, '-')}.csv`;\r\n      a.click();\r\n      URL.revokeObjectURL(a.href);\r\n    },\r\n\r\n    parseCSV(text) {\r\n      // Excel adds the same invisible UTF-8 marker when saving as\r\n      // \"CSV UTF-8\". Remove it, or the first column heading would not match.\r\n      text = String(text || '').replace(/^\\uFEFF/, '');\r\n      const lines = text.split(/\\r?\\n/).filter(l => l.trim());\r\n      if (!lines.length) return { headers: [], rows: [] };\r\n      const parse = line => {\r\n        const result = []; let current = ''; let inQuotes = false;\r\n        for (let i = 0; i < line.length; i++) {\r\n          if (line[i] === '\"') {\r\n            if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\r\n            else inQuotes = !inQuotes;\r\n          } else if (line[i] === ',' && !inQuotes) {\r\n            result.push(current.trim()); current = '';\r\n          } else current += line[i];\r\n        }\r\n        result.push(current.trim());\r\n        return result;\r\n      };\r\n      const headers = parse(lines[0]);\r\n      const rows = lines.slice(1).map(l => parse(l));\r\n      return { headers, rows };\r\n    }\r\n  };\r\n\r\n  // \u2500\u2500\u2500 KEEP-ALIVE PING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  function keepAlive() {\r\n    setInterval(async () => {\r\n      const client = sb();\r\n      if (client && AUTH.company?.id) {\r\n        await client.from('companies').select('id').eq('id', AUTH.company.id).limit(1);\r\n      }\r\n    }, 23 * 60 * 60 * 1000);\r\n  }\r\n\r\n  // \u2500\u2500\u2500 ALL SCREEN SCRIPTS LOADED \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // Every screen has its own script, and each one listens for the \"screen\r\n  // opened\" signal. All of them have run by the time the page fires\r\n  // DOMContentLoaded (or 'load'). The first screen is only opened after\r\n  // that, so no screen can miss its signal. (Before, a second, duplicate\r\n  // signal from the address change sometimes covered for a screen whose\r\n  // script had not loaded yet; that duplicate is now gone.)\r\n  const _allScriptsLoaded = new Promise(resolve => {\r\n    if (document.readyState === 'complete') return resolve();\r\n    document.addEventListener('DOMContentLoaded', resolve, { once: true });\r\n    window.addEventListener('load', resolve, { once: true });\r\n  });\r\n\r\n  // \u2500\u2500\u2500 SESSION KEEPER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // Supabase signs requests with a pass (JWT) that expires about an hour\r\n  // after it is issued, and renews it in the background. Two gaps caused\r\n  // \"JWT expired\" errors:\r\n  //  - AUTH.session is a copy of the pass that was never updated when it\r\n  //    was renewed, and several screens send that copy directly\r\n  //    (Logistics status updates, refunds, the restock stamp), so they\r\n  //    failed after about an hour even during normal use;\r\n  //  - a sleeping computer or background tab pauses the renewal timer, so\r\n  //    the first actions after coming back went out with an expired pass.\r\n  // This keeps AUTH.session in step with every renewal, renews straight\r\n  // away when the tab is shown again or the computer wakes, checks quietly\r\n  // every 5 minutes, and if the pass genuinely cannot be renewed, shows a\r\n  // clear \"please sign in again\" message instead of raw errors. It never\r\n  // reloads the page, so nothing typed is lost.\r\n  let _sessionKeeperStarted = false;\r\n  let _sessionEndedShown = false;\r\n  let _sessionCheck = null;\r\n\r\n  function sessionEnded() {\r\n    if (_sessionEndedShown) return;\r\n    _sessionEndedShown = true;\r\n    AUTH.user = null;\r\n    AUTH.company = null;\r\n    AUTH.session = null;\r\n    try { UI.closeModal(); } catch (e) { /* no modal open */ }\r\n    UI.showLogin();\r\n    const errEl = document.getElementById('ims-login-error');\r\n    if (errEl) { errEl.textContent = 'Your session has ended. Please sign in again.'; errEl.style.display = 'block'; }\r\n  }\r\n\r\n  // A refresh that failed because the pass can never be renewed (as\r\n  // opposed to a brief loss of internet, which must NOT sign anyone out).\r\n  function _isFatalAuthError(error) {\r\n    if (!error) return false;\r\n    if (error.status === 400 || error.status === 401) return true;\r\n    return /refresh token|invalid grant|session.*(not found|missing)/i.test(error.message || '');\r\n  }\r\n\r\n  // Makes sure the pass has at least two minutes left, renewing it now if\r\n  // not. Returns true if the session is good. Safe to call often: calls\r\n  // made while one is already running share its result.\r\n  function ensureFreshSession() {\r\n    if (!AUTH.user) return Promise.resolve(false);\r\n    if (_sessionCheck) return _sessionCheck;\r\n    _sessionCheck = (async () => {\r\n      try {\r\n        const client = sb();\r\n        if (!client) return false;\r\n        const { data: got, error: getErr } = await client.auth.getSession();\r\n        const session = got?.session;\r\n        if (getErr && _isFatalAuthError(getErr)) { sessionEnded(); return false; }\r\n        if (!session) {\r\n          if (navigator.onLine !== false && !getErr) sessionEnded();\r\n          return false;\r\n        }\r\n        const secondsLeft = (session.expires_at || 0) - Math.floor(Date.now() / 1000);\r\n        if (secondsLeft > 120) { AUTH.session = session; return true; }\r\n        const { data, error } = await client.auth.refreshSession();\r\n        if (error || !data?.session) {\r\n          if (_isFatalAuthError(error)) sessionEnded();\r\n          return false;\r\n        }\r\n        AUTH.session = data.session;\r\n        return true;\r\n      } catch (e) {\r\n        console.warn('[StockMaestro] Session check failed (will retry):', e);\r\n        return false;\r\n      } finally {\r\n        _sessionCheck = null;\r\n      }\r\n    })();\r\n    return _sessionCheck;\r\n  }\r\n\r\n  function startSessionKeeper() {\r\n    _sessionEndedShown = false;\r\n    if (_sessionKeeperStarted) return;\r\n    _sessionKeeperStarted = true;\r\n    const client = sb();\r\n    if (!client) return;\r\n    client.auth.onAuthStateChange((event, session) => {\r\n      if (session) AUTH.session = session;\r\n      // Signed out while someone was still using the app: the session was\r\n      // ended by Supabase, not by the Sign out button (which clears the\r\n      // user first).\r\n      if (event === 'SIGNED_OUT' && AUTH.user) sessionEnded();\r\n    });\r\n    document.addEventListener('visibilitychange', () => {\r\n      if (document.visibilityState === 'visible') ensureFreshSession();\r\n    });\r\n    window.addEventListener('focus', () => ensureFreshSession());\r\n    window.addEventListener('online', () => ensureFreshSession());\r\n    setInterval(() => ensureFreshSession(), 5 * 60 * 1000);\r\n  }\r\n\r\n  // \u2500\u2500\u2500 BOOT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // \u2500\u2500\u2500 DRIVER MODE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // A Driver never sees the office screens. They sign in like anyone else\r\n  // and land here; everything they see and do goes through the driver-api\r\n  // Edge Function (the database gives drivers no direct access to company\r\n  // data). Step D1: their open manifests and Start (= Van departed). The\r\n  // full driver screens (stops, deliveries, POD, COD) come in Step D3.\r\n  async function driverApi(action, body = {}) {\r\n    const client = sb();\r\n    if (!client) return { error: 'No connection' };\r\n    const { data: got } = await client.auth.getSession();\r\n    const token = got?.session?.access_token;\r\n    if (!token) return { error: 'Your session has ended. Please sign in again.' };\r\n    try {\r\n      const r = await fetch(`${window.IMS_SUPABASE_URL}/functions/v1/driver-api`, {\r\n        method: 'POST',\r\n        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, 'apikey': window.IMS_SUPABASE_ANON_KEY },\r\n        body: JSON.stringify({ action, ...body })\r\n      });\r\n      const out = await r.json().catch(() => ({}));\r\n      if (!r.ok || out.error) return { error: out.error || (r.status === 404 ? 'The driver service (driver-api) is not set up in Supabase yet.' : `Error ${r.status}`) };\r\n      return out;\r\n    } catch (e) {\r\n      return { error: 'No signal: StockMaestro could not be reached. Try again in a moment.' };\r\n    }\r\n  }\r\n\r\n  const DRIVER = {\r\n    _armed: null,\r\n    async enter() {\r\n      ['ims-login-screen', 'ims-setpw-screen', 'ims-app'].forEach(id => { const x = document.getElementById(id); if (x) x.style.display = 'none'; });\r\n      let el = document.getElementById('ims-driver');\r\n      if (!el) { el = document.createElement('div'); el.id = 'ims-driver'; document.body.appendChild(el); }\r\n      el.style.cssText = 'position:fixed;inset:0;z-index:9999;overflow-y:auto;background:#f3f4f6;color:#111827;-webkit-text-size-adjust:100%;font-size:16px;line-height:1.4';\r\n      // The full driver screens (ims-driver.js). The simple list below stays\r\n      // as a fallback if that file is ever missing.\r\n      if (window.IMS_DRIVER?.enter) { await window.IMS_DRIVER.enter({ api: driverApi }); return; }\r\n      el.innerHTML = '<div style=\"padding:40px 16px;text-align:center;font-size:17px\">Loading your manifests...</div>';\r\n      await this.refresh();\r\n    },\r\n    async refresh() {\r\n      const el = document.getElementById('ims-driver'); if (!el) return;\r\n      const [me, list] = await Promise.all([driverApi('me'), driverApi('my_manifests')]);\r\n      const esc = v => (window.SM.UTIL?.sanitize ? window.SM.UTIL.sanitize(v) : String(v ?? ''));\r\n      const when = d => { try { return new Date(d).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' }); } catch (e) { return ''; } };\r\n      const time = d => { try { return new Date(d).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }); } catch (e) { return ''; } };\r\n      const err = me.error || list.error;\r\n      const card = m => `\r\n        <div style=\"background:#fff;border-radius:14px;padding:16px;margin:0 0 12px;box-shadow:0 1px 3px rgba(0,0,0,.08)\">\r\n          <div style=\"display:flex;justify-content:space-between;align-items:baseline;gap:8px\">\r\n            <div style=\"font-size:19px;font-weight:700\">${esc(m.manifest_number)}</div>\r\n            <div style=\"font-size:14px;color:#6b7280\">${esc(when(m.date))}</div>\r\n          </div>\r\n          <div style=\"font-size:15px;color:#374151;margin:4px 0 12px\">${m.stops} stop${m.stops === 1 ? '' : 's'} &middot; ${m.done} done${m.vehicle ? ` &middot; ${esc(m.vehicle)}` : ''}</div>\r\n          ${m.started\r\n            ? `<div style=\"padding:14px;border-radius:12px;background:#ecfdf5;color:#047857;font-weight:600;text-align:center\">Started ${esc(time(m.departed_at))}</div>`\r\n            : `<button data-start=\"${esc(m.id)}\" onclick=\"window.SM.DRIVER.start('${esc(m.id)}', this)\" style=\"width:100%;min-height:56px;border:0;border-radius:12px;background:#2563eb;color:#fff;font-size:18px;font-weight:700\">Start</button>`}\r\n        </div>`;\r\n      el.innerHTML = `\r\n        <div style=\"max-width:560px;margin:0 auto;padding:16px 16px calc(96px + env(safe-area-inset-bottom))\">\r\n          <div style=\"display:flex;align-items:center;gap:12px;margin:4px 0 16px\">\r\n            ${me.company?.logo_url ? `<img src=\"${esc(me.company.logo_url)}\" alt=\"\" style=\"height:36px;max-width:120px;object-fit:contain\">` : ''}\r\n            <div style=\"min-width:0\">\r\n              <div style=\"font-size:13px;color:#6b7280\">${esc(me.company?.name || 'StockMaestro')}</div>\r\n              <div style=\"font-size:19px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">${esc(me.driver?.name || me.user?.name || 'Driver')}</div>\r\n            </div>\r\n          </div>\r\n          ${err ? `<div style=\"background:#fef2f2;color:#b91c1c;border-radius:12px;padding:14px;margin-bottom:12px\">${esc(err)}</div>` : ''}\r\n          <div style=\"font-size:15px;font-weight:700;color:#374151;margin:8px 0\">My manifests</div>\r\n          ${!err && !(list.manifests || []).length ? '<div style=\"background:#fff;border-radius:14px;padding:20px;text-align:center;color:#6b7280\">No open manifests for you right now.</div>' : ''}\r\n          ${(list.manifests || []).map(card).join('')}\r\n          <div style=\"font-size:13px;color:#9ca3af;text-align:center;margin-top:16px\">Stops, deliveries and proof of delivery are coming in the next update.</div>\r\n        </div>\r\n        <div style=\"position:fixed;left:0;right:0;bottom:0;background:#fff;border-top:1px solid #e5e7eb;padding:10px 16px calc(10px + env(safe-area-inset-bottom));display:flex;gap:10px\">\r\n          <button onclick=\"window.SM.DRIVER.refresh()\" style=\"flex:1;min-height:52px;border:1px solid #d1d5db;border-radius:12px;background:#fff;font-size:16px;font-weight:600;color:#111827\">Refresh</button>\r\n          <button onclick=\"window.SM.AUTH.signOut()\" style=\"flex:1;min-height:52px;border:1px solid #d1d5db;border-radius:12px;background:#fff;font-size:16px;color:#374151\">Sign out</button>\r\n        </div>`;\r\n    },\r\n    // Tap once: \"Tap again to start\"; a second tap within 5 seconds starts.\r\n    async start(id, btn) {\r\n      if (this._armed !== id) {\r\n        this._armed = id;\r\n        if (btn) { btn.textContent = 'Tap again to start'; btn.style.background = '#1d4ed8'; }\r\n        setTimeout(() => { if (this._armed === id) { this._armed = null; if (btn && btn.isConnected) { btn.textContent = 'Start'; btn.style.background = '#2563eb'; } } }, 5000);\r\n        return;\r\n      }\r\n      this._armed = null;\r\n      if (btn) { btn.disabled = true; btn.textContent = 'Starting...'; }\r\n      const r = await driverApi('start', { manifest_id: id });\r\n      if (r.error) { if (btn) { btn.disabled = false; btn.textContent = 'Start'; } alert(r.error); return; }\r\n      await this.refresh();\r\n    }\r\n  };\r\n\r\n  // \u2500\u2500\u2500 QUEUED CUSTOMER EMAILS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // Deliveries recorded by the server (driver app, manifest buttons) queue\r\n  // the customer's email; any open office screen sends it within a minute,\r\n  // with StockMaestro's own templates (EMAIL.send, which also never sends\r\n  // the same email twice). Each queued email is claimed first, so two open\r\n  // office screens never both send it. Failed sends are retried, up to 5\r\n  // times. Never runs in Driver mode.\r\n  let _emailQueueTimer = null, _emailQueueBusy = false;\r\n  async function processEmailQueue() {\r\n    if (!AUTH.user || AUTH.user.role === 'driver' || !AUTH.company?.id || _emailQueueBusy) return;\r\n    _emailQueueBusy = true;\r\n    try {\r\n      const client = sb(); if (!client) return;\r\n      const stale = new Date(Date.now() - 5 * 60000).toISOString();\r\n      const { data: rows } = await client.from('email_queue').select('id,order_id,email_type,attempts,claimed_at')\r\n        .is('sent_at', null).lt('attempts', 5).order('created_at', { ascending: true }).limit(10);\r\n      for (const q of (rows || [])) {\r\n        if (q.claimed_at && q.claimed_at > stale) continue;   // another screen is sending it\r\n        let claim = client.from('email_queue').update({ claimed_at: new Date().toISOString(), claimed_by: AUTH.user.id }).eq('id', q.id);\r\n        claim = q.claimed_at ? claim.eq('claimed_at', q.claimed_at) : claim.is('claimed_at', null);\r\n        const { data: got } = await claim.select('id');\r\n        if (!(got || []).length) continue;\r\n        const res = await EMAIL.send(q.order_id, q.email_type);\r\n        if (res?.sent || res?.skipped) {\r\n          await client.from('email_queue').update({ sent_at: new Date().toISOString(), result: res.sent ? 'sent' : `skipped: ${res.reason || ''}`.trim() }).eq('id', q.id);\r\n        } else {\r\n          await client.from('email_queue').update({ attempts: (q.attempts || 0) + 1, last_error: String(res?.error || 'unknown').slice(0, 500), claimed_at: null }).eq('id', q.id);\r\n        }\r\n      }\r\n    } catch (e) {\r\n      console.warn('[StockMaestro] Email queue:', e?.message || e);\r\n    } finally {\r\n      _emailQueueBusy = false;\r\n    }\r\n  }\r\n  function startEmailQueue() {\r\n    if (_emailQueueTimer || AUTH.user?.role === 'driver') return;\r\n    processEmailQueue();\r\n    _emailQueueTimer = setInterval(processEmailQueue, 60000);\r\n  }\r\n\r\n  async function enterDriverMode() {\r\n    startSessionKeeper();\r\n    await DRIVER.enter();\r\n  }\r\n\r\n  async function enterApp() {\r\n    // A Driver goes straight to Driver mode: never the office screens.\r\n    if (AUTH.user?.role === 'driver') { await enterDriverMode(); return; }\r\n    // Licence check before showing app\r\n    const licenceStatus = await checkLicence();\r\n    if (licenceStatus === 'suspended' || licenceStatus === 'expired') return; // lockout screen shown\r\n    UI.showApp();\r\n    startSessionKeeper();\r\n    await _allScriptsLoaded;\r\n    // Land on the requested screen if this user may open it, otherwise on\r\n    // the first screen they are allowed (for example Imports for an Imports\r\n    // Manager), quietly: no \"no access\" message just for signing in.\r\n    const hash = location.hash.replace('#', '') || 'dashboard';\r\n    UI.showPanel(UI.canOpenPanel(hash) ? hash : (UI.firstAllowedPanel() || hash));\r\n    keepAlive();\r\n    startEmailQueue();\r\n    if (AUTH.company?.id) {\r\n      const client = sb();\r\nif (client) await client.rpc('check_claim_alerts');\r\n      UI.loadNotifications();\r\n    }\r\n    // Bell click toggles the panel; a document click closes it when you\r\n    // click away. Bound once here at init.\r\n    const notifBtn = document.getElementById('ims-notif-btn');\r\n    if (notifBtn && !notifBtn.dataset.bound) {\r\n      notifBtn.dataset.bound = '1';\r\n      notifBtn.addEventListener('click', e => { e.stopPropagation(); UI.toggleNotifPanel(); });\r\n      document.addEventListener('click', e => {\r\n        const panel = document.getElementById('ims-notif-panel');\r\n        if (panel && !panel.contains(e.target) && !notifBtn.contains(e.target)) {\r\n          panel.classList.remove('ims__notif-panel--open');\r\n        }\r\n      });\r\n    }\r\n    window.dispatchEvent(new CustomEvent('ims:ready'));\r\n  }\r\n\r\n  async function boot() {\r\n    UI.init();\r\n\r\n    // Invite / password-reset links redirect here with a Supabase auth\r\n    // token in the URL hash (e.g. #access_token=...&type=invite&...).\r\n    // The client (detectSessionInUrl:true, confirmed default \u2014 no\r\n    // override set in sb()) silently establishes a session from this\r\n    // token. Without this check, a newly invited user would either get\r\n    // dropped straight into the full app with no password ever set (and\r\n    // no way to log back in later), or bounce to a normal login screen\r\n    // with nothing to sign in with \u2014 this intercepts both invite and\r\n    // recovery links first and requires setting a password before\r\n    // proceeding any further.\r\n    const hashParams = new URLSearchParams(location.hash.replace('#', '?'));\r\n    const flowType = hashParams.get('type');\r\n\r\n    // An expired or already-used link comes back as #error=...&error_code=\r\n    // ... Remove it from the address BEFORE connecting (the library would\r\n    // otherwise treat it as a failed sign-in), and say what happened\r\n    // instead of silently showing the login screen.\r\n    if (hashParams.get('error') || hashParams.get('error_code')) {\r\n      history.replaceState(null, '', location.pathname + location.search);\r\n      UI.showLogin();\r\n      showForgotPassword('This link has expired or has already been used. Enter your email below to get a new one.', 'warn');\r\n      return;\r\n    }\r\n\r\n    if (flowType === 'invite' || flowType === 'recovery') {\r\n      // Read the link's temporary login NOW, as the page opens. It used to\r\n      // be read only when \"Set password\" was pressed, by which time another\r\n      // StockMaestro tab or an old stored login could have replaced it,\r\n      // giving \"Auth session missing!\". Keep a copy, so \"Set password\" can\r\n      // put it back if anything replaces it before then.\r\n      const client = sb();\r\n      let session = null;\r\n      if (client) {\r\n        try { const { data } = await client.auth.getSession(); session = data?.session || null; }\r\n        catch (e) { console.warn('[StockMaestro] Could not read the link:', e); }\r\n      }\r\n      _linkSession = session?.access_token\r\n        ? { access_token: session.access_token, refresh_token: session.refresh_token, userId: session.user?.id || null }\r\n        : null;\r\n      history.replaceState(null, '', location.pathname + location.search);\r\n      UI.showSetPassword();\r\n      if (!_linkSession) showLinkProblem();\r\n      return;\r\n    }\r\n\r\n    const hasSession = await AUTH.checkSession();\r\n    if (hasSession) {\r\n      await enterApp();\r\n    } else {\r\n      UI.showLogin();\r\n    }\r\n  }\r\n\r\n  // \u2500\u2500\u2500 INVITE / RESET LINK LOGIN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // The temporary login carried by an invite or reset link, read when the\r\n  // page opens (see boot()).\r\n  let _linkSession = null;\r\n\r\n  // Makes sure the link's login is the current one before the password is\r\n  // set, putting it back if another tab or an old stored login replaced\r\n  // it. Returns true if a usable login is in place.\r\n  async function _ensureLinkSession(client) {\r\n    try {\r\n      const { data } = await client.auth.getSession();\r\n      const current = data?.session || null;\r\n      if (!_linkSession) return !!current;\r\n      if (current && current.user?.id === _linkSession.userId) return true;\r\n      const { data: set, error } = await client.auth.setSession({\r\n        access_token: _linkSession.access_token, refresh_token: _linkSession.refresh_token\r\n      });\r\n      return !error && !!set?.session;\r\n    } catch (e) {\r\n      console.warn('[StockMaestro] Could not restore the link login:', e);\r\n      return false;\r\n    }\r\n  }\r\n\r\n  // Shown on the \"Set new password\" screen when the link cannot be used,\r\n  // instead of the raw \"Auth session missing!\".\r\n  function showLinkProblem() {\r\n    const errEl = document.getElementById('ims-setpw-error');\r\n    if (!errEl) return;\r\n    errEl.innerHTML = 'This link could not be used. It may have expired or already been used. ' +\r\n      '<button type=\"button\" id=\"ims-setpw-newlink\" style=\"background:none;border:0;padding:0;color:inherit;text-decoration:underline;font-weight:600;cursor:pointer\">Request a new link</button>';\r\n    errEl.style.display = 'block';\r\n    document.getElementById('ims-setpw-newlink')?.addEventListener('click', () => {\r\n      UI.showLogin();\r\n      showForgotPassword();\r\n    });\r\n  }\r\n\r\n  // \u2500\u2500\u2500 FORGOT PASSWORD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // \"Forgot password?\" on the login screen. Built here, inside the existing\r\n  // login card and with its existing styles, so no page markup changes are\r\n  // needed. It always gives the same reply, so it cannot be used to find\r\n  // out which email addresses have accounts.\r\n  let _forgotCooldownUntil = 0;\r\n\r\n  function _loginFieldEls() {\r\n    return [\r\n      document.querySelector('label[for=\"ims-login-email\"]'),\r\n      document.getElementById('ims-login-email'),\r\n      document.querySelector('label[for=\"ims-login-password\"]'),\r\n      document.getElementById('ims-login-password'),\r\n      document.getElementById('ims-login-btn'),\r\n      document.getElementById('ims-forgot-link-wrap')\r\n    ].filter(Boolean);\r\n  }\r\n\r\n  function _setForgotMessage(text, kind) {\r\n    const msg = document.getElementById('ims-forgot-msg');\r\n    if (!msg) return;\r\n    if (!text) { msg.style.display = 'none'; msg.textContent = ''; return; }\r\n    const warn = kind === 'warn';\r\n    msg.textContent = text;\r\n    msg.style.cssText = `display:block;margin-bottom:12px;padding:10px 12px;border-radius:8px;font-size:13px;line-height:1.45;` +\r\n      (warn ? 'background:#FEF3C7;color:#92400E;border:1px solid #FCD34D' : 'background:#DCFCE7;color:#166534;border:1px solid #86EFAC');\r\n  }\r\n\r\n  function showForgotPassword(message, kind) {\r\n    const panel = document.getElementById('ims-forgot-panel');\r\n    if (!panel) return;\r\n    _loginFieldEls().forEach(el => { el.style.display = 'none'; });\r\n    const loginErr = document.getElementById('ims-login-error');\r\n    if (loginErr) loginErr.style.display = 'none';\r\n    panel.style.display = 'block';\r\n    const email = document.getElementById('ims-forgot-email');\r\n    const loginEmail = document.getElementById('ims-login-email')?.value?.trim();\r\n    if (email && !email.value && loginEmail) email.value = loginEmail;\r\n    _setForgotMessage(message || '', kind);\r\n    email?.focus();\r\n  }\r\n\r\n  function hideForgotPassword() {\r\n    const panel = document.getElementById('ims-forgot-panel');\r\n    if (panel) panel.style.display = 'none';\r\n    _loginFieldEls().forEach(el => { el.style.display = ''; });\r\n  }\r\n\r\n  function bindForgotPassword() {\r\n    const loginBtn = document.getElementById('ims-login-btn');\r\n    if (!loginBtn || document.getElementById('ims-forgot-panel')) return;\r\n    const linkStyle = 'background:none;border:0;padding:0;font-size:13px;color:#2563EB;cursor:pointer;text-decoration:underline';\r\n\r\n    const wrap = document.createElement('div');\r\n    wrap.id = 'ims-forgot-link-wrap';\r\n    wrap.style.cssText = 'text-align:center;margin-top:12px';\r\n    wrap.innerHTML = `<button type=\"button\" id=\"ims-forgot-link\" style=\"${linkStyle}\">Forgot password?</button>`;\r\n    loginBtn.insertAdjacentElement('afterend', wrap);\r\n\r\n    const panel = document.createElement('div');\r\n    panel.id = 'ims-forgot-panel';\r\n    panel.style.display = 'none';\r\n    panel.innerHTML = `\r\n      <div style=\"font-size:14px;font-weight:600;margin-bottom:6px\">Reset your password</div>\r\n      <div style=\"font-size:13px;opacity:.8;margin-bottom:12px\">Enter your email and we will send you a link to set a new password.</div>\r\n      <div id=\"ims-forgot-msg\" role=\"status\" style=\"display:none\"></div>\r\n      <label class=\"ims__login-label\" for=\"ims-forgot-email\">Email address</label>\r\n      <input class=\"ims__login-input\" type=\"email\" id=\"ims-forgot-email\" autocomplete=\"email\" placeholder=\"your@email.com\">\r\n      <button class=\"ims__login-btn\" id=\"ims-forgot-btn\"><span id=\"ims-forgot-btn-text\">Send reset link</span></button>\r\n      <div style=\"text-align:center;margin-top:12px\"><button type=\"button\" id=\"ims-forgot-back\" style=\"${linkStyle}\">Back to sign in</button></div>`;\r\n    wrap.insertAdjacentElement('afterend', panel);\r\n\r\n    document.getElementById('ims-forgot-link').addEventListener('click', () => showForgotPassword());\r\n    document.getElementById('ims-forgot-back').addEventListener('click', hideForgotPassword);\r\n\r\n    const sendBtn = document.getElementById('ims-forgot-btn');\r\n    const sendText = document.getElementById('ims-forgot-btn-text');\r\n    const emailEl = document.getElementById('ims-forgot-email');\r\n\r\n    function runCooldown() {\r\n      const left = Math.ceil((_forgotCooldownUntil - Date.now()) / 1000);\r\n      if (left > 0) {\r\n        sendBtn.disabled = true;\r\n        sendText.textContent = `Send again in ${left}s`;\r\n        setTimeout(runCooldown, 1000);\r\n      } else {\r\n        sendBtn.disabled = false;\r\n        sendText.textContent = 'Send reset link';\r\n      }\r\n    }\r\n\r\n    async function sendReset() {\r\n      if (Date.now() < _forgotCooldownUntil) return;\r\n      const email = emailEl?.value?.trim() || '';\r\n      if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(email)) {\r\n        _setForgotMessage('Please enter a valid email address.', 'warn');\r\n        return;\r\n      }\r\n      const client = sb();\r\n      if (!client) { _setForgotMessage('Connection error. Please try again.', 'warn'); return; }\r\n      sendBtn.disabled = true;\r\n      sendText.textContent = 'Sending...';\r\n      let error = null;\r\n      try {\r\n        // The link returns to this page, which must be listed in Supabase\r\n        // (Authentication > URL Configuration > Redirect URLs).\r\n        ({ error } = await client.auth.resetPasswordForEmail(email, { redirectTo: location.origin + location.pathname }));\r\n      } catch (e) { error = e; }\r\n      if (error && (error.status === 429 || /rate limit/i.test(error.message || ''))) {\r\n        _setForgotMessage('Too many requests. Please wait a minute and try again.', 'warn');\r\n      } else if (error && !error.status) {\r\n        _setForgotMessage('The email could not be sent. Please check your internet connection and try again.', 'warn');\r\n      } else {\r\n        // Same reply whether or not the email has an account.\r\n        _setForgotMessage('If that email has a StockMaestro account, a link to set a new password is on its way. Please check your inbox, and your spam folder.', 'ok');\r\n      }\r\n      _forgotCooldownUntil = Date.now() + 60000;\r\n      runCooldown();\r\n    }\r\n\r\n    sendBtn.addEventListener('click', sendReset);\r\n    emailEl.addEventListener('keydown', e => { if (e.key === 'Enter') sendReset(); });\r\n  }\r\n\r\n  // \u2500\u2500\u2500 SET PASSWORD HANDLER (invite / reset links) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  function bindSetPassword() {\r\n    const btn     = document.getElementById('ims-setpw-btn');\r\n    const passEl  = document.getElementById('ims-setpw-password');\r\n    const confEl  = document.getElementById('ims-setpw-confirm');\r\n    const errEl   = document.getElementById('ims-setpw-error');\r\n    const btnText = document.getElementById('ims-setpw-btn-text');\r\n\r\n    async function doSetPassword() {\r\n      const pass = passEl?.value;\r\n      const conf = confEl?.value;\r\n      if (errEl) errEl.style.display = 'none';\r\n\r\n      if (!pass || pass.length < 8) {\r\n        if (errEl) { errEl.textContent = 'Password must be at least 8 characters.'; errEl.style.display = 'block'; }\r\n        return;\r\n      }\r\n      if (pass !== conf) {\r\n        if (errEl) { errEl.textContent = 'Passwords do not match.'; errEl.style.display = 'block'; }\r\n        return;\r\n      }\r\n\r\n      if (btn) btn.disabled = true;\r\n      if (btnText) btnText.textContent = 'Setting password...';\r\n\r\n      const client = sb();\r\n      if (!client) {\r\n        if (errEl) { errEl.textContent = 'Connection error \u2014 please try again.'; errEl.style.display = 'block'; }\r\n        if (btn) btn.disabled = false;\r\n        if (btnText) btnText.textContent = 'Set password & continue';\r\n        return;\r\n      }\r\n\r\n      // Make sure the link's own login is the one in use (another tab or an\r\n      // old stored login may have replaced it since the page opened).\r\n      if (!(await _ensureLinkSession(client))) {\r\n        showLinkProblem();\r\n        if (btn) btn.disabled = false;\r\n        if (btnText) btnText.textContent = 'Set password & continue';\r\n        return;\r\n      }\r\n\r\n      const { error } = await client.auth.updateUser({ password: pass });\r\n      if (error && (error.name === 'AuthSessionMissingError' || /session missing/i.test(error.message || ''))) {\r\n        showLinkProblem();\r\n        if (btn) btn.disabled = false;\r\n        if (btnText) btnText.textContent = 'Set password & continue';\r\n        return;\r\n      }\r\n      if (error) {\r\n        if (errEl) { errEl.textContent = error.message || 'Failed to set password. The link may have expired \u2014 ask your administrator to resend the invite.'; errEl.style.display = 'block'; }\r\n        if (btn) btn.disabled = false;\r\n        if (btnText) btnText.textContent = 'Set password & continue';\r\n        return;\r\n      }\r\n\r\n      // Password set successfully \u2014 the session established by the\r\n      // invite/recovery token is still active. Clear the token from the\r\n      // URL first so a subsequent boot() call doesn't re-detect\r\n      // type=invite and loop back to this same screen, then proceed\r\n      // through the normal session flow (which now correctly links the\r\n      // account via loadProfile()'s email fallback) into the app.\r\n      history.replaceState(null, '', location.pathname + location.search);\r\n      _linkSession = null;\r\n      const hasSession = await AUTH.checkSession();\r\n      if (hasSession) {\r\n        await enterApp();\r\n      } else {\r\n        UI.showLogin();\r\n      }\r\n    }\r\n\r\n    btn?.addEventListener('click', doSetPassword);\r\n    confEl?.addEventListener('keydown', e => { if (e.key === 'Enter') doSetPassword(); });\r\n  }\r\n\r\n  // \u2500\u2500\u2500 LOGIN HANDLER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  function bindLogin() {\r\n    const btn     = document.getElementById('ims-login-btn');\r\n    const emailEl = document.getElementById('ims-login-email');\r\n    const passEl  = document.getElementById('ims-login-password');\r\n    const errEl   = document.getElementById('ims-login-error');\r\n    const btnText = document.getElementById('ims-login-btn-text');\r\n\r\n    let _loginAttempts = 0;\r\n    let _loginLockedUntil = 0;\r\n\r\n    async function doLogin() {\r\n      const email = emailEl?.value?.trim();\r\n      const pass  = passEl?.value;\r\n      if (errEl) errEl.style.display = 'none';\r\n\r\n      // Rate limiting \u2014 5 attempts then 60 second lockout\r\n      if (Date.now() < _loginLockedUntil) {\r\n        const secs = Math.ceil((_loginLockedUntil - Date.now()) / 1000);\r\n        if (errEl) { errEl.textContent = `Too many attempts. Try again in ${secs} seconds.`; errEl.style.display = 'block'; }\r\n        return;\r\n      }\r\n\r\n      if (!email || !pass) {\r\n        if (errEl) { errEl.textContent = 'Please enter your email and password.'; errEl.style.display = 'block'; }\r\n        return;\r\n      }\r\n      if (btn) btn.disabled = true;\r\n      if (btnText) btnText.textContent = 'Signing in...';\r\n\r\n      const result = await AUTH.signIn(email, pass);\r\n      if (result.error) {\r\n        _loginAttempts++;\r\n        if (_loginAttempts >= 5) {\r\n          _loginLockedUntil = Date.now() + 60000;\r\n          _loginAttempts = 0;\r\n          if (errEl) { errEl.textContent = 'Too many failed attempts. Please wait 60 seconds.'; errEl.style.display = 'block'; }\r\n        } else {\r\n          if (errEl) { errEl.textContent = result.error; errEl.style.display = 'block'; }\r\n        }\r\n        if (btn) btn.disabled = false;\r\n        if (btnText) btnText.textContent = 'Sign in';\r\n      } else {\r\n        // A Driver goes straight to Driver mode: never the office screens.\r\n        if (AUTH.user?.role === 'driver') { await enterDriverMode(); return; }\r\n        const licenceStatus = await checkLicence();\r\n        if (licenceStatus === 'suspended' || licenceStatus === 'expired') return;\r\n        UI.showApp();\r\n        // Signing in here does not go through enterApp(), so the session\r\n        // keeper (which prevents \"JWT expired\" errors) is started here too.\r\n        startSessionKeeper();\r\n        UI.showPanel(UI.canOpenPanel('dashboard') ? 'dashboard' : (UI.firstAllowedPanel() || 'dashboard'));\r\n        keepAlive();\r\n        startEmailQueue();\r\n        if (AUTH.company?.id) {\r\n          const client = sb();\r\n    if (client) await client.rpc('check_claim_alerts');\r\n          UI.loadNotifications();\r\n        }\r\n        window.dispatchEvent(new CustomEvent('ims:ready'));\r\n      }\r\n    }\r\n\r\n    btn?.addEventListener('click', doLogin);\r\n    passEl?.addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); });\r\n    document.getElementById('ims-logout-btn')?.addEventListener('click', () => AUTH.signOut());\r\n  }\r\n\r\n  // \u2500\u2500\u2500 EXPOSE GLOBALS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // \u2500\u2500\u2500 AUTOMATED CUSTOMER EMAILS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const EMAIL = {\r\n    // Marketplaces run buyer communication through their own messaging\r\n    // systems and hand us a masked relay address, so emailing those buyers\r\n    // directly is both useless and a policy risk. Only our own direct\r\n    // customers get automated mail.\r\n    EXCLUDED_PLATFORMS: [\r\n      'amazon uk', 'amazon', 'ebay uk', 'ebay', 'temu',\r\n      'tiktok shop', 'tiktok', 'tesco', 'b&q / tradepoint', 'b&q'\r\n    ],\r\n\r\n    isValidEmail(v) {\r\n      return typeof v === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(v.trim());\r\n    },\r\n\r\n    async send(orderId, type) {\r\n      try {\r\n        const client = window.supabase.createClient(window.IMS_SUPABASE_URL, window.IMS_SUPABASE_ANON_KEY);\r\n\r\n        const { data: order } = await client.from('orders')\r\n          .select('*, platforms(name), dropship_customers(business_name,contact_name,email), order_items(product_name,sku,quantity,unit_price,total_price), shipments(is_return_collection,tracking_number,couriers(name,tracking_url_base,requires_postcode,postcode_param))')\r\n          .eq('id', orderId).maybeSingle();\r\n\r\n        if (!order) return this._log(orderId, type, null, 'skipped', 'Order not found');\r\n\r\n        const isDropship = order.fulfillment_type === 'dropship';\r\n        const platLower  = (order.platforms?.name || '').toLowerCase().trim();\r\n        const isWebsite  = platLower.includes('website') || platLower.includes('own store');\r\n\r\n        // On a dropship order the recipient is the TRADE PARTNER (e.g. Happy\r\n        // Beds), never their end customer \u2014 we ship on their behalf and the\r\n        // end customer has no relationship with us.\r\n        const recipient = isDropship ? order.dropship_customers?.email : order.email;\r\n\r\n        // Marketplace exclusion applies to our own direct orders only \u2014 a\r\n        // dropship order's platform is irrelevant since the partner is the\r\n        // recipient regardless.\r\n        if (!isDropship && this.EXCLUDED_PLATFORMS.includes(platLower)) {\r\n          return this._log(orderId, type, null, 'skipped', `Marketplace order (${order.platforms?.name}) \u2014 buyer is contacted via the marketplace`);\r\n        }\r\n\r\n        // Website orders: Shopify already emails the customer an order\r\n        // confirmation with the website number, so ours is not sent (two\r\n        // confirmations with different numbers confused customers). The\r\n        // \"on its way\" and \"delivered\" emails still go, as Shopify does not\r\n        // know about those.\r\n        if (!isDropship && type === 'confirmed' && isWebsite && this._refs(order).fromWebsite) {\r\n          return this._log(orderId, type, null, 'skipped', 'Website order: Shopify sends the order confirmation');\r\n        }\r\n\r\n        if (!this.isValidEmail(recipient)) {\r\n          return this._log(orderId, type, null, 'skipped',\r\n            isDropship\r\n              ? 'No valid email address on the dropship partner record'\r\n              : 'No valid customer email address on this order');\r\n        }\r\n\r\n        const { data: already } = await client.from('email_log')\r\n          .select('id').eq('order_id', orderId).eq('email_type', type).eq('status', 'sent').maybeSingle();\r\n        if (already) return { skipped: true, reason: 'already sent' };\r\n\r\n        const tpl = this.buildTemplate(type, order, AUTH.company);\r\n        if (!tpl) return this._log(orderId, type, recipient, 'skipped', `Unknown email type: ${type}`);\r\n\r\n        // Website orders send from hello@fatsofa.co.uk via the Edge\r\n        // Function's separate Fat Sofa SMTP credentials; everything else\r\n        // (wholesale, dropship) keeps using the original Gmail sender \u2014\r\n        // the function defaults to that when `sender` is omitted.\r\n        const body = { to: recipient.trim(), subject: tpl.subject, html: tpl.html };\r\n        if (!isDropship && isWebsite) body.sender = 'fatsofa';\r\n\r\n        const res = await fetch(`${window.IMS_SUPABASE_URL}/functions/v1/send-order-email`, {\r\n          method: 'POST',\r\n          headers: {\r\n            'Content-Type': 'application/json',\r\n            'Authorization': `Bearer ${window.IMS_SUPABASE_ANON_KEY}`\r\n          },\r\n          body: JSON.stringify(body)\r\n        });\r\n        const out = await res.json().catch(() => ({}));\r\n\r\n        if (!res.ok || !out.success) {\r\n          const msg = out.error || `HTTP ${res.status}`;\r\n          await this._log(orderId, type, recipient, 'failed', msg);\r\n          UI.toast(`Order email (${type}) failed: ${msg}`, 'error', 8000);\r\n          return { sent: false, error: msg };\r\n        }\r\n\r\n        await this._log(orderId, type, recipient, 'sent', null, tpl.subject);\r\n        UI.toast(`Email sent to ${recipient}`, 'success', 4000);\r\n        return { sent: true };\r\n      } catch (err) {\r\n        // Never let an email failure break the order flow that called it.\r\n        console.error('[EMAIL.send]', err);\r\n        await this._log(orderId, type, null, 'failed', String(err?.message || err));\r\n        return { sent: false, error: String(err) };\r\n      }\r\n    },\r\n\r\n    async _log(orderId, type, recipient, status, errorMessage, subject) {\r\n      try {\r\n        await DB.insert('email_log', {\r\n          order_id: orderId,\r\n          email_type: type,\r\n          recipient: recipient || null,\r\n          subject: subject || null,\r\n          status: status,\r\n          error_message: errorMessage || null\r\n        });\r\n      } catch (e) { console.warn('[EMAIL._log]', e); }\r\n      return { sent: status === 'sent', skipped: status === 'skipped', reason: errorMessage };\r\n    },\r\n\r\n    _shell(company, heading, introHtml, bodyHtml) {\r\n      const esc = v => UTIL.sanitize(String(v ?? ''));\r\n      // Only a logo stored as a web link is used. A logo stored as the image\r\n      // itself (a \"data:\" address) is blocked by most email programs, which\r\n      // then show the raw code, and it makes every email very large; the\r\n      // company name is shown instead until the logo is uploaded again.\r\n      const logo = /^https?:\\/\\//i.test(company?.logo_url || '')\r\n        ? `<img src=\"${esc(company.logo_url)}\" alt=\"${esc(company.name)}\" style=\"height:44px;max-width:180px;object-fit:contain\">`\r\n        : `<div style=\"font-size:20px;font-weight:800;color:#0F172A\">${esc(company?.name || 'MDF London')}</div>`;\r\n      return `\r\n<div style=\"margin:0;padding:24px 12px;background:#F8FAFC;font-family:'Helvetica Neue',Arial,sans-serif;color:#0F172A\">\r\n  <div style=\"max-width:600px;margin:0 auto;background:#FFFFFF;border:1px solid #E7ECF3;border-radius:14px;overflow:hidden\">\r\n    <div style=\"padding:22px 28px;border-bottom:1px solid #E7ECF3\">${logo}</div>\r\n    <div style=\"padding:28px\">\r\n      <h1 style=\"margin:0 0 10px;font-size:21px;font-weight:800;letter-spacing:-0.02em\">${heading}</h1>\r\n      <div style=\"font-size:14px;line-height:1.65;color:#475569\">${introHtml}</div>\r\n      ${bodyHtml}\r\n    </div>\r\n    <div style=\"padding:20px 28px;border-top:1px solid #E7ECF3;background:#F8FAFC;font-size:12px;line-height:1.6;color:#64748B\">\r\n      <strong style=\"color:#0F172A\">${esc(company?.name || '')}</strong><br>\r\n      ${company?.address ? esc(company.address).replace(/\\n/g, '<br>') + '<br>' : ''}\r\n      ${company?.postcode ? esc(company.postcode) + '<br>' : ''}\r\n      ${company?.email ? `<a href=\"mailto:${esc(company.email)}\" style=\"color:#2563EB;text-decoration:none\">${esc(company.email)}</a>` : ''}\r\n      ${company?.phone ? ' &nbsp;\u00B7&nbsp; ' + esc(company.phone) : ''}\r\n      ${company?.vat_number ? `<br>VAT: ${esc(company.vat_number)}` : ''}\r\n    </div>\r\n  </div>\r\n  <div style=\"max-width:600px;margin:12px auto 0;text-align:center;font-size:11px;color:#94A3B8\">\r\n    This is an automated message about your order \u2014 please do not reply to this address.\r\n  </div>\r\n</div>`;\r\n    },\r\n\r\n    _itemsTable(order) {\r\n      const esc = v => UTIL.sanitize(String(v ?? ''));\r\n      const items = order.order_items || [];\r\n      if (!items.length) return '';\r\n      return `\r\n      <table style=\"width:100%;border-collapse:collapse;margin:20px 0;font-size:13px\">\r\n        <thead>\r\n          <tr>\r\n            <th style=\"text-align:left;padding:8px 0;border-bottom:2px solid #E7ECF3;font-size:11px;text-transform:uppercase;letter-spacing:0.04em;color:#94A3B8\">Item</th>\r\n            <th style=\"text-align:center;padding:8px 0;border-bottom:2px solid #E7ECF3;font-size:11px;text-transform:uppercase;letter-spacing:0.04em;color:#94A3B8\">Qty</th>\r\n            <th style=\"text-align:right;padding:8px 0;border-bottom:2px solid #E7ECF3;font-size:11px;text-transform:uppercase;letter-spacing:0.04em;color:#94A3B8\">Price</th>\r\n          </tr>\r\n        </thead>\r\n        <tbody>\r\n          ${items.map(i => `\r\n          <tr>\r\n            <td style=\"padding:10px 0;border-bottom:1px solid #F1F5F9\">\r\n              <div style=\"font-weight:600\">${esc(i.product_name)}</div>\r\n              ${i.sku ? `<div style=\"font-size:11px;color:#94A3B8\">${esc(i.sku)}</div>` : ''}\r\n            </td>\r\n            <td style=\"padding:10px 0;border-bottom:1px solid #F1F5F9;text-align:center\">${esc(i.quantity)}</td>\r\n            <td style=\"padding:10px 0;border-bottom:1px solid #F1F5F9;text-align:right\">${UTIL.formatCurrency(i.total_price || 0)}</td>\r\n          </tr>`).join('')}\r\n          <tr>\r\n            <td colspan=\"2\" style=\"padding:12px 0;text-align:right;font-weight:700\">Order total</td>\r\n            <td style=\"padding:12px 0;text-align:right;font-weight:800;font-size:16px\">${UTIL.formatCurrency(order.total_value || 0)}</td>\r\n          </tr>\r\n        </tbody>\r\n      </table>`;\r\n    },\r\n\r\n    _addressBlock(order) {\r\n      const esc = v => UTIL.sanitize(String(v ?? ''));\r\n      const isDropship = order.fulfillment_type === 'dropship';\r\n      const name     = isDropship ? (order.end_customer_name || order.customer_name) : order.customer_name;\r\n      const postcode = isDropship ? (order.end_postcode || order.postcode) : order.postcode;\r\n      if (!order.address && !postcode) return '';\r\n      return `\r\n      <div style=\"margin:18px 0;padding:14px 16px;background:#F8FAFC;border:1px solid #E7ECF3;border-radius:10px\">\r\n        <div style=\"font-size:11px;text-transform:uppercase;letter-spacing:0.04em;color:#94A3B8;font-weight:700;margin-bottom:6px\">${isDropship ? 'Delivering to' : 'Delivery address'}</div>\r\n        <div style=\"font-size:13px;line-height:1.6\">\r\n          ${esc(name)}<br>\r\n          ${order.address ? esc(order.address).replace(/\\n/g, '<br>') + '<br>' : ''}\r\n          ${esc(postcode || '')}\r\n        </div>\r\n      </div>`;\r\n    },\r\n\r\n    // The order number the recipient knows, for subjects and messages:\r\n    //  - a website order: the website number, e.g. #1034 (the number in the\r\n    //    customer's Shopify confirmation);\r\n    //  - a dropship order (email to the partner): their own reference, with\r\n    //    ours alongside;\r\n    //  - anything taken directly in StockMaestro: our ORD number.\r\n    // Our number is always added as a small \"Our reference\" line when the\r\n    // main one differs, so a reply can be matched straight away.\r\n    _refs(order) {\r\n      const ours = order.order_number || '';\r\n      const ext = String(order.external_ref || '').trim();\r\n      const hasExt = !!ext && ext.replace(/^#/, '') !== ours;\r\n      const plat = (order.platforms?.name || '').toLowerCase();\r\n      const isWebsite = plat.includes('website') || plat.includes('own store');\r\n      if (order.fulfillment_type === 'dropship' && hasExt) {\r\n        return { main: `${ext} (our ref ${ours})`, ours, footer: false, fromWebsite: false };\r\n      }\r\n      if (isWebsite && hasExt) {\r\n        return { main: ext.startsWith('#') ? ext : `#${ext}`, ours, footer: true, fromWebsite: true };\r\n      }\r\n      return { main: ours, ours, footer: false, fromWebsite: false };\r\n    },\r\n\r\n    buildTemplate(type, order, company) {\r\n      const esc = v => UTIL.sanitize(String(v ?? ''));\r\n      const isDropship = order.fulfillment_type === 'dropship';\r\n      const R = this._refs(order);\r\n      const ref = esc(R.main);\r\n      const ourRef = R.footer\r\n        ? `<p style=\"margin:18px 0 0;font-size:11px;color:#94A3B8\">Our reference: ${esc(R.ours)}</p>` : '';\r\n      const shipment = (order.shipments || []).find(x => !x.is_return_collection);   // the delivery, never a return collection\r\n      const courier = shipment?.couriers;\r\n      const tracking = shipment?.tracking_number;\r\n\r\n      // Dropship mail goes to the trade partner about THEIR customer's order,\r\n      // so the tone is a B2B status notification, not a \"your parcel is\r\n      // coming\" message addressed to a shopper.\r\n      const greetName = isDropship\r\n        ? ((order.dropship_customers?.contact_name || '').trim().split(/\\s+/)[0]\r\n            || order.dropship_customers?.business_name || 'there')\r\n        : ((order.customer_name || '').trim().split(/\\s+/)[0] || 'there');\r\n      const endCustomer = esc(order.end_customer_name || order.customer_name || 'your customer');\r\n\r\n      if (type === 'confirmed') {\r\n        return {\r\n          subject: isDropship\r\n            ? `Dropship order received: ${R.main}`\r\n            : `Order confirmed: ${R.main}`,\r\n          html: this._shell(company,\r\n            isDropship ? 'Dropship order received' : 'Thanks for your order',\r\n            isDropship\r\n              ? `<p style=\"margin:0\">Hi ${esc(greetName)}, we've received dropship order <strong>${ref}</strong> for <strong>${endCustomer}</strong> and it's now being prepared. We'll confirm once it has been dispatched.</p>`\r\n              : `<p style=\"margin:0\">Hi ${esc(greetName)}, we've received your order <strong>${ref}</strong> and it's now being prepared. We'll email you again as soon as it's on its way.</p>`,\r\n            this._itemsTable(order) + this._addressBlock(order) + ourRef\r\n          )\r\n        };\r\n      }\r\n\r\n      if (type === 'dispatched') {\r\n        const trackUrl = courier && tracking ? UTIL.buildTrackingUrl(courier, tracking, order.postcode || order.end_postcode) : null;\r\n        const trackBlock = tracking ? `\r\n          <div style=\"margin:20px 0;padding:16px 18px;background:#EFF4FF;border:1px solid #BFDBFE;border-radius:10px\">\r\n            <div style=\"font-size:11px;text-transform:uppercase;letter-spacing:0.04em;color:#2563EB;font-weight:700;margin-bottom:6px\">Tracking</div>\r\n            <div style=\"font-size:13px;margin-bottom:4px\">Courier: <strong>${esc(courier?.name || 'Courier')}</strong></div>\r\n            <div style=\"font-size:15px;font-family:monospace;font-weight:700;letter-spacing:0.02em\">${esc(tracking)}</div>\r\n            ${trackUrl ? `<a href=\"${esc(trackUrl)}\" style=\"display:inline-block;margin-top:12px;padding:10px 18px;background:#2563EB;color:#FFFFFF;text-decoration:none;border-radius:8px;font-size:13px;font-weight:600\">${isDropship ? 'Track this shipment' : 'Track your parcel'}</a>` : ''}\r\n          </div>` : '';\r\n        return {\r\n          subject: isDropship\r\n            ? `Dispatched: ${R.main} for ${order.end_customer_name || 'your customer'}`\r\n            : `Your order is on its way: ${R.main}`,\r\n          html: this._shell(company,\r\n            isDropship ? 'Dropship order dispatched' : 'Your order has been dispatched',\r\n            isDropship\r\n              ? `<p style=\"margin:0\">Hi ${esc(greetName)}, order <strong>${ref}</strong> for <strong>${endCustomer}</strong> has been dispatched from our warehouse. The tracking details below can be passed on to your customer.</p>`\r\n              : `<p style=\"margin:0\">Hi ${esc(greetName)}, good news \u2014 order <strong>${ref}</strong> has left our warehouse and is on its way to you.</p>`,\r\n            trackBlock + this._itemsTable(order) + this._addressBlock(order) + ourRef\r\n          )\r\n        };\r\n      }\r\n\r\n      if (type === 'delivered') {\r\n        return {\r\n          subject: isDropship\r\n            ? `Delivered: ${R.main} for ${order.end_customer_name || 'your customer'}`\r\n            : `Your order has been delivered: ${R.main}`,\r\n          html: this._shell(company,\r\n            isDropship ? 'Dropship order delivered' : 'Your order has been delivered',\r\n            isDropship\r\n              ? `<p style=\"margin:0\">Hi ${esc(greetName)}, order <strong>${ref}</strong> for <strong>${endCustomer}</strong> has been delivered.</p>\r\n                 <p style=\"margin:12px 0 0\">If your customer reports any issue with the delivery, contact us at ${company?.email ? `<a href=\"mailto:${esc(company.email)}\" style=\"color:#2563EB\">${esc(company.email)}</a>` : 'our usual address'} and we'll look into it.</p>`\r\n              : `<p style=\"margin:0\">Hi ${esc(greetName)}, your order <strong>${ref}</strong> has been marked as delivered. We hope everything arrived in perfect condition.</p>\r\n                 <p style=\"margin:12px 0 0\">If anything isn't right, just reply to ${company?.email ? `<a href=\"mailto:${esc(company.email)}\" style=\"color:#2563EB\">${esc(company.email)}</a>` : 'us'} and we'll sort it out.</p>`,\r\n            this._itemsTable(order) + ourRef\r\n          )\r\n        };\r\n      }\r\n\r\n      if (type === 'review_request') {\r\n        return {\r\n          subject: `How did we do? ${R.main}`,\r\n          html: this._shell(company,\r\n            'How did we do?',\r\n            `<p style=\"margin:0\">Hi ${esc(greetName)}, your order <strong>${ref}</strong> was delivered a few days ago and we hope you're happy with it.</p>\r\n             <p style=\"margin:12px 0 0\">If you have a moment, we'd really appreciate a quick review \u2014 it genuinely helps a small business like ours.</p>`,\r\n            `<div style=\"margin:22px 0;text-align:center\">\r\n               ${company?.review_url ? `<a href=\"${esc(company.review_url)}\" style=\"display:inline-block;padding:12px 24px;background:#2563EB;color:#FFFFFF;text-decoration:none;border-radius:8px;font-size:14px;font-weight:600\">Leave a review</a>`\r\n                 : `<p style=\"margin:0;font-size:13px;color:#64748B\">Simply reply to this email with your feedback.</p>`}\r\n             </div>\r\n             <p style=\"margin:0;font-size:13px;color:#64748B\">And if something wasn't right, please tell us first \u2014 we'd much rather fix it.</p>`\r\n          )\r\n        };\r\n      }\r\n\r\n      return null;\r\n    }\r\n  };\r\n\r\n  // \u2500\u2500\u2500 SHOPIFY WRITE-BACK \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  // Tells Shopify what happened to a website order, through the\r\n  // shopify-sync Edge Function: 'fulfil' (dispatched), 'tracking'\r\n  // (tracking added or changed), 'unfulfil' (taken back from Dispatched),\r\n  // 'cancel' (cancelled: restocked in Shopify, no refund).\r\n  // Fire and forget: callers do not wait, so it never slows or breaks the\r\n  // action. The function ignores orders that are not Shopify orders. Until\r\n  // the function or its app keys are set up, nothing happens. A failure is\r\n  // shown here, logged on the order and rings the bell.\r\n  const SHOPIFY = {\r\n    async sync(action, orderIds) {\r\n      const ids = [...new Set((orderIds || []).filter(Boolean))];\r\n      if (!ids.length) return null;\r\n      try {\r\n        const client = sb();\r\n        if (!client) return null;\r\n        const { data: got } = await client.auth.getSession();\r\n        const token = got?.session?.access_token;\r\n        if (!token) return null;\r\n        const r = await fetch(`${window.IMS_SUPABASE_URL}/functions/v1/shopify-sync`, {\r\n          method: 'POST',\r\n          headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, 'apikey': window.IMS_SUPABASE_ANON_KEY },\r\n          body: JSON.stringify({ action, order_ids: ids })\r\n        });\r\n        if (r.status === 404) return null;   // function not deployed yet\r\n        const out = await r.json().catch(() => ({}));\r\n        if (!r.ok) { console.warn('[shopify-sync]', out.error || r.status); return null; }\r\n        const failed = (out.results || []).filter(x => x.status === 'failed');\r\n        if (failed.length) {\r\n          UI.toast(`Shopify was not updated for ${failed.map(x => x.order_number).join(', ')}: ${failed[0].detail}`, 'warning', 10000);\r\n        }\r\n        // Cancelling never refunds automatically: remind staff to do it.\r\n        const cancelled = (out.results || []).filter(x => x.status === 'done' && action === 'cancel');\r\n        if (cancelled.length) {\r\n          UI.toast(`${cancelled.map(x => x.order_number).join(', ')} also cancelled in Shopify (restocked). If the customer paid, refund them in Shopify.`, 'info', 12000);\r\n        }\r\n        return out;\r\n      } catch (e) {\r\n        console.warn('[shopify-sync]', e);\r\n        return null;\r\n      }\r\n    }\r\n  };\r\n\r\n  window.SM = { IMS, AUTH, DB, UI, UTIL, EMAIL, DRIVER, SHOPIFY };\r\n  window.IMS = window.SM;\r\n\r\n  // \u2500\u2500\u2500 INIT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  if (document.readyState === 'loading') {\r\n    document.addEventListener('DOMContentLoaded', () => { bindLogin(); bindForgotPassword(); bindSetPassword(); boot(); });\r\n  } else {\r\n    bindLogin(); bindForgotPassword(); bindSetPassword(); boot();\r\n  }\r\n\r\n})();\r\n"],
  "mappings": "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",
  "names": ["error"]
}
