Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>master
| @@ -32,7 +32,7 @@ Class BusinessesController_Class | |||
| Dim f, k, page, pageCount, result, rows, total, industries, areas, sort, dir, firstRow, lastRow | |||
| Set f = CreateObject("Scripting.Dictionary") | |||
| For Each k In Array("q", "industry", "area", "status", "show", "contacted", "phone", "sort", "dir") | |||
| For Each k In Array("q", "industry", "area", "status", "show", "contacted", "phone", "outcome", "sort", "dir") | |||
| f(k) = Trim(CStr(Request.QueryString(k))) | |||
| Next | |||
| f("q") = Left(f("q"), 100) | |||
| @@ -31,7 +31,7 @@ Class ExportController_Class | |||
| Dim f, k, rs, line, n, fileName, areaNames, bizAreas | |||
| Set f = CreateObject("Scripting.Dictionary") | |||
| For Each k In Array("q", "industry", "area", "status", "show", "contacted", "phone", "sort", "dir") | |||
| For Each k In Array("q", "industry", "area", "status", "show", "contacted", "phone", "outcome", "sort", "dir") | |||
| f(k) = Trim(CStr(Request.QueryString(k))) | |||
| Next | |||
| f("q") = Left(f("q"), 100) | |||
| @@ -87,7 +87,7 @@ End Function | |||
| Function Businesses_QS(f, page, sort, dir) | |||
| Dim k, s | |||
| s = "" | |||
| For Each k In Array("q", "industry", "area", "status", "show", "contacted", "phone") | |||
| For Each k In Array("q", "industry", "area", "status", "show", "contacted", "phone", "outcome") | |||
| If f.Exists(k) Then | |||
| If Len(f(k)) > 0 Then s = s & "&" & k & "=" & Server.URLEncode(f(k)) | |||
| End If | |||
| @@ -12,6 +12,7 @@ | |||
| ' show "" = everyone, "prospects", "customers" | |||
| ' contacted "never" = only businesses with no contact yet | |||
| ' phone "1" = only businesses with a phone number | |||
| ' outcome an outcome key (or "none") = businesses with a contact logged with that outcome in the last 30 days | |||
| ' sort name | city | industry | area | status | last_contacted | next_action | added (default name) | |||
| ' area sorts by each business's alphabetically first area; businesses with none sort first | |||
| ' dir asc | desc | |||
| @@ -343,7 +344,7 @@ Class BusinessRepository_Class | |||
| End Sub | |||
| Private Sub BuildWhere(f) | |||
| Dim terms, t, d, q, n, frag, ind, ar, st | |||
| Dim terms, t, d, q, n, frag, ind, ar, st, oc | |||
| m_where = "1=1" | |||
| Set m_params = CreateObject("Scripting.Dictionary") | |||
| @@ -406,6 +407,17 @@ Class BusinessRepository_Class | |||
| If Val_(f, "contacted") = "never" Then m_where = m_where & " AND b.last_contacted_at IS NULL" | |||
| If Val_(f, "phone") = "1" Then m_where = m_where & " AND b.phone_digits IS NOT NULL" | |||
| ' Businesses with a contact logged in the last 30 days with this outcome (same window as the dashboard). | |||
| ' "none" = contacts logged without an outcome. Inlined from a whitelist, not a ? marker (see area above). | |||
| oc = Val_(f, "outcome") | |||
| If oc = "none" Then | |||
| m_where = m_where & " AND EXISTS (SELECT x.id FROM interactions x WHERE x.business_id = b.id " & _ | |||
| "AND x.contacted_at >= DateAdd('d', -29, Date()) AND (x.outcome IS NULL OR x.outcome = ''))" | |||
| ElseIf Businesses_IsKey(Businesses_Outcomes(), oc) Then | |||
| m_where = m_where & " AND EXISTS (SELECT x.id FROM interactions x WHERE x.business_id = b.id " & _ | |||
| "AND x.contacted_at >= DateAdd('d', -29, Date()) AND x.outcome = '" & oc & "')" | |||
| End If | |||
| End Sub | |||
| ' FROM clause for the list and export: the business, its industry, and fa.first_area (its alphabetically first | |||
| @@ -46,13 +46,21 @@ Class StatsRepository_Class | |||
| Set ActivityCounts = d | |||
| End Function | |||
| ' Contacts per day for the last `days` days, oldest first: list of Array(date, count) including zero days | |||
| ' Contacts per day for the last `days` days, oldest first: list of Array(date, count, methods) including zero days. | |||
| ' methods is a Dictionary of method key -> count for that day. | |||
| Public Function DailyActivity(days) | |||
| Dim rs, counts, list, i, dt, key | |||
| Dim rs, counts, byMethod, list, i, dt, key, m | |||
| Set counts = CreateObject("Scripting.Dictionary") | |||
| Set rs = Db_Query("SELECT contacted_at, COUNT(*) FROM interactions WHERE contacted_at >= DateAdd('d', " & CLng(1 - days) & ", Date()) GROUP BY contacted_at", Array()) | |||
| Set byMethod = CreateObject("Scripting.Dictionary") | |||
| Set rs = Db_Query("SELECT contacted_at, method, COUNT(*) FROM interactions WHERE contacted_at >= DateAdd('d', " & CLng(1 - days) & ", Date()) GROUP BY contacted_at, method", Array()) | |||
| Do While Not rs.EOF | |||
| If Not IsNull(rs(0)) Then counts(CStr(CLng(CDate(rs(0))))) = CLng(rs(1)) | |||
| If Not IsNull(rs(0)) Then | |||
| key = CStr(CLng(CDate(rs(0)))) | |||
| counts(key) = N(counts(key)) + CLng(rs(2)) | |||
| If Not byMethod.Exists(key) Then Set byMethod(key) = CreateObject("Scripting.Dictionary") | |||
| m = rs(1) & "" | |||
| byMethod(key)(m) = N(byMethod(key)(m)) + CLng(rs(2)) | |||
| End If | |||
| rs.MoveNext | |||
| Loop | |||
| rs.Close | |||
| @@ -60,7 +68,11 @@ Class StatsRepository_Class | |||
| For i = days - 1 To 0 Step -1 | |||
| dt = Date - i | |||
| key = CStr(CLng(dt)) | |||
| If counts.Exists(key) Then list.Add list.Count, Array(dt, counts(key)) Else list.Add list.Count, Array(dt, 0) | |||
| If counts.Exists(key) Then | |||
| list.Add list.Count, Array(dt, counts(key), byMethod(key)) | |||
| Else | |||
| list.Add list.Count, Array(dt, 0, CreateObject("Scripting.Dictionary")) | |||
| End If | |||
| Next | |||
| Set DailyActivity = list | |||
| End Function | |||
| @@ -66,6 +66,13 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <% If Len(f("outcome")) > 0 Then %> | |||
| <input type="hidden" name="outcome" value="<%= H(f("outcome")) %>" /> | |||
| <div class="mt-2 small"> | |||
| <span class="badge text-bg-primary">Outcome: <% If f("outcome") = "none" Then Response.Write "(no outcome)" Else Response.Write H(Businesses_Label(Businesses_Outcomes(), f("outcome"))) %> · last 30 days</span> | |||
| <a href="/businesses" class="ms-1">clear</a> | |||
| </div> | |||
| <% End If %> | |||
| <div class="mt-2"> | |||
| <button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-search"></i> Search</button> | |||
| <a href="/businesses" class="btn btn-link btn-sm">Reset</a> | |||
| @@ -88,7 +88,7 @@ tiles = Array( _ | |||
| dd = daily(di) | |||
| barPct = Round(dd(1) * 100 / maxDaily, 0) | |||
| If dd(1) > 0 And barPct < 6 Then barPct = 6 %> | |||
| <div class="flex-fill d-flex flex-column justify-content-end align-items-center h-100" title="<%= Date_Fmt(dd(0)) %>: <%= dd(1) %>"> | |||
| <div class="flex-fill d-flex flex-column justify-content-end align-items-center h-100" title="<%= Date_Fmt(dd(0)) %>: <%= dd(1) %> total <% For mi = 0 To UBound(Businesses_Methods()) %><%= H(Businesses_Methods()(mi)(1)) %>: <% If dd(2).Exists(Businesses_Methods()(mi)(0)) Then Response.Write dd(2)(Businesses_Methods()(mi)(0)) Else Response.Write 0 %><% If mi < UBound(Businesses_Methods()) Then Response.Write " " %><% Next %>"> | |||
| <div class="small text-muted" style="font-size: .7rem;"><% If dd(1) > 0 Then Response.Write dd(1) %></div> | |||
| <div class="w-100 rounded-top <% If dd(0) = Date Then Response.Write "bg-primary" Else Response.Write "bg-primary-subtle" %>" style="height: <%= barPct %>%; min-height: <% If dd(1) > 0 Then Response.Write "2px" Else Response.Write "1px" %>;<% If dd(1) = 0 Then Response.Write " background:#dee2e6;" %>"></div> | |||
| </div> | |||
| @@ -130,7 +130,7 @@ tiles = Array( _ | |||
| ob = byOutcome(oi) %> | |||
| <tr> | |||
| <td><% If Len(ob(0)) = 0 Then Response.Write "<span class=""text-muted"">(no outcome)</span>" Else Response.Write H(Businesses_Label(Businesses_Outcomes(), ob(0))) %></td> | |||
| <td class="text-end"><%= FormatNumber(ob(1), 0) %></td> | |||
| <td class="text-end"><a href="/businesses?status=all&outcome=<% If Len(ob(0)) = 0 Then Response.Write "none" Else Response.Write Server.URLEncode(ob(0)) %>" class="text-decoration-none" title="List these businesses"><%= FormatNumber(ob(1), 0) %></a></td> | |||
| </tr> | |||
| <% Next %> | |||
| </table> | |||
Powered by TurnKey Linux.