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 | Dim f, k, page, pageCount, result, rows, total, industries, areas, sort, dir, firstRow, lastRow | ||||
| Set f = CreateObject("Scripting.Dictionary") | 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))) | f(k) = Trim(CStr(Request.QueryString(k))) | ||||
| Next | Next | ||||
| f("q") = Left(f("q"), 100) | f("q") = Left(f("q"), 100) | ||||
| @@ -31,7 +31,7 @@ Class ExportController_Class | |||||
| Dim f, k, rs, line, n, fileName, areaNames, bizAreas | Dim f, k, rs, line, n, fileName, areaNames, bizAreas | ||||
| Set f = CreateObject("Scripting.Dictionary") | 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))) | f(k) = Trim(CStr(Request.QueryString(k))) | ||||
| Next | Next | ||||
| f("q") = Left(f("q"), 100) | f("q") = Left(f("q"), 100) | ||||
| @@ -87,7 +87,7 @@ End Function | |||||
| Function Businesses_QS(f, page, sort, dir) | Function Businesses_QS(f, page, sort, dir) | ||||
| Dim k, s | Dim k, s | ||||
| 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 f.Exists(k) Then | ||||
| If Len(f(k)) > 0 Then s = s & "&" & k & "=" & Server.URLEncode(f(k)) | If Len(f(k)) > 0 Then s = s & "&" & k & "=" & Server.URLEncode(f(k)) | ||||
| End If | End If | ||||
| @@ -12,6 +12,7 @@ | |||||
| ' show "" = everyone, "prospects", "customers" | ' show "" = everyone, "prospects", "customers" | ||||
| ' contacted "never" = only businesses with no contact yet | ' contacted "never" = only businesses with no contact yet | ||||
| ' phone "1" = only businesses with a phone number | ' 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) | ' 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 | ' area sorts by each business's alphabetically first area; businesses with none sort first | ||||
| ' dir asc | desc | ' dir asc | desc | ||||
| @@ -343,7 +344,7 @@ Class BusinessRepository_Class | |||||
| End Sub | End Sub | ||||
| Private Sub BuildWhere(f) | 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" | m_where = "1=1" | ||||
| Set m_params = CreateObject("Scripting.Dictionary") | 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, "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" | 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 | End Sub | ||||
| ' FROM clause for the list and export: the business, its industry, and fa.first_area (its alphabetically first | ' 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 | Set ActivityCounts = d | ||||
| End Function | 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) | 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 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 | 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 | rs.MoveNext | ||||
| Loop | Loop | ||||
| rs.Close | rs.Close | ||||
| @@ -60,7 +68,11 @@ Class StatsRepository_Class | |||||
| For i = days - 1 To 0 Step -1 | For i = days - 1 To 0 Step -1 | ||||
| dt = Date - i | dt = Date - i | ||||
| key = CStr(CLng(dt)) | 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 | Next | ||||
| Set DailyActivity = list | Set DailyActivity = list | ||||
| End Function | End Function | ||||
| @@ -66,6 +66,13 @@ | |||||
| </div> | </div> | ||||
| </div> | </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"> | <div class="mt-2"> | ||||
| <button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-search"></i> Search</button> | <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> | <a href="/businesses" class="btn btn-link btn-sm">Reset</a> | ||||
| @@ -88,7 +88,7 @@ tiles = Array( _ | |||||
| dd = daily(di) | dd = daily(di) | ||||
| barPct = Round(dd(1) * 100 / maxDaily, 0) | barPct = Round(dd(1) * 100 / maxDaily, 0) | ||||
| If dd(1) > 0 And barPct < 6 Then barPct = 6 %> | 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="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 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> | </div> | ||||
| @@ -130,7 +130,7 @@ tiles = Array( _ | |||||
| ob = byOutcome(oi) %> | ob = byOutcome(oi) %> | ||||
| <tr> | <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><% 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> | </tr> | ||||
| <% Next %> | <% Next %> | ||||
| </table> | </table> | ||||
Powered by TurnKey Linux.