1import{s as o,g as f}from"./index-CQeJlwfi.js";import{r as p}from"./BottomNav-BW-QI8om.js";import"./supabase-DopYWMZw.js";import"./formatters-CGO5DC6w.js";import"./toast-0DDrSrA7.js";(function(){try{var c=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{};c.SENTRY_RELEASE={id:"280815f9a48432bd"};var e=new c.Error().stack;e&&(c._sentryDebugIds=c._sentryDebugIds||{},c._sentryDebugIds[e]="45ab7a6e-c8aa-411d-a419-71602c17a998",c._sentryDebugIdIdentifier="sentry-dbid-45ab7a6e-c8aa-411d-a419-71602c17a998")}catch{}})();class g{static async create(e){const{data:t,error:r}=await o.from("call_records").insert(e).select().single();return r?{callRecord:null,error:r}:{callRecord:t,error:null}}static async getAll(e){return await this.list(e,{limit:null})}static async list(e,t={}){const{orderBy:r="started_at",ascending:a=!1,limit:n=null,offset:s=0,direction:l=null,status:d=null}=t;let i=o.from("call_records").select("*, contacts(name, phone_number)").eq("user_id",e);l&&(i=i.eq("direction",l)),d&&(i=i.eq("status",d)),i=i.order(r,{ascending:a}),n&&(i=i.range(s,s+n-1));const{data:m,error:u}=await i;return u?{callRecords:[],error:u}:{callRecords:m,error:null}}static async getById(e){const{data:t,error:r}=await o.from("call_records").select("*, contacts(name, phone_number)").eq("id",e).single();return r?{callRecord:null,error:r}:{callRecord:t,error:null}}static async getByCallSid(e){let{data:t,error:r}=await o.from("call_records").select("*").eq("vendor_call_id",e).limit(1);return(!t||t.length===0)&&(r==null?void 0:r.code)!=="PGRST116"&&({data:t,error:r}=await o.from("call_records").select("*").eq("call_sid",e).limit(1)),r&&r.code!=="PGRST116"?{callRecord:null,error:r}:{callRecord:t&&t.length>0?t[0]:null,error:null}}static async update(e,t){const{data:r,error:a}=await o.from("call_records").update(t).eq("id",e).select().single();return a?{callRecord:null,error:a}:{callRecord:r,error:null}}static async updateByCallSid(e,t){const{data:r,error:a}=await o.from("call_records").update(t).or(`vendor_call_id.eq.${e},call_sid.eq.${e}`).select().limit(1);return a?{callRecord:null,error:a}:{callRecord:r&&r.length>0?r[0]:null,error:null}}static async getByContact(e,t={}){const{limit:r=null,offset:a=0}=t;let n=o.from("call_records").select("*").eq("contact_id",e).order("started_at",{ascending:!1});r&&(n=n.range(a,a+r-1));const{data:s,error:l}=await n;return l?{callRecords:[],error:l}:{callRecords:s,error:null}}static async getRecent(e,t=10){return await this.list(e,{orderBy:"started_at",ascending:!1,limit:t})}static async getMissed(e){const{data:t,error:r}=await o.from("call_records").select("*, contacts(name, phone_number)").eq("user_id",e).in("status",["no-answer","failed"]).order("started_at",{ascending:!1});return r?{callRecords:[],error:r}:{callRecords:t,error:null}}static async getWithRecordings(e){const{data:t,error:r}=await o.from("call_records").select("*, contacts(name, phone_number)").eq("user_id",e).eq("status","completed").not("recording_url","is",null).order("started_at",{ascending:!1});return r?{callRecords:[],error:r}:{callRecords:t,error:null}}static async searchTranscripts(e,t){const{data:r,error:a}=await o.from("call_records").select("*, contacts(name, phone_number)").eq("user_id",e).ilike("transcript",`%${t}%`).order("started_at",{ascending:!1});return a?{callRecords:[],error:a}:{callRecords:r,error:null}}static async delete(e){const{error:t}=await o.from("call_records").delete().eq("id",e);return{error:t}}static async getStats(e){const{data:t,error:r}=await o.from("call_records").select("status, duration_seconds").eq("user_id",e);return r?{stats:null,error:r}:{stats:{total:t.length,completed:t.filter(n=>n.status==="completed").length,missed:t.filter(n=>["no-answer","failed"].includes(n.status)).length,totalDuration:t.reduce((n,s)=>n+(s.duration_seconds||0),0)},error:null}}}class w{constructor(){this.calls=[],this.currentFilter="all"}async render(){const{user:e}=await f();if(!e){navigateTo("/login");return}const{callRecords:t}=await g.list(e.id,{orderBy:"started_at",ascending:!1});this.calls=t;const r=document.getElementById("app");r.innerHTML=` 2 <div class="container with-bottom-nav" style="padding-top: 1.5rem;"> 3 <button onclick="navigateTo('/settings')" class="back-btn" style="margin-bottom: 1rem;"> 4 <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 5 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> 6 </svg> 7 Back 8 </button> 9 <h1>Call History</h1> 10 11 <div class="card" style="margin-bottom: 1rem;"> 12 <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> 13 <button class="btn btn-secondary filter-btn active" data-filter="all"> 14 All Calls 15 </button> 16 <button class="btn btn-secondary filter-btn" data-filter="inbound"> 17 Inbound 18 </button> 19 <button class="btn btn-secondary filter-btn" data-filter="outbound"> 20 Outbound 21 </button> 22 <button class="btn btn-secondary filter-btn" data-filter="completed"> 23 Completed 24 </button> 25 <button class="btn btn-secondary filter-btn" data-filter="missed"> 26 Missed 27 </button> 28 </div> 29 </div> 30 31 <div class="card"> 32 <div id="calls-list"> 33 ${this.renderCallsList()} 34 </div> 35 </div> 36 37 <!-- Call Details Modal --> 38 <div id="call-modal" class="modal hidden"> 39 <div class="modal-content card" style="max-width: 600px; margin: 2rem auto;"> 40 <div id="call-details"></div> 41 <button class="btn btn-secondary btn-full mt-3" id="close-modal-btn"> 42 Close 43 </button> 44 </div> 45 </div> 46 </div> 47 ${p("/settings")} 48 `,this.attachEventListeners()}renderCallsList(){let e=this.calls;
48switch(this.currentFilter){case"inbound":e=this.calls.filter(t=>t.direction==="inbound");break;case"outbound":e=this.calls.filter(t=>t.direction==="outbound");break;case"completed":e=this.calls.filter(t=>t.status==="completed");break;case"missed":e=this.calls.filter(t=>["no-answer","failed"].includes(t.status));break}return e.length===0?'<p class="text-muted text-center">No calls found</p>':e.map(t=>{var r,a;return` 49 <div class="call-item" data-id="${t.id}" style=" 50 padding: 1rem; 51 border-bottom: 1px solid var(--border-color); 52 cursor: pointer; 53 transition: background-color 0.2s; 54 " onmouseover="this.style.backgroundColor='var(--bg-secondary)'" 55 onmouseout="this.style.backgroundColor='transparent'"> 56 <div style="display: flex; justify-content: space-between; align-items: start;"> 57 <div style="flex: 1;"> 58 <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;"> 59 <span style="font-size: 1.5rem;"> 60 ${t.direction==="inbound"?"ð":"ð±"} 61 </span> 62 <div> 63 <div style="font-weight: 600;"> 64 ${((r=t.contacts)==null?void 0:r.name)||((a=t.contacts)==null?void 0:a.phone_number)||"Unknown"} 65 </div> 66 <div class="text-sm text-muted"> 67 ${t.direction} ⢠${t.status} 68 ${t.duration_seconds?` ⢠${this.formatDuration(t.duration_seconds)}`:""} 69 </div> 70 </div> 71 </div> 72 ${t.transcript?` 73 <div class="text-sm text-muted" style="margin-top: 0.5rem; font-style: italic;"> 74 "${t.transcript.substring(0,100)}${t.transcript.length>100?"...":""}" 75 </div> 76 `:""} 77 </div> 78 <div class="text-sm text-muted" style="text-align: right;"> 79 ${this.formatDateTime(t.started_at)} 80 </div> 81 </div> 82 </div> 83 `}).join("")}formatDuration(e){const t=Math.floor(e/60),r=e%60;return`${t}:${r.toString().padStart(2,"0")}`}formatDateTime(e){const t=new Date(e),a=new Date-t,n=Math.floor(a/6e4),s=Math.floor(a/36e5),l=Math.floor(a/864e5);return n<60?`${n} min ago`:s<24?`${s} hour${s>1?"s":""} ago`:l<7?`${l} day${l>1?"s":""} ago`:t.toLocaleDateString()}attachEventListeners(){const e=document.getElementById("call-modal"),t=document.getElementById("close-modal-btn");document.querySelectorAll(".filter-btn").forEach(r=>{r.addEventListener("click",a=>{document.querySelectorAll(".filter-btn").forEach(n=>n.classList.remove("active")),a.target.classList.add("active"),this.currentFilter=a.target.dataset.filter,document.getElementById("calls-list").innerHTML=this.renderCallsList(),this.attachCallItemListeners()})}),t.addEventListener("click",()=>{e.classList.add("hidden")}),this.attachCallItemListeners()}attachCallItemListeners(){const e=document.querySelectorAll(".call-item");console.log(`Found ${e.length} call items to attach listeners to`),e.forEach(t=>{t.addEventListener("click",r=>{const a=r.currentTarget.dataset.id;console.log("Call item clicked, ID:",a),this.showCallDetails(a)})})}async showCallDetails(e){var s,l;console.log("showCallDetails called with ID:",e);const{callRecord:t}=await g.getById(e);if(console.log("Retrieved call record:",t),!t){console.error("No call record found for ID:",e);return}const r=document.getElementById("call-modal"),a=document.getElementById("call-details");a.innerHTML=` 84 <h2>Call Details</h2> 85 86 <div style="margin-top: 1rem;"> 87 <div class="form-group"> 88 <strong>Contact:</strong> ${((s=t.contacts)==null?void 0:s.name)||"Unknown"} 89 </div> 90 91 <div class="form-group"> 92 <strong>Phone:</strong> ${((l=t.contacts)==null?void 0:l.phone_number)||"N/A"} 93 </div> 94 95 <div class="form-group"> 96 <strong>Direction:</strong> 97 <span style="text-transform: capitalize;">${t.direction}</span> 98 </div> 99 100 <div class="form-group"> 101 <strong>Status:</strong>
102 <span style="text-transform: capitalize;">${t.status}</span> 103 </div> 104 105 <div class="form-group"> 106 <strong>Started:</strong> ${new Date(t.started_at).toLocaleString()} 107 </div> 108 109 ${t.duration_seconds?` 110 <div class="form-group"> 111 <strong>Duration:</strong> ${this.formatDuration(t.duration_seconds)} 112 </div> 113 `:""} 114 115 ${t.recordings&&t.recordings.length>0||t.recording_url?` 116 <div class="form-group"> 117 <strong>Recordings:</strong><br> 118 <div id="recordings-container" style="margin-top: 0.5rem;"> 119 <span style="color: var(--text-muted);">Loading recordings...</span> 120 </div> 121 </div> 122 `:""} 123 124 ${t.transcript?` 125 <div class="form-group"> 126 <strong>Transcript:</strong> 127 <div class="card" style="margin-top: 0.5rem; background: var(--bg-secondary); white-space: pre-wrap;"> 128 ${t.transcript} 129 </div> 130 </div> 131 `:""} 132 </div> 133 `,r.classList.remove("hidden");const n=t.recordings||[];n.length===0&&t.recording_url&&n.push({url:t.recording_url,label:"main",duration:t.duration_seconds}),n.length>0?(console.log("Loading recordings:",n),setTimeout(()=>{this.loadRecordings(n)},100)):console.log("No recordings found for this call")}formatRecordingLabel(e){return{main:"Conversation",transfer_conference:"Transfer Call",transferee_consult:"Transferred Call",reconnect_after_decline:"Reconnect After Decline",reconnect_to_agent:"Reconnect",back_to_agent:"Back to Agent"}[e]||e.replace(/_/g," ").replace(/\b\w/g,r=>r.toUpperCase())}loadRecordings(e){const t=document.getElementById("recordings-container");if(!t){console.error("recordings-container not found in DOM");return}t.innerHTML=e.map((r,a)=>` 134 <div class="recording-item" style="margin-bottom: 1rem; padding: 0.75rem; background: var(--bg-secondary); border-radius: 8px;"> 135 <div style="font-weight: 500; margin-bottom: 0.5rem; color: var(--text-secondary);"> 136 ${this.formatRecordingLabel(r.label)} 137 ${r.duration?`<span style="font-weight: normal; color: var(--text-muted);"> (${this.formatDuration(r.duration)})</span>`:""} 138 </div> 139 <div id="recording-player-${a}"> 140 <span style="color: var(--text-muted);">Loading...</span> 141 </div> 142 </div> 143 `).join(""),e.forEach((r,a)=>{this.loadRecording(r.url,`recording-player-${a}`)})}loadRecording(e,t="recording-player-container"){console.log("loadRecording called with URL:",e);const r=document.getElementById(t);if(!r){console.error(`${t} not found in DOM`);return}try{r.innerHTML=` 144 <audio controls style="width: 100%;" src="${e}"> 145 Your browser does not support audio playback. 146 </audio> 147 `,console.log("Audio player created successfully with URL:",e)}catch(a){console.error("Error loading recording:",a),r.innerHTML=`<span style="color: var(--danger);">Error: ${a.message}</span>`}}}export{w as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.