- remove domain-specific controllers, models, repositories, and views - keep only Home/Error starter routing and controller registry entries - add IIS Express startup support with run_site.cmd and applicationhost.config Co-Authored-By: Abacus.AI CLI <agent@abacus.ai>master
| @@ -1,397 +0,0 @@ | |||
| <% | |||
| ' HouseholdController - CRUD controller for Households | |||
| ' NorthTerritory app | |||
| ' | |||
| ' Dependencies: | |||
| ' - app/models/POBO_Households.asp | |||
| ' - app/models/HouseholdsRepository.asp | |||
| ' - app/models/POBO_HouseholderNames.asp | |||
| ' - app/models/HouseholderNamesRepository.asp | |||
| %> | |||
| <!--#include file="../models/POBO_Households.asp" --> | |||
| <!--#include file="../models/HouseholdsRepository.asp" --> | |||
| <!--#include file="../models/POBO_HouseholderNames.asp" --> | |||
| <!--#include file="../models/HouseholderNamesRepository.asp" --> | |||
| <% | |||
| Class HouseholdController_Class | |||
| Private m_useLayout | |||
| Private m_title | |||
| ' Public properties for views | |||
| Public households ' LinkedList for Index | |||
| Public household ' Single POBO for Show/Edit | |||
| Public territoriesList ' For dropdown | |||
| Public territoryNamesById ' Dictionary for territory labels | |||
| Public householderNames ' LinkedList for Show | |||
| ' Pagination properties | |||
| Public currentPage | |||
| Public pageCount | |||
| Public recordCount | |||
| Public perPage | |||
| Public searchTerm | |||
| Public filterTerritoryId | |||
| Public filterDoNotCall | |||
| Private Sub Class_Initialize() | |||
| m_useLayout = True | |||
| m_title = "Households" | |||
| currentPage = 1 | |||
| pageCount = 0 | |||
| recordCount = 0 | |||
| perPage = 25 | |||
| searchTerm = "" | |||
| filterTerritoryId = 0 | |||
| filterDoNotCall = -1 | |||
| End Sub | |||
| Public Property Get useLayout | |||
| useLayout = m_useLayout | |||
| End Property | |||
| Public Property Let useLayout(v) | |||
| m_useLayout = v | |||
| End Property | |||
| Public Property Get Title | |||
| Title = m_title | |||
| End Property | |||
| Public Property Let Title(v) | |||
| m_title = v | |||
| End Property | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Index - List all households with pagination, search, and territory filter | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Index() | |||
| ' Get pagination params | |||
| If Request.QueryString("page") <> "" And IsNumeric(Request.QueryString("page")) Then | |||
| currentPage = CInt(Request.QueryString("page")) | |||
| If currentPage < 1 Then currentPage = 1 | |||
| End If | |||
| ' Get search param | |||
| searchTerm = Trim(Request.QueryString("q") & "") | |||
| ' Get territory filter | |||
| If Request.QueryString("territory") <> "" And IsNumeric(Request.QueryString("territory")) Then | |||
| filterTerritoryId = CInt(Request.QueryString("territory")) | |||
| End If | |||
| If Request.QueryString("dnc") <> "" Then | |||
| If Request.QueryString("dnc") = "1" Then | |||
| filterDoNotCall = 1 | |||
| ElseIf Request.QueryString("dnc") = "0" Then | |||
| filterDoNotCall = 0 | |||
| End If | |||
| End If | |||
| ' Load territories for filter dropdown | |||
| Set territoriesList = TerritoriesRepository.GetAll(Empty) | |||
| Set territoryNamesById = BuildTerritoryNamesById(territoriesList) | |||
| ' Fetch households with pagination | |||
| If searchTerm <> "" And filterDoNotCall <> -1 Then | |||
| Set households = HouseholdsRepository.SearchTablePagedByDoNotCall( _ | |||
| Array("Address", "StreetName"), _ | |||
| searchTerm, _ | |||
| filterDoNotCall, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| ElseIf searchTerm <> "" Then | |||
| ' Search in Address and StreetName columns | |||
| Set households = HouseholdsRepository.SearchTablePaged( _ | |||
| Array("Address", "StreetName"), _ | |||
| searchTerm, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| ElseIf filterTerritoryId > 0 And filterDoNotCall <> -1 Then | |||
| Set households = HouseholdsRepository.FindPagedByTerritoryAndDoNotCall( _ | |||
| filterTerritoryId, _ | |||
| filterDoNotCall, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| ElseIf filterTerritoryId > 0 Then | |||
| ' Filter by territory | |||
| Set households = HouseholdsRepository.FindPaged( _ | |||
| Array("TerritoryId", filterTerritoryId), _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| ElseIf filterDoNotCall <> -1 Then | |||
| Set households = HouseholdsRepository.FindPaged( _ | |||
| Array("DoNotCall", filterDoNotCall), _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| Else | |||
| Set households = HouseholdsRepository.FindPaged( _ | |||
| Empty, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| End If | |||
| %> <!--#include file="../views/Household/index.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Show - Display a single household | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Show(id) | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(id) | |||
| If Err.Number <> 0 Or household Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Household not found." | |||
| Response.Redirect "/households" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| ' Load householder names for this household | |||
| Set householderNames = HouseholderNamesRepository.Find( _ | |||
| Array("HouseholdId", id), _ | |||
| "Name" _ | |||
| ) | |||
| %> <!--#include file="../views/Household/show.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' MarkReturned - Quick action to mark a householder name as returned | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub MarkReturned(id) | |||
| Dim householderId, hn, householdId | |||
| householderId = Request.Form("householder_id") | |||
| If householderId = "" Or Not IsNumeric(householderId) Then | |||
| Flash().Error = "Invalid householder ID." | |||
| Response.Redirect "/households/" & id | |||
| Exit Sub | |||
| End If | |||
| On Error Resume Next | |||
| Set hn = HouseholderNamesRepository.FindByID(CLng(householderId)) | |||
| If Err.Number <> 0 Or hn Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().Error = "Householder name not found." | |||
| Response.Redirect "/households/" & id | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| ' Toggle the returned status | |||
| If hn.LetterReturned = 1 Then | |||
| hn.LetterReturned = 0 | |||
| hn.ReturnDate = #1/1/1970# | |||
| Else | |||
| hn.LetterReturned = 1 | |||
| hn.ReturnDate = Now() | |||
| End If | |||
| HouseholderNamesRepository.Update hn | |||
| If hn.LetterReturned = 1 Then | |||
| Flash().Success = "Marked as returned." | |||
| Else | |||
| Flash().Success = "Marked as not returned." | |||
| End If | |||
| Response.Redirect "/households/" & id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Create - Display form for new household | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Create() | |||
| Set household = New POBO_Households | |||
| ' Pre-fill territory if passed in query string | |||
| If Request.QueryString("territory") <> "" And IsNumeric(Request.QueryString("territory")) Then | |||
| household.TerritoryId = CInt(Request.QueryString("territory")) | |||
| End If | |||
| ' Load territories for dropdown | |||
| Set territoriesList = TerritoriesRepository.GetAll(Empty) | |||
| %> <!--#include file="../views/Household/create.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Store - Save new household | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Store() | |||
| Set household = New POBO_Households | |||
| household.Address = Trim(Request.Form("Address")) | |||
| household.StreetNumber = Request.Form("StreetNumber") | |||
| household.StreetName = Trim(Request.Form("StreetName")) | |||
| household.Latitude = Trim(Request.Form("Latitude")) | |||
| household.Longitude = Trim(Request.Form("Longitude")) | |||
| household.IsBusiness = IIf(Request.Form("IsBusiness") = "1", 1, 0) | |||
| household.DoNotCall = IIf(Request.Form("DoNotCall") = "1", 1, 0) | |||
| household.DoNotCallNotes = Trim(Request.Form("DoNotCallNotes")) | |||
| household.DoNotCallPrivateNotes = Trim(Request.Form("DoNotCallPrivateNotes")) | |||
| household.TerritoryId = Request.Form("TerritoryId") | |||
| If Request.Form("DoNotCallDate") <> "" Then | |||
| household.DoNotCallDate = CDate(Request.Form("DoNotCallDate")) | |||
| ElseIf household.DoNotCall = 1 Then | |||
| household.DoNotCallDate = Date() | |||
| Else | |||
| household.DoNotCallDate = Null | |||
| End If | |||
| ' Validation | |||
| If household.Address = "" Then | |||
| Flash().AddError "Address is required." | |||
| Response.Redirect "/households/new" | |||
| Exit Sub | |||
| End If | |||
| If Not IsNumeric(household.TerritoryId) Or CInt(household.TerritoryId) < 1 Then | |||
| Flash().AddError "Please select a territory." | |||
| Response.Redirect "/households/new" | |||
| Exit Sub | |||
| End If | |||
| HouseholdsRepository.AddNew household | |||
| Flash().Success = "Household created successfully." | |||
| Response.Redirect "/households/" & household.Id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Edit - Display form to edit household | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Edit(id) | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(id) | |||
| If Err.Number <> 0 Or household Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Household not found." | |||
| Response.Redirect "/households" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| ' Load territories for dropdown | |||
| Set territoriesList = TerritoriesRepository.GetAll(Empty) | |||
| %> <!--#include file="../views/Household/edit.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Update - Save changes to household | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Update(id) | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(id) | |||
| If Err.Number <> 0 Or household Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Household not found." | |||
| Response.Redirect "/households" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| household.Address = Trim(Request.Form("Address")) | |||
| household.StreetNumber = Request.Form("StreetNumber") | |||
| household.StreetName = Trim(Request.Form("StreetName")) | |||
| household.Latitude = Trim(Request.Form("Latitude")) | |||
| household.Longitude = Trim(Request.Form("Longitude")) | |||
| household.IsBusiness = IIf(Request.Form("IsBusiness") = "1", 1, 0) | |||
| household.DoNotCall = IIf(Request.Form("DoNotCall") = "1", 1, 0) | |||
| household.DoNotCallNotes = Trim(Request.Form("DoNotCallNotes")) | |||
| household.DoNotCallPrivateNotes = Trim(Request.Form("DoNotCallPrivateNotes")) | |||
| household.TerritoryId = Request.Form("TerritoryId") | |||
| If Request.Form("DoNotCallDate") <> "" Then | |||
| household.DoNotCallDate = CDate(Request.Form("DoNotCallDate")) | |||
| ElseIf household.DoNotCall = 1 Then | |||
| If IsNull(household.DoNotCallDate) Then | |||
| household.DoNotCallDate = Date() | |||
| ElseIf Trim(CStr(household.DoNotCallDate)) = "" Then | |||
| household.DoNotCallDate = Date() | |||
| End If | |||
| Else | |||
| household.DoNotCallDate = Null | |||
| End If | |||
| ' Validation | |||
| If household.Address = "" Then | |||
| Flash().AddError "Address is required." | |||
| Response.Redirect "/households/" & id & "/edit" | |||
| Exit Sub | |||
| End If | |||
| HouseholdsRepository.Update household | |||
| Flash().Success = "Household updated successfully." | |||
| Response.Redirect "/households/" & id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Delete - Remove a household | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Delete(id) | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(id) | |||
| If Err.Number <> 0 Or household Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Household not found." | |||
| Response.Redirect "/households" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| HouseholdsRepository.Delete id | |||
| Flash().Success = "Household deleted successfully." | |||
| Response.Redirect "/households" | |||
| End Sub | |||
| Private Function BuildTerritoryNamesById(territories) | |||
| Dim dict : Set dict = Server.CreateObject("Scripting.Dictionary") | |||
| Dim iter, territory | |||
| Set iter = territories.Iterator() | |||
| Do While iter.HasNext() | |||
| Set territory = iter.GetNext() | |||
| dict(CStr(territory.Id)) = territory.Name & "" | |||
| Loop | |||
| Set BuildTerritoryNamesById = dict | |||
| End Function | |||
| End Class | |||
| ' Singleton instance | |||
| Dim HouseholdController_Class__Singleton | |||
| Function HouseholdController() | |||
| If IsEmpty(HouseholdController_Class__Singleton) Then | |||
| Set HouseholdController_Class__Singleton = New HouseholdController_Class | |||
| End If | |||
| Set HouseholdController = HouseholdController_Class__Singleton | |||
| End Function | |||
| %> | |||
| @@ -1,284 +0,0 @@ | |||
| <% | |||
| ' HouseholderNameController - CRUD controller for HouseholderNames | |||
| ' NorthTerritory app | |||
| ' | |||
| ' Dependencies (all included via HouseholdController which loads first): | |||
| ' - app/models/POBO_HouseholderNames.asp | |||
| ' - app/models/HouseholderNamesRepository.asp | |||
| ' - app/models/POBO_Households.asp | |||
| ' - app/models/HouseholdsRepository.asp | |||
| Class HouseholderNameController_Class | |||
| Private m_useLayout | |||
| Private m_title | |||
| ' Public properties for views | |||
| Public householderNames ' LinkedList for Index | |||
| Public householderName ' Single POBO for Show/Edit | |||
| Public household ' Parent household | |||
| Public householdsList ' For dropdown | |||
| ' Pagination properties | |||
| Public currentPage | |||
| Public pageCount | |||
| Public recordCount | |||
| Public perPage | |||
| Public searchTerm | |||
| Public filterHouseholdId | |||
| Private Sub Class_Initialize() | |||
| m_useLayout = True | |||
| m_title = "Householder Names" | |||
| currentPage = 1 | |||
| pageCount = 0 | |||
| recordCount = 0 | |||
| perPage = 25 | |||
| searchTerm = "" | |||
| filterHouseholdId = 0 | |||
| End Sub | |||
| Public Property Get useLayout | |||
| useLayout = m_useLayout | |||
| End Property | |||
| Public Property Let useLayout(v) | |||
| m_useLayout = v | |||
| End Property | |||
| Public Property Get Title | |||
| Title = m_title | |||
| End Property | |||
| Public Property Let Title(v) | |||
| m_title = v | |||
| End Property | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Index - List all householder names with pagination, search, and household filter | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Index() | |||
| ' Get pagination params | |||
| If Request.QueryString("page") <> "" And IsNumeric(Request.QueryString("page")) Then | |||
| currentPage = CInt(Request.QueryString("page")) | |||
| If currentPage < 1 Then currentPage = 1 | |||
| End If | |||
| ' Get search param | |||
| searchTerm = Trim(Request.QueryString("q") & "") | |||
| ' Get household filter | |||
| If Request.QueryString("household") <> "" And IsNumeric(Request.QueryString("household")) Then | |||
| filterHouseholdId = CLng(Request.QueryString("household")) | |||
| End If | |||
| ' Fetch householder names with pagination | |||
| If searchTerm <> "" Then | |||
| ' Search in Name column | |||
| Set householderNames = HouseholderNamesRepository.SearchTablePaged( _ | |||
| Array("Name"), _ | |||
| searchTerm, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| ElseIf filterHouseholdId > 0 Then | |||
| ' Filter by household | |||
| Set householderNames = HouseholderNamesRepository.FindPaged( _ | |||
| Array("HouseholdId", filterHouseholdId), _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| ' Load parent household for context | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(filterHouseholdId) | |||
| On Error GoTo 0 | |||
| Else | |||
| Set householderNames = HouseholderNamesRepository.FindPaged( _ | |||
| Empty, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| End If | |||
| %> <!--#include file="../views/HouseholderName/index.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Show - Display a single householder name | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Show(id) | |||
| On Error Resume Next | |||
| Set householderName = HouseholderNamesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or householderName Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().Error = "Householder name not found." | |||
| Response.Redirect "/householder-names" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| ' Load parent household | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(householderName.HouseholdId) | |||
| On Error GoTo 0 | |||
| %> <!--#include file="../views/HouseholderName/show.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Create - Display form for new householder name | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Create() | |||
| Set householderName = New POBO_HouseholderNames | |||
| householderName.Created = Now() | |||
| householderName.LetterReturned = 0 | |||
| ' Pre-fill household if passed in query string | |||
| If Request.QueryString("household") <> "" And IsNumeric(Request.QueryString("household")) Then | |||
| householderName.HouseholdId = CLng(Request.QueryString("household")) | |||
| ' Load parent household for context | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(householderName.HouseholdId) | |||
| On Error GoTo 0 | |||
| End If | |||
| %> <!--#include file="../views/HouseholderName/create.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Store - Save new householder name | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Store() | |||
| Set householderName = New POBO_HouseholderNames | |||
| householderName.Name = Trim(Request.Form("Name")) | |||
| householderName.HouseholdId = Request.Form("HouseholdId") | |||
| householderName.LetterReturned = IIf(Request.Form("LetterReturned") = "1", 1, 0) | |||
| householderName.Created = Now() | |||
| If Request.Form("ReturnDate") <> "" Then | |||
| On Error Resume Next | |||
| householderName.ReturnDate = CDate(Request.Form("ReturnDate")) | |||
| On Error GoTo 0 | |||
| End If | |||
| ' Validation | |||
| If householderName.Name = "" Then | |||
| Flash().Error = "Name is required." | |||
| Response.Redirect "/householder-names/new?household=" & householderName.HouseholdId | |||
| Exit Sub | |||
| End If | |||
| If Not IsNumeric(householderName.HouseholdId) Or CLng(householderName.HouseholdId) < 1 Then | |||
| Flash().Error = "Please select a household." | |||
| Response.Redirect "/householder-names/new" | |||
| Exit Sub | |||
| End If | |||
| HouseholderNamesRepository.AddNew householderName | |||
| Flash().Success = "Householder name created successfully." | |||
| Response.Redirect "/householder-names/" & householderName.Id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Edit - Display form to edit householder name | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Edit(id) | |||
| On Error Resume Next | |||
| Set householderName = HouseholderNamesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or householderName Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().Error = "Householder name not found." | |||
| Response.Redirect "/householder-names" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| ' Load parent household for context | |||
| On Error Resume Next | |||
| Set household = HouseholdsRepository.FindByID(householderName.HouseholdId) | |||
| On Error GoTo 0 | |||
| %> <!--#include file="../views/HouseholderName/edit.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Update - Save changes to householder name | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Update(id) | |||
| On Error Resume Next | |||
| Set householderName = HouseholderNamesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or householderName Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().Error = "Householder name not found." | |||
| Response.Redirect "/householder-names" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| householderName.Name = Trim(Request.Form("Name")) | |||
| householderName.LetterReturned = IIf(Request.Form("LetterReturned") = "1", 1, 0) | |||
| If Request.Form("ReturnDate") <> "" Then | |||
| On Error Resume Next | |||
| householderName.ReturnDate = CDate(Request.Form("ReturnDate")) | |||
| On Error GoTo 0 | |||
| Else | |||
| householderName.ReturnDate = #1/1/1970# | |||
| End If | |||
| ' Validation | |||
| If householderName.Name = "" Then | |||
| Flash().Error = "Name is required." | |||
| Response.Redirect "/householder-names/" & id & "/edit" | |||
| Exit Sub | |||
| End If | |||
| HouseholderNamesRepository.Update householderName | |||
| Flash().Success = "Householder name updated successfully." | |||
| Response.Redirect "/householder-names/" & id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Delete - Remove a householder name | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Delete(id) | |||
| On Error Resume Next | |||
| Set householderName = HouseholderNamesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or householderName Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().Error = "Householder name not found." | |||
| Response.Redirect "/householder-names" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| Dim householdId | |||
| householdId = householderName.HouseholdId | |||
| HouseholderNamesRepository.Delete id | |||
| Flash().Success = "Householder name deleted successfully." | |||
| Response.Redirect "/householder-names?household=" & householdId | |||
| End Sub | |||
| End Class | |||
| ' Singleton instance | |||
| Dim HouseholderNameController_Class__Singleton | |||
| Function HouseholderNameController() | |||
| If IsEmpty(HouseholderNameController_Class__Singleton) Then | |||
| Set HouseholderNameController_Class__Singleton = New HouseholderNameController_Class | |||
| End If | |||
| Set HouseholderNameController = HouseholderNameController_Class__Singleton | |||
| End Function | |||
| %> | |||
| @@ -1,226 +0,0 @@ | |||
| <% | |||
| ' TerritoryController - CRUD controller for Territories | |||
| ' Generated for NorthTerritory app | |||
| ' | |||
| ' Dependencies: | |||
| ' - app/models/POBO_Territories.asp | |||
| ' - app/models/TerritoriesRepository.asp | |||
| %> | |||
| <!--#include file="../models/POBO_Territories.asp" --> | |||
| <!--#include file="../models/TerritoriesRepository.asp" --> | |||
| <% | |||
| Class TerritoryController_Class | |||
| Private m_useLayout | |||
| Private m_title | |||
| ' Public properties for views | |||
| Public territories ' LinkedList for Index | |||
| Public territory ' Single POBO for Show/Edit | |||
| Public territoryHouseholdCounts | |||
| Public territoryStreets ' LinkedList of street names for Show | |||
| ' Pagination properties | |||
| Public currentPage | |||
| Public pageCount | |||
| Public recordCount | |||
| Public perPage | |||
| Public searchTerm | |||
| Private Sub Class_Initialize() | |||
| m_useLayout = True | |||
| m_title = "Territories" | |||
| currentPage = 1 | |||
| pageCount = 0 | |||
| recordCount = 0 | |||
| perPage = 20 | |||
| searchTerm = "" | |||
| Set territoryHouseholdCounts = Nothing | |||
| End Sub | |||
| Public Property Get useLayout | |||
| useLayout = m_useLayout | |||
| End Property | |||
| Public Property Let useLayout(v) | |||
| m_useLayout = v | |||
| End Property | |||
| Public Property Get Title | |||
| Title = m_title | |||
| End Property | |||
| Public Property Let Title(v) | |||
| m_title = v | |||
| End Property | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Index - List all territories with pagination and search | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Index() | |||
| ' Get pagination params | |||
| If Request.QueryString("page") <> "" And IsNumeric(Request.QueryString("page")) Then | |||
| currentPage = CInt(Request.QueryString("page")) | |||
| If currentPage < 1 Then currentPage = 1 | |||
| End If | |||
| ' Get search param | |||
| searchTerm = Trim(Request.QueryString("q") & "") | |||
| ' Fetch territories with pagination | |||
| If searchTerm <> "" Then | |||
| ' Search in Name and Description columns | |||
| Set territories = TerritoriesRepository.SearchTablePaged( _ | |||
| Array("Name", "Description"), _ | |||
| searchTerm, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| Else | |||
| Set territories = TerritoriesRepository.FindPaged( _ | |||
| Empty, _ | |||
| Empty, _ | |||
| perPage, _ | |||
| currentPage, _ | |||
| pageCount, _ | |||
| recordCount _ | |||
| ) | |||
| End If | |||
| Set territoryHouseholdCounts = HouseholdsRepository.GetCountsByTerritory() | |||
| %> <!--#include file="../views/Territory/index.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Show - Display a single territory | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Show(id) | |||
| On Error Resume Next | |||
| Set territory = TerritoriesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or territory Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Territory not found." | |||
| Response.Redirect "/territories" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| ' Load distinct street names for this territory | |||
| Set territoryStreets = HouseholdsRepository.GetDistinctStreetsByTerritory(id) | |||
| %> <!--#include file="../views/Territory/show.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Create - Display form for new territory | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Create() | |||
| Set territory = New POBO_Territories | |||
| %> <!--#include file="../views/Territory/create.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Store - Save new territory | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Store() | |||
| Set territory = New POBO_Territories | |||
| territory.Name = Trim(Request.Form("Name")) | |||
| territory.Description = Trim(Request.Form("Description")) | |||
| territory.Coordinates = Trim(Request.Form("Coordinates")) | |||
| ' Validation | |||
| If territory.Name = "" Then | |||
| Flash().AddError "Name is required." | |||
| Response.Redirect "/territories/new" | |||
| Exit Sub | |||
| End If | |||
| TerritoriesRepository.AddNew territory | |||
| Flash().Success = "Territory created successfully." | |||
| Response.Redirect "/territories/" & territory.Id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Edit - Display form to edit territory | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Edit(id) | |||
| On Error Resume Next | |||
| Set territory = TerritoriesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or territory Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Territory not found." | |||
| Response.Redirect "/territories" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| %> <!--#include file="../views/Territory/edit.asp" --> <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Update - Save changes to territory | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Update(id) | |||
| On Error Resume Next | |||
| Set territory = TerritoriesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or territory Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Territory not found." | |||
| Response.Redirect "/territories" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| territory.Name = Trim(Request.Form("Name")) | |||
| territory.Description = Trim(Request.Form("Description")) | |||
| territory.Coordinates = Trim(Request.Form("Coordinates")) | |||
| ' Validation | |||
| If territory.Name = "" Then | |||
| Flash().AddError "Name is required." | |||
| Response.Redirect "/territories/" & id & "/edit" | |||
| Exit Sub | |||
| End If | |||
| TerritoriesRepository.Update territory | |||
| Flash().Success = "Territory updated successfully." | |||
| Response.Redirect "/territories/" & id | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' Delete - Remove a territory | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub Delete(id) | |||
| On Error Resume Next | |||
| Set territory = TerritoriesRepository.FindByID(id) | |||
| If Err.Number <> 0 Or territory Is Nothing Then | |||
| On Error GoTo 0 | |||
| Flash().AddError "Territory not found." | |||
| Response.Redirect "/territories" | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| TerritoriesRepository.Delete id | |||
| Flash().Success = "Territory deleted successfully." | |||
| Response.Redirect "/territories" | |||
| End Sub | |||
| End Class | |||
| ' Singleton instance | |||
| Dim TerritoryController_Class__Singleton | |||
| Function TerritoryController() | |||
| If IsEmpty(TerritoryController_Class__Singleton) Then | |||
| Set TerritoryController_Class__Singleton = New TerritoryController_Class | |||
| End If | |||
| Set TerritoryController = TerritoryController_Class__Singleton | |||
| End Function | |||
| %> | |||
| @@ -1,5 +1,2 @@ | |||
| <!--#include file="ErrorController.asp" --> | |||
| <!--#include file="HomeController.asp" --> | |||
| <!--#include file="TerritoryController.asp" --> | |||
| <!--#include file="HouseholdController.asp" --> | |||
| <!--#include file="HouseholderNameController.asp" --> | |||
| <!--#include file="ErrorController.asp" --> | |||
| @@ -1,176 +0,0 @@ | |||
| <% | |||
| ' Auto-generated Repository for table [HouseholderNames] | |||
| ' Generated on 1/17/2026 7:59:02 PM | |||
| ' Generator: GenerateRepo.vbs v1.0 | |||
| ' | |||
| ' Dependencies: | |||
| ' - core/lib.DAL.asp (DAL singleton for database access) | |||
| ' - core/lib.AutoMapper.asp (Automapper for object mapping) | |||
| ' - core/lib.Collections.asp (LinkedList_Class) | |||
| ' - core/lib.helpers.asp (KVUnzip, BuildOrderBy, QI, Destroy) | |||
| Class HouseholderNamesRepository_Class | |||
| Public Function FindByID(id) | |||
| Dim sql : sql = "Select [Created], [HouseholdId], [Id], [LetterReturned], [Name], [ReturnDate] FROM [HouseholderNames] WHERE [Id] = ?" | |||
| Dim rs : Set rs = DAL.Query(sql, Array(id)) | |||
| If rs.EOF Then | |||
| Err.Raise 1, "HouseholderNamesRepository_Class", RecordNotFoundException("Id", id) | |||
| Else | |||
| Set FindByID = Automapper.AutoMap(rs, "POBO_HouseholderNames") | |||
| End If | |||
| Destroy rs | |||
| End Function | |||
| Public Function GetAll(orderBy) | |||
| Set GetAll = Find(Empty, orderBy) | |||
| End Function | |||
| Public Function Find(where_kvarray, order_string_or_array) | |||
| Dim sql : sql = "Select [Created], [HouseholdId], [Id], [LetterReturned], [Name], [ReturnDate] FROM [HouseholderNames]" | |||
| Dim where_keys, where_values, i | |||
| If Not IsEmpty(where_kvarray) Then | |||
| KVUnzip where_kvarray, where_keys, where_values | |||
| If Not IsEmpty(where_keys) Then | |||
| sql = sql & " WHERE " | |||
| For i = 0 To UBound(where_keys) | |||
| If i > 0 Then sql = sql & " AND " | |||
| sql = sql & " " & QI(where_keys(i)) & " = ?" | |||
| Next | |||
| End If | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.Query(sql, where_values) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Do Until rs.EOF | |||
| list.Push Automapper.AutoMap(rs, "POBO_HouseholderNames") | |||
| rs.MoveNext | |||
| Loop | |||
| Set Find = list | |||
| Destroy rs | |||
| End Function | |||
| Public Function FindPaged(where_kvarray, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Created], [HouseholdId], [Id], [LetterReturned], [Name], [ReturnDate] FROM [HouseholderNames]" | |||
| Dim where_keys, where_values, i | |||
| If Not IsEmpty(where_kvarray) Then | |||
| KVUnzip where_kvarray, where_keys, where_values | |||
| If Not IsEmpty(where_keys) Then | |||
| sql = sql & " WHERE " | |||
| For i = 0 To UBound(where_keys) | |||
| If i > 0 Then sql = sql & " AND " | |||
| sql = sql & " " & QI(where_keys(i)) & " = ?" | |||
| Next | |||
| End If | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, where_values, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set FindPaged = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Public Function SearchTablePaged(columns_array, search_value, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Created], [HouseholdId], [Id], [LetterReturned], [Name], [ReturnDate] FROM [HouseholderNames]" | |||
| Dim i, params() | |||
| If IsArray(columns_array) And UBound(columns_array) >= 0 Then | |||
| sql = sql & " WHERE " | |||
| ReDim params(UBound(columns_array)) | |||
| For i = 0 To UBound(columns_array) | |||
| If i > 0 Then sql = sql & " OR " | |||
| sql = sql & " " & QI(columns_array(i)) & " LIKE ?" | |||
| params(i) = "%" & search_value & "%" | |||
| Next | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, params, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set SearchTablePaged = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Private Function PagedList(rs, per_page) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Dim x : x = 0 | |||
| Do While (per_page <= 0 Or x < per_page) And Not rs.EOF | |||
| list.Push Automapper.AutoMap(rs, "POBO_HouseholderNames") | |||
| x = x + 1 | |||
| rs.MoveNext | |||
| Loop | |||
| Set PagedList = list | |||
| End Function | |||
| Public Sub AddNew(ByRef model) | |||
| Dim sql : sql = "INSERT INTO [HouseholderNames] ([Created], [HouseholdId], [LetterReturned], [Name], [ReturnDate]) VALUES (?, ?, ?, ?, ?)" | |||
| DAL.[Execute] sql, Array(model.Created, model.HouseholdId, model.LetterReturned, model.Name, model.ReturnDate) | |||
| ' Retrieve the newly inserted ID | |||
| On Error Resume Next | |||
| Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty) | |||
| If Err.Number <> 0 Then | |||
| ' Fallback for Access databases | |||
| Err.Clear | |||
| Set rsId = DAL.Query("SELECT TOP 1 [Id] FROM [HouseholderNames] ORDER BY [Id] DESC", Empty) | |||
| End If | |||
| On Error GoTo 0 | |||
| If Not rsId.EOF Then | |||
| If Not IsNull(rsId(0)) Then model.Id = rsId(0) | |||
| End If | |||
| Destroy rsId | |||
| End Sub | |||
| Public Sub Update(model) | |||
| Dim sql : sql = "UPDATE [HouseholderNames] SET [Created] = ?, [HouseholdId] = ?, [LetterReturned] = ?, [Name] = ?, [ReturnDate] = ? WHERE [Id] = ?" | |||
| DAL.[Execute] sql, Array(model.Created, model.HouseholdId, model.LetterReturned, model.Name, model.ReturnDate, model.Id) | |||
| End Sub | |||
| Public Sub Delete(id) | |||
| Dim sql : sql = "DELETE FROM [HouseholderNames] WHERE [Id] = ?" | |||
| DAL.[Execute] sql, Array(id) | |||
| End Sub | |||
| Private Function RecordNotFoundException(ByVal field_name, ByVal field_val) | |||
| RecordNotFoundException = "HouseholderNames record was not found with " & field_name & " = '" & field_val & "'." | |||
| End Function | |||
| Private Function QI(name) | |||
| QI = "[" & Replace(CStr(name), "]", "]]") & "]" | |||
| End Function | |||
| Private Function BuildOrderBy(orderArg, defaultCol) | |||
| Dim s : s = "" | |||
| If IsEmpty(orderArg) Or IsNull(orderArg) Or orderArg = "" Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf IsArray(orderArg) Then | |||
| Dim i : s = " ORDER BY " | |||
| For i = 0 To UBound(orderArg) | |||
| If i > 0 Then s = s & ", " | |||
| s = s & QI(orderArg(i)) | |||
| Next | |||
| Else | |||
| s = " ORDER BY " & QI(orderArg) | |||
| End If | |||
| BuildOrderBy = s | |||
| End Function | |||
| End Class | |||
| Dim HouseholderNamesRepository__Singleton | |||
| Function HouseholderNamesRepository() | |||
| If IsEmpty(HouseholderNamesRepository__Singleton) Then | |||
| Set HouseholderNamesRepository__Singleton = new HouseholderNamesRepository_Class | |||
| End If | |||
| Set HouseholderNamesRepository = HouseholderNamesRepository__Singleton | |||
| End Function | |||
| %> | |||
| @@ -1,243 +0,0 @@ | |||
| <% | |||
| ' Auto-generated Repository for table [Households] | |||
| ' Generator: GenerateRepo.vbs v1.0 | |||
| ' | |||
| ' Dependencies: | |||
| ' - core/lib.DAL.asp (DAL singleton for database access) | |||
| ' - core/lib.AutoMapper.asp (Automapper for object mapping) | |||
| ' - core/lib.Collections.asp (LinkedList_Class) | |||
| ' - core/lib.helpers.asp (KVUnzip, BuildOrderBy, QI, Destroy) | |||
| Class HouseholdsRepository_Class | |||
| Public Function FindByID(id) | |||
| Dim sql : sql = "Select [Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [Id], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId] FROM [Households] WHERE [Id] = ?" | |||
| Dim rs : Set rs = DAL.Query(sql, Array(id)) | |||
| If rs.EOF Then | |||
| Err.Raise 1, "HouseholdsRepository_Class", RecordNotFoundException("Id", id) | |||
| Else | |||
| Set FindByID = Automapper.AutoMap(rs, "POBO_Households") | |||
| End If | |||
| Destroy rs | |||
| End Function | |||
| Public Function GetAll(orderBy) | |||
| Set GetAll = Find(Empty, orderBy) | |||
| End Function | |||
| Public Function Find(where_kvarray, order_string_or_array) | |||
| Dim sql : sql = "Select [Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [Id], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId] FROM [Households]" | |||
| Dim where_keys, where_values, i | |||
| If Not IsEmpty(where_kvarray) Then | |||
| KVUnzip where_kvarray, where_keys, where_values | |||
| If Not IsEmpty(where_keys) Then | |||
| sql = sql & " WHERE " | |||
| For i = 0 To UBound(where_keys) | |||
| If i > 0 Then sql = sql & " AND " | |||
| sql = sql & " " & QI(where_keys(i)) & " = ?" | |||
| Next | |||
| End If | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.Query(sql, where_values) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Do Until rs.EOF | |||
| list.Push Automapper.AutoMap(rs, "POBO_Households") | |||
| rs.MoveNext | |||
| Loop | |||
| Set Find = list | |||
| Destroy rs | |||
| End Function | |||
| Public Function FindPaged(where_kvarray, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [Id], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId] FROM [Households]" | |||
| Dim where_keys, where_values, i | |||
| If Not IsEmpty(where_kvarray) Then | |||
| KVUnzip where_kvarray, where_keys, where_values | |||
| If Not IsEmpty(where_keys) Then | |||
| sql = sql & " WHERE " | |||
| For i = 0 To UBound(where_keys) | |||
| If i > 0 Then sql = sql & " AND " | |||
| sql = sql & " " & QI(where_keys(i)) & " = ?" | |||
| Next | |||
| End If | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, where_values, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set FindPaged = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Public Function SearchTablePaged(columns_array, search_value, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [Id], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId] FROM [Households]" | |||
| Dim i, params() | |||
| If IsArray(columns_array) And UBound(columns_array) >= 0 Then | |||
| sql = sql & " WHERE " | |||
| ReDim params(UBound(columns_array)) | |||
| For i = 0 To UBound(columns_array) | |||
| If i > 0 Then sql = sql & " OR " | |||
| sql = sql & " " & QI(columns_array(i)) & " LIKE ?" | |||
| params(i) = "%" & search_value & "%" | |||
| Next | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, params, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set SearchTablePaged = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Public Function SearchTablePagedByDoNotCall(columns_array, search_value, doNotCallValue, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [Id], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId] FROM [Households] WHERE [DoNotCall] = ?" | |||
| Dim i, params() | |||
| If IsArray(columns_array) And UBound(columns_array) >= 0 Then | |||
| ReDim params(UBound(columns_array) + 1) | |||
| params(0) = doNotCallValue | |||
| sql = sql & " AND (" | |||
| For i = 0 To UBound(columns_array) | |||
| If i > 0 Then sql = sql & " OR " | |||
| sql = sql & " " & QI(columns_array(i)) & " LIKE ?" | |||
| params(i + 1) = "%" & search_value & "%" | |||
| Next | |||
| sql = sql & ")" | |||
| Else | |||
| ReDim params(0) | |||
| params(0) = doNotCallValue | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, params, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set SearchTablePagedByDoNotCall = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Public Function FindPagedByTerritoryAndDoNotCall(territoryId, doNotCallValue, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [Id], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId] FROM [Households] WHERE [TerritoryId] = ? AND [DoNotCall] = ?" | |||
| Dim rs : Set rs = DAL.PagedQuery(sql & BuildOrderBy(order_string_or_array, "[Id]"), Array(territoryId, doNotCallValue), per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set FindPagedByTerritoryAndDoNotCall = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Private Function PagedList(rs, per_page) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Dim x : x = 0 | |||
| Do While (per_page <= 0 Or x < per_page) And Not rs.EOF | |||
| list.Push Automapper.AutoMap(rs, "POBO_Households") | |||
| x = x + 1 | |||
| rs.MoveNext | |||
| Loop | |||
| Set PagedList = list | |||
| End Function | |||
| Public Function GetCountsByTerritory() | |||
| Dim sql : sql = "SELECT [TerritoryId], COUNT(*) AS [HouseholdCount] FROM [Households] GROUP BY [TerritoryId]" | |||
| Dim rs : Set rs = DAL.Query(sql, Empty) | |||
| Dim dict : Set dict = Server.CreateObject("Scripting.Dictionary") | |||
| Do Until rs.EOF | |||
| If Not IsNull(rs("TerritoryId")) Then | |||
| dict(CLng(rs("TerritoryId"))) = CLng(rs("HouseholdCount")) | |||
| End If | |||
| rs.MoveNext | |||
| Loop | |||
| Set GetCountsByTerritory = dict | |||
| Destroy rs | |||
| End Function | |||
| Public Function GetDistinctStreetsByTerritory(territoryId) | |||
| Dim sql : sql = "SELECT DISTINCT [StreetName] FROM [Households] WHERE [TerritoryId] = ? AND [StreetName] IS NOT NULL AND [StreetName] <> '' ORDER BY [StreetName]" | |||
| Dim rs : Set rs = DAL.Query(sql, Array(territoryId)) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Do Until rs.EOF | |||
| list.Push rs("StreetName") & "" | |||
| rs.MoveNext | |||
| Loop | |||
| Set GetDistinctStreetsByTerritory = list | |||
| Destroy rs | |||
| End Function | |||
| Public Sub AddNew(ByRef model) | |||
| Dim sql : sql = "INSERT INTO [Households] ([Address], [DoNotCall], [DoNotCallDate], [DoNotCallNotes], [DoNotCallPrivateNotes], [IsBusiness], [Latitude], [Longitude], [StreetName], [StreetNumber], [TerritoryId]) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" | |||
| DAL.[Execute] sql, Array(model.Address, model.DoNotCall, model.DoNotCallDate, model.DoNotCallNotes, model.DoNotCallPrivateNotes, model.IsBusiness, model.Latitude, model.Longitude, model.StreetName, model.StreetNumber, model.TerritoryId) | |||
| ' Retrieve the newly inserted ID | |||
| On Error Resume Next | |||
| Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty) | |||
| If Err.Number <> 0 Then | |||
| ' Fallback for Access databases | |||
| Err.Clear | |||
| Set rsId = DAL.Query("SELECT TOP 1 [Id] FROM [Households] ORDER BY [Id] DESC", Empty) | |||
| End If | |||
| On Error GoTo 0 | |||
| If Not rsId.EOF Then | |||
| If Not IsNull(rsId(0)) Then model.Id = rsId(0) | |||
| End If | |||
| Destroy rsId | |||
| End Sub | |||
| Public Sub Update(model) | |||
| Dim sql : sql = "UPDATE [Households] SET [Address] = ?, [DoNotCall] = ?, [DoNotCallDate] = ?, [DoNotCallNotes] = ?, [DoNotCallPrivateNotes] = ?, [IsBusiness] = ?, [Latitude] = ?, [Longitude] = ?, [StreetName] = ?, [StreetNumber] = ?, [TerritoryId] = ? WHERE [Id] = ?" | |||
| DAL.[Execute] sql, Array(model.Address, model.DoNotCall, model.DoNotCallDate, model.DoNotCallNotes, model.DoNotCallPrivateNotes, model.IsBusiness, model.Latitude, model.Longitude, model.StreetName, model.StreetNumber, model.TerritoryId, model.Id) | |||
| End Sub | |||
| Public Sub Delete(id) | |||
| Dim sql : sql = "DELETE FROM [Households] WHERE [Id] = ?" | |||
| DAL.[Execute] sql, Array(id) | |||
| End Sub | |||
| Private Function RecordNotFoundException(ByVal field_name, ByVal field_val) | |||
| RecordNotFoundException = "Households record was not found with " & field_name & " = '" & field_val & "'." | |||
| End Function | |||
| Private Function QI(name) | |||
| QI = "[" & Replace(CStr(name), "]", "]]") & "]" | |||
| End Function | |||
| Private Function BuildOrderBy(orderArg, defaultCol) | |||
| Dim s : s = "" | |||
| If IsEmpty(orderArg) Or IsNull(orderArg) Or orderArg = "" Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf IsArray(orderArg) Then | |||
| Dim i : s = " ORDER BY " | |||
| For i = 0 To UBound(orderArg) | |||
| If i > 0 Then s = s & ", " | |||
| s = s & QI(orderArg(i)) | |||
| Next | |||
| Else | |||
| s = " ORDER BY " & QI(orderArg) | |||
| End If | |||
| BuildOrderBy = s | |||
| End Function | |||
| End Class | |||
| Dim HouseholdsRepository__Singleton | |||
| Function HouseholdsRepository() | |||
| If IsEmpty(HouseholdsRepository__Singleton) Then | |||
| Set HouseholdsRepository__Singleton = new HouseholdsRepository_Class | |||
| End If | |||
| Set HouseholdsRepository = HouseholdsRepository__Singleton | |||
| End Function | |||
| %> | |||
| @@ -1,133 +0,0 @@ | |||
| <% | |||
| ' Auto-generated POBO for table [HouseholderNames] | |||
| ' Generated on 1/17/2026 7:59:02 PM | |||
| ' Generator: GenerateRepo.vbs v1.0 | |||
| ' | |||
| ' Dependencies: core/helpers.asp (QuoteValue function) | |||
| Class POBO_HouseholderNames | |||
| ' Public array of all property names | |||
| Public Properties | |||
| Private pCreated | |||
| Private pHouseholdId | |||
| Private pId | |||
| Private pLetterReturned | |||
| Private pName | |||
| Private pReturnDate | |||
| Private Sub Class_Initialize() | |||
| pCreated = #1/1/1970# | |||
| pHouseholdId = 0 | |||
| pId = 0 | |||
| pLetterReturned = 0 | |||
| pName = Null | |||
| pReturnDate = #1/1/1970# | |||
| Properties = Array("Created","HouseholdId","Id","LetterReturned","Name","ReturnDate") | |||
| End Sub | |||
| Public Property Get PrimaryKey() | |||
| PrimaryKey = "Id" | |||
| End Property | |||
| Public Property Get TableName() | |||
| TableName = "HouseholderNames" | |||
| End Property | |||
| Public Property Get Created() | |||
| Created = pCreated | |||
| End Property | |||
| Public Property Let Created(val) | |||
| On Error Resume Next | |||
| pCreated = CDate(val) | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_HouseholderNames.Created", "Invalid value for Created: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get HouseholdId() | |||
| HouseholdId = pHouseholdId | |||
| End Property | |||
| Public Property Let HouseholdId(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pHouseholdId = CDbl(val) | |||
| Else | |||
| pHouseholdId = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_HouseholderNames.HouseholdId", "Invalid value for HouseholdId: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get Id() | |||
| Id = pId | |||
| End Property | |||
| Public Property Let Id(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pId = CDbl(val) | |||
| Else | |||
| pId = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_HouseholderNames.Id", "Invalid value for Id: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get LetterReturned() | |||
| LetterReturned = pLetterReturned | |||
| End Property | |||
| Public Property Let LetterReturned(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pLetterReturned = CDbl(val) | |||
| Else | |||
| pLetterReturned = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_HouseholderNames.LetterReturned", "Invalid value for LetterReturned: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get Name() | |||
| Name = pName | |||
| End Property | |||
| Public Property Let Name(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pName = CDbl(val) | |||
| Else | |||
| pName = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_HouseholderNames.Name", "Invalid value for Name: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get ReturnDate() | |||
| ReturnDate = pReturnDate | |||
| End Property | |||
| Public Property Let ReturnDate(val) | |||
| On Error Resume Next | |||
| pReturnDate = CDate(val) | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_HouseholderNames.ReturnDate", "Invalid value for ReturnDate: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| End Class | |||
| %> | |||
| @@ -1,172 +0,0 @@ | |||
| <% | |||
| ' Auto-generated POBO for table [Households] | |||
| ' Generator: GenerateRepo.vbs v1.0 | |||
| ' | |||
| ' Dependencies: core/helpers.asp (QuoteValue function) | |||
| Class POBO_Households | |||
| ' Public array of all property names | |||
| Public Properties | |||
| Private pAddress | |||
| Private pDoNotCall | |||
| Private pDoNotCallDate | |||
| Private pDoNotCallNotes | |||
| Private pDoNotCallPrivateNotes | |||
| Private pId | |||
| Private pIsBusiness | |||
| Private pLatitude | |||
| Private pLongitude | |||
| Private pStreetName | |||
| Private pStreetNumber | |||
| Private pTerritoryId | |||
| Private Sub Class_Initialize() | |||
| pAddress = Null | |||
| pDoNotCall = 0 | |||
| pDoNotCallDate = Null | |||
| pDoNotCallNotes = Null | |||
| pDoNotCallPrivateNotes = Null | |||
| pId = 0 | |||
| pIsBusiness = 0 | |||
| pLatitude = Null | |||
| pLongitude = Null | |||
| pStreetName = Null | |||
| pStreetNumber = 0 | |||
| pTerritoryId = 0 | |||
| Properties = Array("Address","DoNotCall","DoNotCallDate","DoNotCallNotes","DoNotCallPrivateNotes","Id","IsBusiness","Latitude","Longitude","StreetName","StreetNumber","TerritoryId") | |||
| End Sub | |||
| Public Property Get PrimaryKey() | |||
| PrimaryKey = "Id" | |||
| End Property | |||
| Public Property Get TableName() | |||
| TableName = "Households" | |||
| End Property | |||
| Public Property Get Address() | |||
| Address = pAddress | |||
| End Property | |||
| Public Property Let Address(val) | |||
| pAddress = val | |||
| End Property | |||
| Public Property Get DoNotCall() | |||
| DoNotCall = pDoNotCall | |||
| End Property | |||
| Public Property Let DoNotCall(val) | |||
| If IsNumeric(val) Then | |||
| pDoNotCall = CLng(val) | |||
| Else | |||
| pDoNotCall = val | |||
| End If | |||
| End Property | |||
| Public Property Get DoNotCallDate() | |||
| DoNotCallDate = pDoNotCallDate | |||
| End Property | |||
| Public Property Let DoNotCallDate(val) | |||
| If IsNull(val) Then | |||
| pDoNotCallDate = Null | |||
| ElseIf Trim(CStr(val)) = "" Then | |||
| pDoNotCallDate = Null | |||
| Else | |||
| pDoNotCallDate = CDate(val) | |||
| End If | |||
| End Property | |||
| Public Property Get DoNotCallNotes() | |||
| DoNotCallNotes = pDoNotCallNotes | |||
| End Property | |||
| Public Property Let DoNotCallNotes(val) | |||
| pDoNotCallNotes = val | |||
| End Property | |||
| Public Property Get DoNotCallPrivateNotes() | |||
| DoNotCallPrivateNotes = pDoNotCallPrivateNotes | |||
| End Property | |||
| Public Property Let DoNotCallPrivateNotes(val) | |||
| pDoNotCallPrivateNotes = val | |||
| End Property | |||
| Public Property Get Id() | |||
| Id = pId | |||
| End Property | |||
| Public Property Let Id(val) | |||
| If IsNumeric(val) Then | |||
| pId = CLng(val) | |||
| Else | |||
| pId = val | |||
| End If | |||
| End Property | |||
| Public Property Get IsBusiness() | |||
| IsBusiness = pIsBusiness | |||
| End Property | |||
| Public Property Let IsBusiness(val) | |||
| If IsNumeric(val) Then | |||
| pIsBusiness = CLng(val) | |||
| Else | |||
| pIsBusiness = val | |||
| End If | |||
| End Property | |||
| Public Property Get Latitude() | |||
| Latitude = pLatitude | |||
| End Property | |||
| Public Property Let Latitude(val) | |||
| pLatitude = val | |||
| End Property | |||
| Public Property Get Longitude() | |||
| Longitude = pLongitude | |||
| End Property | |||
| Public Property Let Longitude(val) | |||
| pLongitude = val | |||
| End Property | |||
| Public Property Get StreetName() | |||
| StreetName = pStreetName | |||
| End Property | |||
| Public Property Let StreetName(val) | |||
| pStreetName = val | |||
| End Property | |||
| Public Property Get StreetNumber() | |||
| StreetNumber = pStreetNumber | |||
| End Property | |||
| Public Property Let StreetNumber(val) | |||
| If IsNumeric(val) Then | |||
| pStreetNumber = CLng(val) | |||
| Else | |||
| pStreetNumber = val | |||
| End If | |||
| End Property | |||
| Public Property Get TerritoryId() | |||
| TerritoryId = pTerritoryId | |||
| End Property | |||
| Public Property Let TerritoryId(val) | |||
| If IsNumeric(val) Then | |||
| pTerritoryId = CLng(val) | |||
| Else | |||
| pTerritoryId = val | |||
| End If | |||
| End Property | |||
| End Class | |||
| %> | |||
| @@ -1,103 +0,0 @@ | |||
| <% | |||
| ' Auto-generated POBO for table [Territories] | |||
| ' Generated on 1/17/2026 2:53:15 PM | |||
| ' Generator: GenerateRepo.vbs v1.0 | |||
| ' | |||
| ' Dependencies: core/helpers.asp (QuoteValue function) | |||
| Class POBO_Territories | |||
| ' Public array of all property names | |||
| Public Properties | |||
| Private pCoordinates | |||
| Private pDescription | |||
| Private pId | |||
| Private pName | |||
| Private Sub Class_Initialize() | |||
| pCoordinates = Null | |||
| pDescription = Null | |||
| pId = 0 | |||
| pName = Null | |||
| Properties = Array("Coordinates","Description","Id","Name") | |||
| End Sub | |||
| Public Property Get PrimaryKey() | |||
| PrimaryKey = "Id" | |||
| End Property | |||
| Public Property Get TableName() | |||
| TableName = "Territories" | |||
| End Property | |||
| Public Property Get Coordinates() | |||
| Coordinates = pCoordinates | |||
| End Property | |||
| Public Property Let Coordinates(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pCoordinates = CDbl(val) | |||
| Else | |||
| pCoordinates = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_Territories.Coordinates", "Invalid value for Coordinates: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get Description() | |||
| Description = pDescription | |||
| End Property | |||
| Public Property Let Description(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pDescription = CDbl(val) | |||
| Else | |||
| pDescription = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_Territories.Description", "Invalid value for Description: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get Id() | |||
| Id = pId | |||
| End Property | |||
| Public Property Let Id(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pId = CDbl(val) | |||
| Else | |||
| pId = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_Territories.Id", "Invalid value for Id: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| Public Property Get Name() | |||
| Name = pName | |||
| End Property | |||
| Public Property Let Name(val) | |||
| On Error Resume Next | |||
| If IsNumeric(val) Then | |||
| pName = CDbl(val) | |||
| Else | |||
| pName = val | |||
| End If | |||
| If Err.Number <> 0 Then | |||
| Err.Raise Err.Number, "POBO_Territories.Name", "Invalid value for Name: " & Err.Description | |||
| End If | |||
| On Error GoTo 0 | |||
| End Property | |||
| End Class | |||
| %> | |||
| @@ -1,176 +0,0 @@ | |||
| <% | |||
| ' Auto-generated Repository for table [Territories] | |||
| ' Generated on 1/17/2026 2:53:15 PM | |||
| ' Generator: GenerateRepo.vbs v1.0 | |||
| ' | |||
| ' Dependencies: | |||
| ' - core/lib.DAL.asp (DAL singleton for database access) | |||
| ' - core/lib.AutoMapper.asp (Automapper for object mapping) | |||
| ' - core/lib.Collections.asp (LinkedList_Class) | |||
| ' - core/lib.helpers.asp (KVUnzip, BuildOrderBy, QI, Destroy) | |||
| Class TerritoriesRepository_Class | |||
| Public Function FindByID(id) | |||
| Dim sql : sql = "Select [Coordinates], [Description], [Id], [Name] FROM [Territories] WHERE [Id] = ?" | |||
| Dim rs : Set rs = DAL.Query(sql, Array(id)) | |||
| If rs.EOF Then | |||
| Err.Raise 1, "TerritoriesRepository_Class", RecordNotFoundException("Id", id) | |||
| Else | |||
| Set FindByID = Automapper.AutoMap(rs, "POBO_Territories") | |||
| End If | |||
| Destroy rs | |||
| End Function | |||
| Public Function GetAll(orderBy) | |||
| Set GetAll = Find(Empty, orderBy) | |||
| End Function | |||
| Public Function Find(where_kvarray, order_string_or_array) | |||
| Dim sql : sql = "Select [Coordinates], [Description], [Id], [Name] FROM [Territories]" | |||
| Dim where_keys, where_values, i | |||
| If Not IsEmpty(where_kvarray) Then | |||
| KVUnzip where_kvarray, where_keys, where_values | |||
| If Not IsEmpty(where_keys) Then | |||
| sql = sql & " WHERE " | |||
| For i = 0 To UBound(where_keys) | |||
| If i > 0 Then sql = sql & " AND " | |||
| sql = sql & " " & QI(where_keys(i)) & " = ?" | |||
| Next | |||
| End If | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.Query(sql, where_values) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Do Until rs.EOF | |||
| list.Push Automapper.AutoMap(rs, "POBO_Territories") | |||
| rs.MoveNext | |||
| Loop | |||
| Set Find = list | |||
| Destroy rs | |||
| End Function | |||
| Public Function FindPaged(where_kvarray, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Coordinates], [Description], [Id], [Name] FROM [Territories]" | |||
| Dim where_keys, where_values, i | |||
| If Not IsEmpty(where_kvarray) Then | |||
| KVUnzip where_kvarray, where_keys, where_values | |||
| If Not IsEmpty(where_keys) Then | |||
| sql = sql & " WHERE " | |||
| For i = 0 To UBound(where_keys) | |||
| If i > 0 Then sql = sql & " AND " | |||
| sql = sql & " " & QI(where_keys(i)) & " = ?" | |||
| Next | |||
| End If | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, where_values, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set FindPaged = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Public Function SearchTablePaged(columns_array, search_value, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | |||
| Dim sql : sql = "Select [Coordinates], [Description], [Id], [Name] FROM [Territories]" | |||
| Dim i, params() | |||
| If IsArray(columns_array) And UBound(columns_array) >= 0 Then | |||
| sql = sql & " WHERE " | |||
| ReDim params(UBound(columns_array)) | |||
| For i = 0 To UBound(columns_array) | |||
| If i > 0 Then sql = sql & " OR " | |||
| sql = sql & " " & QI(columns_array(i)) & " LIKE ?" | |||
| params(i) = "%" & search_value & "%" | |||
| Next | |||
| End If | |||
| sql = sql & BuildOrderBy(order_string_or_array, "[Id]") | |||
| Dim rs : Set rs = DAL.PagedQuery(sql, params, per_page, page_num) | |||
| If Not rs.EOF Then | |||
| rs.PageSize = per_page | |||
| rs.AbsolutePage = page_num | |||
| page_count = rs.PageCount | |||
| record_count = rs.RecordCount | |||
| End If | |||
| Set SearchTablePaged = PagedList(rs, per_page) | |||
| Destroy rs | |||
| End Function | |||
| Private Function PagedList(rs, per_page) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Dim x : x = 0 | |||
| Do While (per_page <= 0 Or x < per_page) And Not rs.EOF | |||
| list.Push Automapper.AutoMap(rs, "POBO_Territories") | |||
| x = x + 1 | |||
| rs.MoveNext | |||
| Loop | |||
| Set PagedList = list | |||
| End Function | |||
| Public Sub AddNew(ByRef model) | |||
| Dim sql : sql = "INSERT INTO [Territories] ([Coordinates], [Description], [Name]) VALUES (?, ?, ?)" | |||
| DAL.[Execute] sql, Array(model.Coordinates, model.Description, model.Name) | |||
| ' Retrieve the newly inserted ID | |||
| On Error Resume Next | |||
| Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty) | |||
| If Err.Number <> 0 Then | |||
| ' Fallback for Access databases | |||
| Err.Clear | |||
| Set rsId = DAL.Query("SELECT TOP 1 [Id] FROM [Territories] ORDER BY [Id] DESC", Empty) | |||
| End If | |||
| On Error GoTo 0 | |||
| If Not rsId.EOF Then | |||
| If Not IsNull(rsId(0)) Then model.Id = rsId(0) | |||
| End If | |||
| Destroy rsId | |||
| End Sub | |||
| Public Sub Update(model) | |||
| Dim sql : sql = "UPDATE [Territories] SET [Coordinates] = ?, [Description] = ?, [Name] = ? WHERE [Id] = ?" | |||
| DAL.[Execute] sql, Array(model.Coordinates, model.Description, model.Name, model.Id) | |||
| End Sub | |||
| Public Sub Delete(id) | |||
| Dim sql : sql = "DELETE FROM [Territories] WHERE [Id] = ?" | |||
| DAL.[Execute] sql, Array(id) | |||
| End Sub | |||
| Private Function RecordNotFoundException(ByVal field_name, ByVal field_val) | |||
| RecordNotFoundException = "Territories record was not found with " & field_name & " = '" & field_val & "'." | |||
| End Function | |||
| Private Function QI(name) | |||
| QI = "[" & Replace(CStr(name), "]", "]]") & "]" | |||
| End Function | |||
| Private Function BuildOrderBy(orderArg, defaultCol) | |||
| Dim s : s = "" | |||
| If IsEmpty(orderArg) Or IsNull(orderArg) Or orderArg = "" Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf IsArray(orderArg) Then | |||
| Dim i : s = " ORDER BY " | |||
| For i = 0 To UBound(orderArg) | |||
| If i > 0 Then s = s & ", " | |||
| s = s & QI(orderArg(i)) | |||
| Next | |||
| Else | |||
| s = " ORDER BY " & QI(orderArg) | |||
| End If | |||
| BuildOrderBy = s | |||
| End Function | |||
| End Class | |||
| Dim TerritoriesRepository__Singleton | |||
| Function TerritoriesRepository() | |||
| If IsEmpty(TerritoriesRepository__Singleton) Then | |||
| Set TerritoriesRepository__Singleton = new TerritoriesRepository_Class | |||
| End If | |||
| Set TerritoriesRepository = TerritoriesRepository__Singleton | |||
| End Function | |||
| %> | |||
| @@ -1,153 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/households">Households</a></li> | |||
| <li class="breadcrumb-item active" aria-current="page">New Household</li> | |||
| </ol> | |||
| </nav> | |||
| <h1>New Household</h1> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="card"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/households"> | |||
| <div class="row"> | |||
| <div class="col-md-6"> | |||
| <div class="mb-3"> | |||
| <label for="Address" class="form-label">Address <span class="text-danger">*</span></label> | |||
| <input type="text" class="form-control" id="Address" name="Address" required maxlength="255"> | |||
| </div> | |||
| <div class="row"> | |||
| <div class="col-md-4"> | |||
| <div class="mb-3"> | |||
| <label for="StreetNumber" class="form-label">Street Number</label> | |||
| <input type="number" class="form-control" id="StreetNumber" name="StreetNumber" value="0"> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-8"> | |||
| <div class="mb-3"> | |||
| <label for="StreetName" class="form-label">Street Name</label> | |||
| <input type="text" class="form-control" id="StreetName" name="StreetName" maxlength="255"> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="TerritoryId" class="form-label">Territory <span class="text-danger">*</span></label> | |||
| <select class="form-select" id="TerritoryId" name="TerritoryId" required> | |||
| <option value="">Select a territory...</option> | |||
| <% | |||
| Dim tIter, tItem | |||
| Set tIter = HouseholdController.territoriesList.Iterator() | |||
| Do While tIter.HasNext() | |||
| Set tItem = tIter.GetNext() | |||
| %> | |||
| <option value="<%= tItem.Id %>" <% If tItem.Id = HouseholdController.household.TerritoryId Then Response.Write "selected" End If %>><%= Server.HTMLEncode(tItem.Name) %></option> | |||
| <% | |||
| Loop | |||
| %> | |||
| </select> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <div class="form-check"> | |||
| <input class="form-check-input" type="checkbox" id="IsBusiness" name="IsBusiness" value="1"> | |||
| <label class="form-check-label" for="IsBusiness"> | |||
| This household is a business | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="card border-danger-subtle mb-3"> | |||
| <div class="card-body"> | |||
| <div class="form-check mb-3"> | |||
| <input class="form-check-input" type="checkbox" id="DoNotCall" name="DoNotCall" value="1"> | |||
| <label class="form-check-label" for="DoNotCall"> | |||
| Mark this household as Do Not Call | |||
| </label> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="DoNotCallDate" class="form-label">Do Not Call Date</label> | |||
| <input type="date" class="form-control" id="DoNotCallDate" name="DoNotCallDate"> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="DoNotCallNotes" class="form-label">Do Not Call Notes</label> | |||
| <textarea class="form-control" id="DoNotCallNotes" name="DoNotCallNotes" rows="3"></textarea> | |||
| </div> | |||
| <div class="mb-0"> | |||
| <label for="DoNotCallPrivateNotes" class="form-label">Private Do Not Call Notes</label> | |||
| <textarea class="form-control" id="DoNotCallPrivateNotes" name="DoNotCallPrivateNotes" rows="3"></textarea> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="row"> | |||
| <div class="col-md-6"> | |||
| <div class="mb-3"> | |||
| <label for="Latitude" class="form-label">Latitude</label> | |||
| <input type="text" class="form-control" id="Latitude" name="Latitude" placeholder="-33.8688"> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-6"> | |||
| <div class="mb-3"> | |||
| <label for="Longitude" class="form-label">Longitude</label> | |||
| <input type="text" class="form-control" id="Longitude" name="Longitude" placeholder="151.2093"> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button type="submit" class="btn btn-primary">Create Household</button> | |||
| <a href="/households" class="btn btn-secondary">Cancel</a> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-6"> | |||
| <label class="form-label">Location (click to set coordinates)</label> | |||
| <div id="map" style="height: 350px; width: 100%; border-radius: 8px;"></div> | |||
| <small class="text-muted">Click on the map to set latitude and longitude.</small> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(GetAppSetting("GoogleMapsApiKey")) %>&callback=initMap" async defer></script> | |||
| <script> | |||
| var map, marker; | |||
| function initMap() { | |||
| var defaultCenter = { lat: -33.8688, lng: 151.2093 }; | |||
| map = new google.maps.Map(document.getElementById('map'), { | |||
| zoom: 12, | |||
| center: defaultCenter, | |||
| mapTypeId: 'roadmap' | |||
| }); | |||
| // Click to place marker and set coordinates | |||
| map.addListener('click', function(event) { | |||
| placeMarker(event.latLng); | |||
| }); | |||
| } | |||
| function placeMarker(location) { | |||
| if (marker) { | |||
| marker.setPosition(location); | |||
| } else { | |||
| marker = new google.maps.Marker({ | |||
| position: location, | |||
| map: map | |||
| }); | |||
| } | |||
| document.getElementById('Latitude').value = location.lat().toFixed(6); | |||
| document.getElementById('Longitude').value = location.lng().toFixed(6); | |||
| } | |||
| </script> | |||
| @@ -1,190 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/households">Households</a></li> | |||
| <li class="breadcrumb-item"><a href="/households/<%= HouseholdController.household.Id %>"><%= Server.HTMLEncode(HouseholdController.household.Address) %></a></li> | |||
| <li class="breadcrumb-item active" aria-current="page">Edit</li> | |||
| </ol> | |||
| </nav> | |||
| <h1>Edit Household</h1> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="card"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/households/<%= HouseholdController.household.Id %>"> | |||
| <div class="row"> | |||
| <div class="col-md-6"> | |||
| <div class="mb-3"> | |||
| <label for="Address" class="form-label">Address <span class="text-danger">*</span></label> | |||
| <input type="text" class="form-control" id="Address" name="Address" required maxlength="255" value="<%= Server.HTMLEncode(HouseholdController.household.Address) %>"> | |||
| </div> | |||
| <div class="row"> | |||
| <div class="col-md-4"> | |||
| <div class="mb-3"> | |||
| <label for="StreetNumber" class="form-label">Street Number</label> | |||
| <input type="number" class="form-control" id="StreetNumber" name="StreetNumber" value="<%= HouseholdController.household.StreetNumber %>"> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-8"> | |||
| <div class="mb-3"> | |||
| <label for="StreetName" class="form-label">Street Name</label> | |||
| <input type="text" class="form-control" id="StreetName" name="StreetName" maxlength="255" value="<%= Server.HTMLEncode(HouseholdController.household.StreetName & "") %>"> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="TerritoryId" class="form-label">Territory <span class="text-danger">*</span></label> | |||
| <select class="form-select" id="TerritoryId" name="TerritoryId" required> | |||
| <option value="">Select a territory...</option> | |||
| <% | |||
| Dim tIter, tItem | |||
| Set tIter = HouseholdController.territoriesList.Iterator() | |||
| Do While tIter.HasNext() | |||
| Set tItem = tIter.GetNext() | |||
| %> | |||
| <option value="<%= tItem.Id %>" <% If tItem.Id = HouseholdController.household.TerritoryId Then Response.Write "selected" End If %>><%= Server.HTMLEncode(tItem.Name) %></option> | |||
| <% | |||
| Loop | |||
| %> | |||
| </select> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <div class="form-check"> | |||
| <input class="form-check-input" type="checkbox" id="IsBusiness" name="IsBusiness" value="1" | |||
| <% If HouseholdController.household.IsBusiness = 1 Then Response.Write "checked" End If %>> | |||
| <label class="form-check-label" for="IsBusiness"> | |||
| This household is a business | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <% | |||
| Dim doNotCallDateVal, doNotCallNotesVal, doNotCallPrivateNotesVal | |||
| doNotCallDateVal = "" | |||
| doNotCallNotesVal = "" | |||
| doNotCallPrivateNotesVal = "" | |||
| If IsDate(HouseholdController.household.DoNotCallDate) Then | |||
| doNotCallDateVal = Year(HouseholdController.household.DoNotCallDate) & "-" & _ | |||
| Right("0" & Month(HouseholdController.household.DoNotCallDate), 2) & "-" & _ | |||
| Right("0" & Day(HouseholdController.household.DoNotCallDate), 2) | |||
| End If | |||
| If Not IsNull(HouseholdController.household.DoNotCallNotes) Then | |||
| doNotCallNotesVal = HouseholdController.household.DoNotCallNotes | |||
| End If | |||
| If Not IsNull(HouseholdController.household.DoNotCallPrivateNotes) Then | |||
| doNotCallPrivateNotesVal = HouseholdController.household.DoNotCallPrivateNotes | |||
| End If | |||
| %> | |||
| <div class="card border-danger-subtle mb-3"> | |||
| <div class="card-body"> | |||
| <div class="form-check mb-3"> | |||
| <input class="form-check-input" type="checkbox" id="DoNotCall" name="DoNotCall" value="1" | |||
| <% If HouseholdController.household.DoNotCall = 1 Then Response.Write "checked" End If %>> | |||
| <label class="form-check-label" for="DoNotCall"> | |||
| Mark this household as Do Not Call | |||
| </label> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="DoNotCallDate" class="form-label">Do Not Call Date</label> | |||
| <input type="date" class="form-control" id="DoNotCallDate" name="DoNotCallDate" value="<%= doNotCallDateVal %>"> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="DoNotCallNotes" class="form-label">Do Not Call Notes</label> | |||
| <textarea class="form-control" id="DoNotCallNotes" name="DoNotCallNotes" rows="3"><%= Server.HTMLEncode(doNotCallNotesVal) %></textarea> | |||
| </div> | |||
| <div class="mb-0"> | |||
| <label for="DoNotCallPrivateNotes" class="form-label">Private Do Not Call Notes</label> | |||
| <textarea class="form-control" id="DoNotCallPrivateNotes" name="DoNotCallPrivateNotes" rows="3"><%= Server.HTMLEncode(doNotCallPrivateNotesVal) %></textarea> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="row"> | |||
| <div class="col-md-6"> | |||
| <div class="mb-3"> | |||
| <label for="Latitude" class="form-label">Latitude</label> | |||
| <input type="text" class="form-control" id="Latitude" name="Latitude" value="<%= Server.HTMLEncode(HouseholdController.household.Latitude & "") %>"> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-6"> | |||
| <div class="mb-3"> | |||
| <label for="Longitude" class="form-label">Longitude</label> | |||
| <input type="text" class="form-control" id="Longitude" name="Longitude" value="<%= Server.HTMLEncode(HouseholdController.household.Longitude & "") %>"> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button type="submit" class="btn btn-primary">Update Household</button> | |||
| <a href="/households/<%= HouseholdController.household.Id %>" class="btn btn-secondary">Cancel</a> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-6"> | |||
| <label class="form-label">Location (click to update coordinates)</label> | |||
| <div id="map" style="height: 350px; width: 100%; border-radius: 8px;"></div> | |||
| <small class="text-muted">Click on the map to update latitude and longitude.</small> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script> | |||
| var existingLat = <%= IIf(HouseholdController.household.Latitude & "" <> "", HouseholdController.household.Latitude, "null") %>; | |||
| var existingLng = <%= IIf(HouseholdController.household.Longitude & "" <> "", HouseholdController.household.Longitude, "null") %>; | |||
| </script> | |||
| <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(GetAppSetting("GoogleMapsApiKey")) %>&callback=initMap" async defer></script> | |||
| <script> | |||
| var map, marker; | |||
| function initMap() { | |||
| var defaultCenter = { lat: -33.8688, lng: 151.2093 }; | |||
| // Use existing coordinates if available | |||
| if (existingLat !== null && existingLng !== null) { | |||
| defaultCenter = { lat: existingLat, lng: existingLng }; | |||
| } | |||
| map = new google.maps.Map(document.getElementById('map'), { | |||
| zoom: existingLat !== null ? 17 : 12, | |||
| center: defaultCenter, | |||
| mapTypeId: 'roadmap' | |||
| }); | |||
| // Place marker if coordinates exist | |||
| if (existingLat !== null && existingLng !== null) { | |||
| marker = new google.maps.Marker({ | |||
| position: defaultCenter, | |||
| map: map | |||
| }); | |||
| } | |||
| // Click to place/move marker and set coordinates | |||
| map.addListener('click', function(event) { | |||
| placeMarker(event.latLng); | |||
| }); | |||
| } | |||
| function placeMarker(location) { | |||
| if (marker) { | |||
| marker.setPosition(location); | |||
| } else { | |||
| marker = new google.maps.Marker({ | |||
| position: location, | |||
| map: map | |||
| }); | |||
| } | |||
| document.getElementById('Latitude').value = location.lat().toFixed(6); | |||
| document.getElementById('Longitude').value = location.lng().toFixed(6); | |||
| } | |||
| </script> | |||
| @@ -1,187 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <div class="d-flex justify-content-between align-items-center mb-4"> | |||
| <h1>Households</h1> | |||
| <a href="/households/new" class="btn btn-primary">New Household</a> | |||
| </div> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <!-- Search and Filter Form --> | |||
| <div class="card mb-4"> | |||
| <div class="card-body"> | |||
| <form method="get" action="/households" class="row g-3"> | |||
| <div class="col-md-5"> | |||
| <div class="input-group"> | |||
| <input type="text" class="form-control" name="q" placeholder="Search by address or street name..." value="<%= Server.HTMLEncode(HouseholdController.searchTerm) %>"> | |||
| <button class="btn btn-outline-primary" type="submit">Search</button> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-4"> | |||
| <select name="territory" class="form-select" onchange="this.form.submit()"> | |||
| <option value="">All Territories</option> | |||
| <% | |||
| Dim tIter, tItem | |||
| Set tIter = HouseholdController.territoriesList.Iterator() | |||
| Do While tIter.HasNext() | |||
| Set tItem = tIter.GetNext() | |||
| %> | |||
| <option value="<%= tItem.Id %>" <% If tItem.Id = HouseholdController.filterTerritoryId Then Response.Write "selected" End If %>><%= Server.HTMLEncode(tItem.Name) %></option> | |||
| <% | |||
| Loop | |||
| %> | |||
| </select> | |||
| </div> | |||
| <div class="col-md-2"> | |||
| <select name="dnc" class="form-select" onchange="this.form.submit()"> | |||
| <option value="" <% If HouseholdController.filterDoNotCall = -1 Then Response.Write "selected" End If %>>All DNC Status</option> | |||
| <option value="1" <% If HouseholdController.filterDoNotCall = 1 Then Response.Write "selected" End If %>>Do Not Call</option> | |||
| <option value="0" <% If HouseholdController.filterDoNotCall = 0 Then Response.Write "selected" End If %>>Callable</option> | |||
| </select> | |||
| </div> | |||
| <div class="col-md-1 text-end"> | |||
| <span class="text-muted"> | |||
| <%= HouseholdController.recordCount %> household(s) | |||
| </span> | |||
| <% If HouseholdController.searchTerm <> "" Or HouseholdController.filterTerritoryId > 0 Or HouseholdController.filterDoNotCall > -1 Then %> | |||
| <a href="/households" class="btn btn-sm btn-outline-secondary ms-2">Clear</a> | |||
| <% End If %> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| <div class="table-responsive"> | |||
| <table class="table table-striped table-hover"> | |||
| <thead class="table-dark"> | |||
| <tr> | |||
| <th>ID</th> | |||
| <th>Address</th> | |||
| <th>Type</th> | |||
| <th>DNC</th> | |||
| <th>Street</th> | |||
| <th>Territory</th> | |||
| <th>Actions</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <% | |||
| Dim iter, h | |||
| Set iter = HouseholdController.households.Iterator() | |||
| If Not iter.HasNext() Then | |||
| %> | |||
| <tr> | |||
| <td colspan="7" class="text-center text-muted py-4"> | |||
| <% If HouseholdController.searchTerm <> "" Then %> | |||
| No households found matching "<%= Server.HTMLEncode(HouseholdController.searchTerm) %>" | |||
| <% ElseIf HouseholdController.filterTerritoryId > 0 Then %> | |||
| No households found in this territory. | |||
| <% Else %> | |||
| No households found. <a href="/households/new">Create one</a> | |||
| <% End If %> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Else | |||
| Do While iter.HasNext() | |||
| Set h = iter.GetNext() | |||
| %> | |||
| <tr> | |||
| <td><%= h.Id %></td> | |||
| <td><%= Server.HTMLEncode(h.Address) %></td> | |||
| <td> | |||
| <% If h.IsBusiness = 1 Then %> | |||
| <span class="badge bg-info">Business</span> | |||
| <% Else %> | |||
| <span class="badge bg-secondary">Residential</span> | |||
| <% End If %> | |||
| </td> | |||
| <td> | |||
| <% If h.DoNotCall = 1 Then %> | |||
| <span class="badge bg-danger">Do Not Call</span> | |||
| <% If IsDate(h.DoNotCallDate) Then %> | |||
| <div><small class="text-muted"><%= FormatDateTime(h.DoNotCallDate, 2) %></small></div> | |||
| <% End If %> | |||
| <% Else %> | |||
| <span class="text-muted">No</span> | |||
| <% End If %> | |||
| </td> | |||
| <td><%= h.StreetNumber %> <%= Server.HTMLEncode(h.StreetName) %></td> | |||
| <td> | |||
| <a href="/territories/<%= h.TerritoryId %>"> | |||
| <% | |||
| If HouseholdController.territoryNamesById.Exists(CStr(h.TerritoryId)) Then | |||
| Response.Write Server.HTMLEncode(HouseholdController.territoryNamesById(CStr(h.TerritoryId))) | |||
| Else | |||
| Response.Write "Territory " & h.TerritoryId | |||
| End If | |||
| %> | |||
| </a> | |||
| </td> | |||
| <td> | |||
| <a href="/households/<%= h.Id %>" class="btn btn-sm btn-info">View</a> | |||
| <a href="/households/<%= h.Id %>/edit" class="btn btn-sm btn-warning">Edit</a> | |||
| <form method="post" action="/households/<%= h.Id %>/delete" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this household?');"> | |||
| <button type="submit" class="btn btn-sm btn-danger">Delete</button> | |||
| </form> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Loop | |||
| End If | |||
| %> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| <!-- Pagination --> | |||
| <% If HouseholdController.pageCount > 1 Then %> | |||
| <% | |||
| Dim baseUrl, pg, startPage, endPage | |||
| baseUrl = "/households?" | |||
| If HouseholdController.searchTerm <> "" Then | |||
| baseUrl = baseUrl & "q=" & Server.URLEncode(HouseholdController.searchTerm) & "&" | |||
| End If | |||
| If HouseholdController.filterTerritoryId > 0 Then | |||
| baseUrl = baseUrl & "territory=" & HouseholdController.filterTerritoryId & "&" | |||
| End If | |||
| If HouseholdController.filterDoNotCall > -1 Then | |||
| baseUrl = baseUrl & "dnc=" & HouseholdController.filterDoNotCall & "&" | |||
| End If | |||
| ' Calculate pagination range (show 5 pages at a time) | |||
| startPage = HouseholdController.currentPage - 2 | |||
| If startPage < 1 Then startPage = 1 | |||
| endPage = startPage + 4 | |||
| If endPage > HouseholdController.pageCount Then | |||
| endPage = HouseholdController.pageCount | |||
| startPage = endPage - 4 | |||
| If startPage < 1 Then startPage = 1 | |||
| End If | |||
| %> | |||
| <nav aria-label="Household pagination"> | |||
| <ul class="pagination justify-content-center"> | |||
| <li class="page-item <% If HouseholdController.currentPage = 1 Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=1">« First</a> | |||
| </li> | |||
| <li class="page-item <% If HouseholdController.currentPage = 1 Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= HouseholdController.currentPage - 1 %>">‹ Prev</a> | |||
| </li> | |||
| <% For pg = startPage To endPage %> | |||
| <li class="page-item <% If pg = HouseholdController.currentPage Then Response.Write "active" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= pg %>"><%= pg %></a> | |||
| </li> | |||
| <% Next %> | |||
| <li class="page-item <% If HouseholdController.currentPage >= HouseholdController.pageCount Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= HouseholdController.currentPage + 1 %>">Next ›</a> | |||
| </li> | |||
| <li class="page-item <% If HouseholdController.currentPage >= HouseholdController.pageCount Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= HouseholdController.pageCount %>">Last »</a> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| <p class="text-center text-muted"> | |||
| Page <%= HouseholdController.currentPage %> of <%= HouseholdController.pageCount %> | |||
| </p> | |||
| <% End If %> | |||
| </div> | |||
| @@ -1,291 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <% | |||
| Dim dncNotesVal, dncPrivateNotesVal | |||
| dncNotesVal = "" | |||
| dncPrivateNotesVal = "" | |||
| If Not IsNull(HouseholdController.household.DoNotCallNotes) Then dncNotesVal = HouseholdController.household.DoNotCallNotes | |||
| If Not IsNull(HouseholdController.household.DoNotCallPrivateNotes) Then dncPrivateNotesVal = HouseholdController.household.DoNotCallPrivateNotes | |||
| %> | |||
| <nav aria-label="breadcrumb"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/households">Households</a></li> | |||
| <li class="breadcrumb-item active" aria-current="page"><%= Server.HTMLEncode(HouseholdController.household.Address) %></li> | |||
| </ol> | |||
| </nav> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="card"> | |||
| <div class="card-header d-flex justify-content-between align-items-center"> | |||
| <h2 class="mb-0"><%= Server.HTMLEncode(HouseholdController.household.Address) %></h2> | |||
| <div> | |||
| <a href="/households/<%= HouseholdController.household.Id %>/edit" class="btn btn-warning">Edit</a> | |||
| <form method="post" action="/households/<%= HouseholdController.household.Id %>/delete" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this household?');"> | |||
| <button type="submit" class="btn btn-danger">Delete</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| <div class="card-body"> | |||
| <div class="row"> | |||
| <div class="col-md-6"> | |||
| <dl> | |||
| <dt>ID</dt> | |||
| <dd><%= HouseholdController.household.Id %></dd> | |||
| <dt>Address</dt> | |||
| <dd><%= Server.HTMLEncode(HouseholdController.household.Address) %></dd> | |||
| <dt>Street Number</dt> | |||
| <dd><%= HouseholdController.household.StreetNumber %></dd> | |||
| <dt>Street Name</dt> | |||
| <dd><%= Server.HTMLEncode(HouseholdController.household.StreetName & "") %></dd> | |||
| <dt>Type</dt> | |||
| <dd> | |||
| <% If HouseholdController.household.IsBusiness = 1 Then %> | |||
| <span class="badge bg-info">Business</span> | |||
| <% Else %> | |||
| <span class="badge bg-secondary">Residential</span> | |||
| <% End If %> | |||
| <% If HouseholdController.household.DoNotCall = 1 Then %> | |||
| <span class="badge bg-danger ms-2">Do Not Call</span> | |||
| <% End If %> | |||
| </dd> | |||
| <dt>Do Not Call Date</dt> | |||
| <dd> | |||
| <% If IsDate(HouseholdController.household.DoNotCallDate) Then %> | |||
| <%= FormatDateTime(HouseholdController.household.DoNotCallDate, 2) %> | |||
| <% Else %> | |||
| <span class="text-muted">Not set</span> | |||
| <% End If %> | |||
| </dd> | |||
| <dt>Do Not Call Notes</dt> | |||
| <dd> | |||
| <% If Trim(dncNotesVal) <> "" Then %> | |||
| <%= Server.HTMLEncode(dncNotesVal) %> | |||
| <% Else %> | |||
| <span class="text-muted">None</span> | |||
| <% End If %> | |||
| </dd> | |||
| <dt>Private DNC Notes</dt> | |||
| <dd> | |||
| <% If Trim(dncPrivateNotesVal) <> "" Then %> | |||
| <%= Server.HTMLEncode(dncPrivateNotesVal) %> | |||
| <% Else %> | |||
| <span class="text-muted">None</span> | |||
| <% End If %> | |||
| </dd> | |||
| <dt>Territory</dt> | |||
| <dd><a href="/territories/<%= HouseholdController.household.TerritoryId %>">Territory <%= HouseholdController.household.TerritoryId %></a></dd> | |||
| </dl> | |||
| </div> | |||
| <div class="col-md-6"> | |||
| <dl> | |||
| <dt>Latitude</dt> | |||
| <dd><%= Server.HTMLEncode(HouseholdController.household.Latitude & "") %></dd> | |||
| <dt>Longitude</dt> | |||
| <dd><%= Server.HTMLEncode(HouseholdController.household.Longitude & "") %></dd> | |||
| </dl> | |||
| <% If HouseholdController.household.Latitude <> "" And HouseholdController.household.Longitude <> "" Then %> | |||
| <div id="map" style="height: 250px; width: 100%; border-radius: 8px;"></div> | |||
| <% End If %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <!-- Householder Names Section --> | |||
| <div class="card mt-4"> | |||
| <div class="card-header d-flex justify-content-between align-items-center"> | |||
| <h5 class="mb-0">Householder Names</h5> | |||
| <a href="/householder-names/new?household=<%= HouseholdController.household.Id %>" class="btn btn-sm btn-success">Add Name</a> | |||
| </div> | |||
| <div class="card-body p-0"> | |||
| <% | |||
| Dim hnIter, hn | |||
| Set hnIter = HouseholdController.householderNames.Iterator() | |||
| If Not hnIter.HasNext() Then | |||
| %> | |||
| <div class="p-3 text-center text-muted"> | |||
| No householder names recorded. <a href="/householder-names/new?household=<%= HouseholdController.household.Id %>">Add one</a> | |||
| </div> | |||
| <% | |||
| Else | |||
| %> | |||
| <table class="table table-hover mb-0"> | |||
| <thead class="table-light"> | |||
| <tr> | |||
| <th>Name</th> | |||
| <th>Type</th> | |||
| <th style="width: 180px;">Letter Status</th> | |||
| <th style="width: 100px;">Actions</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <% | |||
| Do While hnIter.HasNext() | |||
| Set hn = hnIter.GetNext() | |||
| %> | |||
| <tr> | |||
| <td> | |||
| <a href="/householder-names/<%= hn.Id %>"><%= Server.HTMLEncode(hn.Name & "") %></a> | |||
| </td> | |||
| <td> | |||
| <% If HouseholdController.household.IsBusiness = 1 Then %> | |||
| <span class="badge bg-info">Business</span> | |||
| <% Else %> | |||
| <span class="badge bg-secondary">Residential</span> | |||
| <% End If %> | |||
| </td> | |||
| <td> | |||
| <form method="post" action="/households/<%= HouseholdController.household.Id %>/mark-returned" style="display:inline;"> | |||
| <input type="hidden" name="householder_id" value="<%= hn.Id %>"> | |||
| <% If hn.LetterReturned = 1 Then %> | |||
| <button type="submit" class="btn btn-sm btn-warning" title="Click to mark as NOT returned"> | |||
| Returned | |||
| <% If Year(hn.ReturnDate) > 1970 Then %> | |||
| <small>(<%= FormatDateTime(hn.ReturnDate, 2) %>)</small> | |||
| <% End If %> | |||
| </button> | |||
| <% Else %> | |||
| <button type="submit" class="btn btn-sm btn-outline-success" title="Click to mark as returned"> | |||
| Not Returned | |||
| </button> | |||
| <% End If %> | |||
| </form> | |||
| </td> | |||
| <td> | |||
| <a href="/householder-names/<%= hn.Id %>/edit" class="btn btn-sm btn-outline-warning" title="Edit">Edit</a> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Loop | |||
| %> | |||
| </tbody> | |||
| </table> | |||
| <% | |||
| End If | |||
| %> | |||
| </div> | |||
| </div> | |||
| <div class="mt-3"> | |||
| <a href="/households" class="btn btn-secondary">Back to List</a> | |||
| <a href="/households?territory=<%= HouseholdController.household.TerritoryId %>" class="btn btn-outline-primary">View Territory Households</a> | |||
| </div> | |||
| </div> | |||
| <% If HouseholdController.household.Latitude <> "" And HouseholdController.household.Longitude <> "" Then %> | |||
| <% | |||
| Dim mapProvider, googleMapsKey, mapTilerKey, mapTilerStyle, mapTilerSdkJsUrl, mapTilerSdkCssUrl | |||
| mapProvider = LCase(Trim(GetAppSetting("MapProvider") & "")) | |||
| If mapProvider <> "maptiler" Then mapProvider = "google" | |||
| googleMapsKey = Trim(GetAppSetting("GoogleMapsApiKey") & "") | |||
| mapTilerKey = Trim(GetAppSetting("MapTilerApiKey") & "") | |||
| mapTilerStyle = Trim(GetAppSetting("MapTilerStyle") & "") | |||
| If mapTilerStyle = "" Or LCase(mapTilerStyle) = "nothing" Then mapTilerStyle = "streets-v2" | |||
| mapTilerSdkJsUrl = Trim(GetAppSetting("MapTilerSdkJsUrl") & "") | |||
| mapTilerSdkCssUrl = Trim(GetAppSetting("MapTilerSdkCssUrl") & "") | |||
| If mapTilerSdkJsUrl = "" Or LCase(mapTilerSdkJsUrl) = "nothing" Then mapTilerSdkJsUrl = "https://cdn.maptiler.com/maptiler-sdk-js/v3.0.0/maptiler-sdk.umd.min.js" | |||
| If mapTilerSdkCssUrl = "" Or LCase(mapTilerSdkCssUrl) = "nothing" Then mapTilerSdkCssUrl = "https://cdn.maptiler.com/maptiler-sdk-js/v3.0.0/maptiler-sdk.css" | |||
| %> | |||
| <script> | |||
| var householdLat = <%= HouseholdController.household.Latitude %>; | |||
| var householdLng = <%= HouseholdController.household.Longitude %>; | |||
| var mapProvider = "<%= Replace(mapProvider, """", "\""") %>"; | |||
| var googleMapsKey = "<%= Replace(googleMapsKey, """", "\""") %>"; | |||
| var mapTilerKey = "<%= Replace(mapTilerKey, """", "\""") %>"; | |||
| var mapTilerStyle = "<%= Replace(mapTilerStyle, """", "\""") %>"; | |||
| </script> | |||
| <% If mapProvider = "maptiler" Then %> | |||
| <link rel="stylesheet" href="<%= Server.HTMLEncode(mapTilerSdkCssUrl) %>" /> | |||
| <script src="<%= Server.HTMLEncode(mapTilerSdkJsUrl) %>"></script> | |||
| <% Else %> | |||
| <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(googleMapsKey) %>&callback=initMap" async defer></script> | |||
| <% End If %> | |||
| <script> | |||
| function setMapMessage(message) { | |||
| var mapEl = document.getElementById('map'); | |||
| if (mapEl) { | |||
| mapEl.innerHTML = '<div class="alert alert-info">' + message + '</div>'; | |||
| } | |||
| } | |||
| function initMap() { | |||
| try { | |||
| if (mapProvider === 'maptiler') { | |||
| if (!mapTilerKey || mapTilerKey === 'nothing') { | |||
| setMapMessage('MapTiler API key is missing.'); | |||
| return; | |||
| } | |||
| if (typeof maptilersdk === 'undefined') { | |||
| setMapMessage('MapTiler SDK failed to load.'); | |||
| return; | |||
| } | |||
| maptilersdk.config.apiKey = mapTilerKey; | |||
| var styleUrl = 'https://api.maptiler.com/maps/' + encodeURIComponent(mapTilerStyle) + '/style.json?key=' + encodeURIComponent(mapTilerKey); | |||
| var location = [parseFloat(householdLng), parseFloat(householdLat)]; | |||
| var map = new maptilersdk.Map({ | |||
| container: 'map', | |||
| style: styleUrl, | |||
| center: location, | |||
| zoom: 17 | |||
| }); | |||
| new maptilersdk.Marker() | |||
| .setLngLat(location) | |||
| .addTo(map); | |||
| return; | |||
| } | |||
| var location = { lat: householdLat, lng: householdLng }; | |||
| var map = new google.maps.Map(document.getElementById('map'), { | |||
| zoom: 17, | |||
| center: location, | |||
| mapTypeId: 'roadmap' | |||
| }); | |||
| var marker = new google.maps.Marker({ | |||
| position: location, | |||
| map: map, | |||
| title: '<%= Replace(HouseholdController.household.Address, "'", "\'") %>' | |||
| }); | |||
| } catch (e) { | |||
| setMapMessage('Map error: ' + e.message); | |||
| } | |||
| } | |||
| if (mapProvider === 'maptiler') { | |||
| if (document.readyState === 'loading') { | |||
| document.addEventListener('DOMContentLoaded', initMap); | |||
| } else { | |||
| initMap(); | |||
| } | |||
| setTimeout(function() { | |||
| if (typeof maptilersdk === 'undefined') { | |||
| setMapMessage('MapTiler SDK failed to load.'); | |||
| } | |||
| }, 2000); | |||
| } else { | |||
| setTimeout(function() { | |||
| if (typeof google === 'undefined') { | |||
| setMapMessage('Google Maps failed to load.'); | |||
| } | |||
| }, 2000); | |||
| } | |||
| </script> | |||
| <% End If %> | |||
| @@ -1,86 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb" class="mb-3"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/householder-names">Householder Names</a></li> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <li class="breadcrumb-item"><a href="/households/<%= HouseholderNameController.household.Id %>"><%= Server.HTMLEncode(HouseholderNameController.household.Address) %></a></li> | |||
| <% End If %> | |||
| <li class="breadcrumb-item active">New</li> | |||
| </ol> | |||
| </nav> | |||
| <h1 class="mb-4"> | |||
| New Householder Name | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <small class="text-muted">for <%= Server.HTMLEncode(HouseholderNameController.household.Address) %></small> | |||
| <% End If %> | |||
| </h1> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="row"> | |||
| <div class="col-md-8"> | |||
| <div class="card"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/householder-names"> | |||
| <div class="mb-3"> | |||
| <label for="Name" class="form-label">Name <span class="text-danger">*</span></label> | |||
| <input type="text" class="form-control" id="Name" name="Name" required autofocus | |||
| value="<%= Server.HTMLEncode(HouseholderNameController.householderName.Name & "") %>"> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="HouseholdId" class="form-label">Household <span class="text-danger">*</span></label> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <input type="hidden" name="HouseholdId" value="<%= HouseholderNameController.household.Id %>"> | |||
| <input type="text" class="form-control" readonly value="<%= Server.HTMLEncode(HouseholderNameController.household.Address) %>"> | |||
| <% Else %> | |||
| <input type="number" class="form-control" id="HouseholdId" name="HouseholdId" required | |||
| value="<%= HouseholderNameController.householderName.HouseholdId %>" | |||
| placeholder="Enter Household ID"> | |||
| <div class="form-text">Enter the ID of the household this name belongs to.</div> | |||
| <% End If %> | |||
| </div> | |||
| <!-- IsBusiness moved to Household --> | |||
| <div class="mb-3"> | |||
| <div class="form-check"> | |||
| <input class="form-check-input" type="checkbox" id="LetterReturned" name="LetterReturned" value="1" | |||
| <% If HouseholderNameController.householderName.LetterReturned = 1 Then Response.Write "checked" End If %>> | |||
| <label class="form-check-label" for="LetterReturned"> | |||
| Letter was returned | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="ReturnDate" class="form-label">Return Date</label> | |||
| <input type="date" class="form-control" id="ReturnDate" name="ReturnDate"> | |||
| <div class="form-text">Only fill in if letter was returned.</div> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button type="submit" class="btn btn-primary">Create Householder Name</button> | |||
| <a href="/householder-names<% If HouseholderNameController.householderName.HouseholdId > 0 Then Response.Write "?household=" & HouseholderNameController.householderName.HouseholdId End If %>" class="btn btn-secondary">Cancel</a> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-4"> | |||
| <div class="card"> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">Help</h5> | |||
| </div> | |||
| <div class="card-body"> | |||
| <p class="mb-2"><strong>Name:</strong> The name of the person or business at this address.</p> | |||
| <p class="mb-2"><strong>Type:</strong> Business/Residential is now stored on the household, not the name.</p> | |||
| <p class="mb-0"><strong>Letter Returned:</strong> Check if a letter sent to this name was returned as undeliverable.</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -1,95 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb" class="mb-3"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/householder-names">Householder Names</a></li> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <li class="breadcrumb-item"><a href="/households/<%= HouseholderNameController.household.Id %>"><%= Server.HTMLEncode(HouseholderNameController.household.Address) %></a></li> | |||
| <% End If %> | |||
| <li class="breadcrumb-item"><a href="/householder-names/<%= HouseholderNameController.householderName.Id %>"><%= Server.HTMLEncode(HouseholderNameController.householderName.Name & "") %></a></li> | |||
| <li class="breadcrumb-item active">Edit</li> | |||
| </ol> | |||
| </nav> | |||
| <h1 class="mb-4">Edit Householder Name</h1> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="row"> | |||
| <div class="col-md-8"> | |||
| <div class="card"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/householder-names/<%= HouseholderNameController.householderName.Id %>"> | |||
| <div class="mb-3"> | |||
| <label for="Name" class="form-label">Name <span class="text-danger">*</span></label> | |||
| <input type="text" class="form-control" id="Name" name="Name" required autofocus | |||
| value="<%= Server.HTMLEncode(HouseholderNameController.householderName.Name & "") %>"> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label">Household</label> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <p class="form-control-plaintext"> | |||
| <a href="/households/<%= HouseholderNameController.household.Id %>"><%= Server.HTMLEncode(HouseholderNameController.household.Address) %></a> | |||
| </p> | |||
| <% Else %> | |||
| <p class="form-control-plaintext">ID: <%= HouseholderNameController.householderName.HouseholdId %></p> | |||
| <% End If %> | |||
| </div> | |||
| <!-- IsBusiness moved to Household --> | |||
| <div class="mb-3"> | |||
| <div class="form-check"> | |||
| <input class="form-check-input" type="checkbox" id="LetterReturned" name="LetterReturned" value="1" | |||
| <% If HouseholderNameController.householderName.LetterReturned = 1 Then Response.Write "checked" End If %>> | |||
| <label class="form-check-label" for="LetterReturned"> | |||
| Letter was returned | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="ReturnDate" class="form-label">Return Date</label> | |||
| <% | |||
| Dim returnDateVal | |||
| returnDateVal = "" | |||
| If Year(HouseholderNameController.householderName.ReturnDate) > 1970 Then | |||
| returnDateVal = Year(HouseholderNameController.householderName.ReturnDate) & "-" & _ | |||
| Right("0" & Month(HouseholderNameController.householderName.ReturnDate), 2) & "-" & _ | |||
| Right("0" & Day(HouseholderNameController.householderName.ReturnDate), 2) | |||
| End If | |||
| %> | |||
| <input type="date" class="form-control" id="ReturnDate" name="ReturnDate" value="<%= returnDateVal %>"> | |||
| <div class="form-text">Only fill in if letter was returned.</div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label">Created</label> | |||
| <p class="form-control-plaintext"><%= FormatDateTime(HouseholderNameController.householderName.Created, 2) %></p> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button type="submit" class="btn btn-primary">Update Householder Name</button> | |||
| <a href="/householder-names/<%= HouseholderNameController.householderName.Id %>" class="btn btn-secondary">Cancel</a> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-4"> | |||
| <div class="card border-danger"> | |||
| <div class="card-header bg-danger text-white"> | |||
| <h5 class="mb-0">Danger Zone</h5> | |||
| </div> | |||
| <div class="card-body"> | |||
| <p>Permanently delete this householder name.</p> | |||
| <form method="post" action="/householder-names/<%= HouseholderNameController.householderName.Id %>/delete" onsubmit="return confirm('Are you sure you want to delete this householder name? This action cannot be undone.');"> | |||
| <button type="submit" class="btn btn-danger w-100">Delete Householder Name</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -1,171 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <div class="d-flex justify-content-between align-items-center mb-4"> | |||
| <h1> | |||
| Householder Names | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <small class="text-muted">for <%= Server.HTMLEncode(HouseholderNameController.household.Address) %></small> | |||
| <% End If %> | |||
| </h1> | |||
| <a href="/householder-names/new<% If HouseholderNameController.filterHouseholdId > 0 Then Response.Write "?household=" & HouseholderNameController.filterHouseholdId End If %>" class="btn btn-primary">New Householder Name</a> | |||
| </div> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <!-- Search Form --> | |||
| <div class="card mb-4"> | |||
| <div class="card-body"> | |||
| <form method="get" action="/householder-names" class="row g-3"> | |||
| <div class="col-md-6"> | |||
| <div class="input-group"> | |||
| <input type="text" class="form-control" name="q" placeholder="Search by name..." value="<%= Server.HTMLEncode(HouseholderNameController.searchTerm) %>"> | |||
| <button class="btn btn-outline-primary" type="submit">Search</button> | |||
| <% If HouseholderNameController.searchTerm <> "" Or HouseholderNameController.filterHouseholdId > 0 Then %> | |||
| <a href="/householder-names" class="btn btn-outline-secondary">Clear</a> | |||
| <% End If %> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-3 text-end"> | |||
| <span class="text-muted"> | |||
| <% If HouseholderNameController.searchTerm <> "" Then %> | |||
| Found <%= HouseholderNameController.recordCount %> result(s) | |||
| <% Else %> | |||
| <%= HouseholderNameController.recordCount %> total | |||
| <% End If %> | |||
| </span> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <div class="alert alert-info"> | |||
| Showing householder names for: <strong><a href="/households/<%= HouseholderNameController.household.Id %>"><%= Server.HTMLEncode(HouseholderNameController.household.Address) %></a></strong> | |||
| <a href="/householder-names" class="btn btn-sm btn-outline-info ms-2">Show All</a> | |||
| </div> | |||
| <% End If %> | |||
| <div class="table-responsive"> | |||
| <table class="table table-striped table-hover"> | |||
| <thead class="table-dark"> | |||
| <tr> | |||
| <th>ID</th> | |||
| <th>Name</th> | |||
| <th>Type</th> | |||
| <th>Letter Returned</th> | |||
| <th>Created</th> | |||
| <th>Actions</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <% | |||
| Dim iter, hn | |||
| Set iter = HouseholderNameController.householderNames.Iterator() | |||
| If Not iter.HasNext() Then | |||
| %> | |||
| <tr> | |||
| <td colspan="6" class="text-center text-muted py-4"> | |||
| <% If HouseholderNameController.searchTerm <> "" Then %> | |||
| No householder names found matching "<%= Server.HTMLEncode(HouseholderNameController.searchTerm) %>" | |||
| <% ElseIf HouseholderNameController.filterHouseholdId > 0 Then %> | |||
| No householder names for this household. <a href="/householder-names/new?household=<%= HouseholderNameController.filterHouseholdId %>">Add one</a> | |||
| <% Else %> | |||
| No householder names found. <a href="/householder-names/new">Create one</a> | |||
| <% End If %> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Else | |||
| Do While iter.HasNext() | |||
| Set hn = iter.GetNext() | |||
| %> | |||
| <tr> | |||
| <td><%= hn.Id %></td> | |||
| <td><%= Server.HTMLEncode(hn.Name & "") %></td> | |||
| <td> | |||
| <span class="badge bg-secondary">Household</span> | |||
| </td> | |||
| <td> | |||
| <% If hn.LetterReturned = 1 Then %> | |||
| <span class="badge bg-warning text-dark">Returned</span> | |||
| <% If Year(hn.ReturnDate) > 1970 Then %> | |||
| <small class="text-muted"><%= FormatDateTime(hn.ReturnDate, 2) %></small> | |||
| <% End If %> | |||
| <% Else %> | |||
| <span class="badge bg-success">No</span> | |||
| <% End If %> | |||
| </td> | |||
| <td><%= FormatDateTime(hn.Created, 2) %></td> | |||
| <td> | |||
| <a href="/householder-names/<%= hn.Id %>" class="btn btn-sm btn-info">View</a> | |||
| <a href="/householder-names/<%= hn.Id %>/edit" class="btn btn-sm btn-warning">Edit</a> | |||
| <form method="post" action="/householder-names/<%= hn.Id %>/delete" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this householder name?');"> | |||
| <button type="submit" class="btn btn-sm btn-danger">Delete</button> | |||
| </form> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Loop | |||
| End If | |||
| %> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| <!-- Pagination --> | |||
| <% If HouseholderNameController.pageCount > 1 Then %> | |||
| <% | |||
| Dim baseUrl, pg, startPage, endPage | |||
| baseUrl = "/householder-names?" | |||
| If HouseholderNameController.searchTerm <> "" Then | |||
| baseUrl = baseUrl & "q=" & Server.URLEncode(HouseholderNameController.searchTerm) & "&" | |||
| End If | |||
| If HouseholderNameController.filterHouseholdId > 0 Then | |||
| baseUrl = baseUrl & "household=" & HouseholderNameController.filterHouseholdId & "&" | |||
| End If | |||
| ' Calculate pagination range (show 5 pages at a time) | |||
| startPage = HouseholderNameController.currentPage - 2 | |||
| If startPage < 1 Then startPage = 1 | |||
| endPage = startPage + 4 | |||
| If endPage > HouseholderNameController.pageCount Then | |||
| endPage = HouseholderNameController.pageCount | |||
| startPage = endPage - 4 | |||
| If startPage < 1 Then startPage = 1 | |||
| End If | |||
| %> | |||
| <nav aria-label="Householder names pagination"> | |||
| <ul class="pagination justify-content-center"> | |||
| <!-- First page --> | |||
| <li class="page-item <% If HouseholderNameController.currentPage = 1 Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=1">« First</a> | |||
| </li> | |||
| <!-- Previous page --> | |||
| <li class="page-item <% If HouseholderNameController.currentPage = 1 Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= HouseholderNameController.currentPage - 1 %>">‹ Prev</a> | |||
| </li> | |||
| <!-- Page numbers --> | |||
| <% For pg = startPage To endPage %> | |||
| <li class="page-item <% If pg = HouseholderNameController.currentPage Then Response.Write "active" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= pg %>"><%= pg %></a> | |||
| </li> | |||
| <% Next %> | |||
| <!-- Next page --> | |||
| <li class="page-item <% If HouseholderNameController.currentPage >= HouseholderNameController.pageCount Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= HouseholderNameController.currentPage + 1 %>">Next ›</a> | |||
| </li> | |||
| <!-- Last page --> | |||
| <li class="page-item <% If HouseholderNameController.currentPage >= HouseholderNameController.pageCount Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= HouseholderNameController.pageCount %>">Last »</a> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| <p class="text-center text-muted"> | |||
| Page <%= HouseholderNameController.currentPage %> of <%= HouseholderNameController.pageCount %> | |||
| </p> | |||
| <% End If %> | |||
| </div> | |||
| @@ -1,90 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb" class="mb-3"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/householder-names">Householder Names</a></li> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <li class="breadcrumb-item"><a href="/households/<%= HouseholderNameController.household.Id %>"><%= Server.HTMLEncode(HouseholderNameController.household.Address) %></a></li> | |||
| <% End If %> | |||
| <li class="breadcrumb-item active"><%= Server.HTMLEncode(HouseholderNameController.householderName.Name & "") %></li> | |||
| </ol> | |||
| </nav> | |||
| <div class="d-flex justify-content-between align-items-center mb-4"> | |||
| <h1><%= Server.HTMLEncode(HouseholderNameController.householderName.Name & "") %></h1> | |||
| <div> | |||
| <a href="/householder-names/<%= HouseholderNameController.householderName.Id %>/edit" class="btn btn-warning">Edit</a> | |||
| <a href="/householder-names?household=<%= HouseholderNameController.householderName.HouseholdId %>" class="btn btn-secondary">Back to List</a> | |||
| </div> | |||
| </div> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="row"> | |||
| <div class="col-md-8"> | |||
| <div class="card"> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">Householder Details</h5> | |||
| </div> | |||
| <div class="card-body"> | |||
| <dl class="row mb-0"> | |||
| <dt class="col-sm-4">ID</dt> | |||
| <dd class="col-sm-8"><%= HouseholderNameController.householderName.Id %></dd> | |||
| <dt class="col-sm-4">Name</dt> | |||
| <dd class="col-sm-8"><%= Server.HTMLEncode(HouseholderNameController.householderName.Name & "") %></dd> | |||
| <dt class="col-sm-4">Type</dt> | |||
| <dd class="col-sm-8"> | |||
| <span class="badge bg-secondary">Household</span> | |||
| </dd> | |||
| <dt class="col-sm-4">Letter Returned</dt> | |||
| <dd class="col-sm-8"> | |||
| <% If HouseholderNameController.householderName.LetterReturned = 1 Then %> | |||
| <span class="badge bg-warning text-dark">Yes - Returned</span> | |||
| <% If Year(HouseholderNameController.householderName.ReturnDate) > 1970 Then %> | |||
| <br><small class="text-muted">Return Date: <%= FormatDateTime(HouseholderNameController.householderName.ReturnDate, 2) %></small> | |||
| <% End If %> | |||
| <% Else %> | |||
| <span class="badge bg-success">No</span> | |||
| <% End If %> | |||
| </dd> | |||
| <dt class="col-sm-4">Created</dt> | |||
| <dd class="col-sm-8"><%= FormatDateTime(HouseholderNameController.householderName.Created, 2) %></dd> | |||
| <dt class="col-sm-4">Household</dt> | |||
| <dd class="col-sm-8"> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <a href="/households/<%= HouseholderNameController.household.Id %>"><%= Server.HTMLEncode(HouseholderNameController.household.Address) %></a> | |||
| <% Else %> | |||
| ID: <%= HouseholderNameController.householderName.HouseholdId %> | |||
| <% End If %> | |||
| </dd> | |||
| </dl> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-4"> | |||
| <div class="card"> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">Actions</h5> | |||
| </div> | |||
| <div class="card-body"> | |||
| <div class="d-grid gap-2"> | |||
| <a href="/householder-names/<%= HouseholderNameController.householderName.Id %>/edit" class="btn btn-warning">Edit</a> | |||
| <% If IsObject(HouseholderNameController.household) Then %> | |||
| <a href="/households/<%= HouseholderNameController.household.Id %>" class="btn btn-outline-primary">View Household</a> | |||
| <a href="/householder-names/new?household=<%= HouseholderNameController.householderName.HouseholdId %>" class="btn btn-outline-success">Add Another Name</a> | |||
| <% End If %> | |||
| <form method="post" action="/householder-names/<%= HouseholderNameController.householderName.Id %>/delete" onsubmit="return confirm('Are you sure you want to delete this householder name?');"> | |||
| <button type="submit" class="btn btn-danger w-100">Delete</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -1,134 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/territories">Territories</a></li> | |||
| <li class="breadcrumb-item active" aria-current="page">New Territory</li> | |||
| </ol> | |||
| </nav> | |||
| <h1>New Territory</h1> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="card"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/territories"> | |||
| <div class="row"> | |||
| <div class="col-md-4"> | |||
| <div class="mb-3"> | |||
| <label for="Name" class="form-label">Name <span class="text-danger">*</span></label> | |||
| <input type="text" class="form-control" id="Name" name="Name" required maxlength="255"> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="Description" class="form-label">Description</label> | |||
| <textarea class="form-control" id="Description" name="Description" rows="2"></textarea> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="Coordinates" class="form-label">Coordinates (JSON)</label> | |||
| <textarea class="form-control" id="Coordinates" name="Coordinates" rows="4" style="font-family: monospace; font-size: 12px;"></textarea> | |||
| <small class="text-muted">Click on the map to draw the territory boundary.</small> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button type="submit" class="btn btn-primary">Create Territory</button> | |||
| <a href="/territories" class="btn btn-secondary">Cancel</a> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-8"> | |||
| <label class="form-label">Territory Boundary</label> | |||
| <div id="map" style="height: 400px; width: 100%; border-radius: 8px;"></div> | |||
| <div class="mt-2"> | |||
| <button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearPolygon()">Clear Polygon</button> | |||
| <button type="button" class="btn btn-sm btn-outline-secondary" onclick="undoLastPoint()">Undo Last Point</button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(GetAppSetting("GoogleMapsApiKey")) %>&callback=initMap" async defer></script> | |||
| <script> | |||
| var map, polygon; | |||
| function initMap() { | |||
| var defaultCenter = { lat: -33.8688, lng: 151.2093 }; // Sydney default | |||
| map = new google.maps.Map(document.getElementById('map'), { | |||
| zoom: 12, | |||
| center: defaultCenter, | |||
| mapTypeId: 'roadmap' | |||
| }); | |||
| // Create editable polygon (empty initially) | |||
| polygon = new google.maps.Polygon({ | |||
| paths: [], | |||
| strokeColor: '#FF0000', | |||
| strokeOpacity: 0.8, | |||
| strokeWeight: 2, | |||
| fillColor: '#FF0000', | |||
| fillOpacity: 0.35, | |||
| editable: true, | |||
| draggable: true | |||
| }); | |||
| polygon.setMap(map); | |||
| // Click to add points | |||
| map.addListener('click', function(event) { | |||
| var path = polygon.getPath(); | |||
| path.push(event.latLng); | |||
| updateCoordinatesField(); | |||
| }); | |||
| // Update field when polygon is edited | |||
| google.maps.event.addListener(polygon.getPath(), 'set_at', updateCoordinatesField); | |||
| google.maps.event.addListener(polygon.getPath(), 'insert_at', updateCoordinatesField); | |||
| google.maps.event.addListener(polygon.getPath(), 'remove_at', updateCoordinatesField); | |||
| // Also update when dragged | |||
| google.maps.event.addListener(polygon, 'dragend', updateCoordinatesField); | |||
| } | |||
| function updateCoordinatesField() { | |||
| var path = polygon.getPath(); | |||
| var coords = []; | |||
| for (var i = 0; i < path.getLength(); i++) { | |||
| var point = path.getAt(i); | |||
| coords.push({ lat: point.lat(), lng: point.lng() }); | |||
| } | |||
| document.getElementById('Coordinates').value = JSON.stringify(coords, null, 2); | |||
| } | |||
| function clearPolygon() { | |||
| polygon.getPath().clear(); | |||
| updateCoordinatesField(); | |||
| } | |||
| function undoLastPoint() { | |||
| var path = polygon.getPath(); | |||
| if (path.getLength() > 0) { | |||
| path.pop(); | |||
| updateCoordinatesField(); | |||
| } | |||
| } | |||
| // Allow manual JSON editing | |||
| document.getElementById('Coordinates').addEventListener('change', function() { | |||
| try { | |||
| var coords = JSON.parse(this.value); | |||
| if (Array.isArray(coords)) { | |||
| var path = polygon.getPath(); | |||
| path.clear(); | |||
| coords.forEach(function(coord) { | |||
| path.push(new google.maps.LatLng(coord.lat || coord[0], coord.lng || coord[1])); | |||
| }); | |||
| } | |||
| } catch (e) { | |||
| // Invalid JSON, ignore | |||
| } | |||
| }); | |||
| </script> | |||
| @@ -1,164 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/territories">Territories</a></li> | |||
| <li class="breadcrumb-item"><a href="/territories/<%= TerritoryController.territory.Id %>"><%= Server.HTMLEncode(TerritoryController.territory.Name) %></a></li> | |||
| <li class="breadcrumb-item active" aria-current="page">Edit</li> | |||
| </ol> | |||
| </nav> | |||
| <h1>Edit Territory</h1> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="card"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/territories/<%= TerritoryController.territory.Id %>"> | |||
| <div class="row"> | |||
| <div class="col-md-4"> | |||
| <div class="mb-3"> | |||
| <label for="Name" class="form-label">Name <span class="text-danger">*</span></label> | |||
| <input type="text" class="form-control" id="Name" name="Name" required maxlength="255" value="<%= Server.HTMLEncode(TerritoryController.territory.Name) %>"> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="Description" class="form-label">Description</label> | |||
| <textarea class="form-control" id="Description" name="Description" rows="2"><%= Server.HTMLEncode(TerritoryController.territory.Description & "") %></textarea> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label for="Coordinates" class="form-label">Coordinates (JSON)</label> | |||
| <textarea class="form-control" id="Coordinates" name="Coordinates" rows="4" style="font-family: monospace; font-size: 12px;"><%= Server.HTMLEncode(TerritoryController.territory.Coordinates & "") %></textarea> | |||
| <small class="text-muted">Click on the map to add points, or edit JSON directly.</small> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button type="submit" class="btn btn-primary">Update Territory</button> | |||
| <a href="/territories/<%= TerritoryController.territory.Id %>" class="btn btn-secondary">Cancel</a> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-8"> | |||
| <label class="form-label">Territory Boundary</label> | |||
| <div id="map" style="height: 400px; width: 100%; border-radius: 8px;"></div> | |||
| <div class="mt-2"> | |||
| <button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearPolygon()">Clear Polygon</button> | |||
| <button type="button" class="btn btn-sm btn-outline-secondary" onclick="undoLastPoint()">Undo Last Point</button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script> | |||
| var existingCoordinates = <%= TerritoryController.territory.Coordinates & "" %>; | |||
| </script> | |||
| <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(GetAppSetting("GoogleMapsApiKey")) %>&callback=initMap" async defer></script> | |||
| <script> | |||
| var map, polygon, polygonCoords = []; | |||
| function initMap() { | |||
| var defaultCenter = { lat: -33.8688, lng: 151.2093 }; // Sydney default | |||
| // Parse existing coordinates | |||
| if (existingCoordinates && Array.isArray(existingCoordinates) && existingCoordinates.length > 0) { | |||
| polygonCoords = existingCoordinates.map(function(coord) { | |||
| return { lat: coord.lat || coord[0], lng: coord.lng || coord[1] }; | |||
| }); | |||
| } | |||
| // Calculate center from existing coords or use default | |||
| var center = defaultCenter; | |||
| if (polygonCoords.length > 0) { | |||
| var bounds = new google.maps.LatLngBounds(); | |||
| polygonCoords.forEach(function(coord) { | |||
| bounds.extend(coord); | |||
| }); | |||
| center = bounds.getCenter(); | |||
| } | |||
| map = new google.maps.Map(document.getElementById('map'), { | |||
| zoom: 14, | |||
| center: center, | |||
| mapTypeId: 'roadmap' | |||
| }); | |||
| // Create editable polygon | |||
| polygon = new google.maps.Polygon({ | |||
| paths: polygonCoords, | |||
| strokeColor: '#FF0000', | |||
| strokeOpacity: 0.8, | |||
| strokeWeight: 2, | |||
| fillColor: '#FF0000', | |||
| fillOpacity: 0.35, | |||
| editable: true, | |||
| draggable: true | |||
| }); | |||
| polygon.setMap(map); | |||
| // Fit bounds if we have coordinates | |||
| if (polygonCoords.length > 0) { | |||
| var bounds = new google.maps.LatLngBounds(); | |||
| polygonCoords.forEach(function(coord) { | |||
| bounds.extend(coord); | |||
| }); | |||
| map.fitBounds(bounds); | |||
| } | |||
| // Click to add points | |||
| map.addListener('click', function(event) { | |||
| var path = polygon.getPath(); | |||
| path.push(event.latLng); | |||
| updateCoordinatesField(); | |||
| }); | |||
| // Update field when polygon is edited | |||
| google.maps.event.addListener(polygon.getPath(), 'set_at', updateCoordinatesField); | |||
| google.maps.event.addListener(polygon.getPath(), 'insert_at', updateCoordinatesField); | |||
| google.maps.event.addListener(polygon.getPath(), 'remove_at', updateCoordinatesField); | |||
| // Also update when dragged | |||
| google.maps.event.addListener(polygon, 'dragend', updateCoordinatesField); | |||
| } | |||
| function updateCoordinatesField() { | |||
| var path = polygon.getPath(); | |||
| var coords = []; | |||
| for (var i = 0; i < path.getLength(); i++) { | |||
| var point = path.getAt(i); | |||
| coords.push({ lat: point.lat(), lng: point.lng() }); | |||
| } | |||
| document.getElementById('Coordinates').value = JSON.stringify(coords, null, 2); | |||
| } | |||
| function clearPolygon() { | |||
| polygon.getPath().clear(); | |||
| updateCoordinatesField(); | |||
| } | |||
| function undoLastPoint() { | |||
| var path = polygon.getPath(); | |||
| if (path.getLength() > 0) { | |||
| path.pop(); | |||
| updateCoordinatesField(); | |||
| } | |||
| } | |||
| // Allow manual JSON editing | |||
| document.getElementById('Coordinates').addEventListener('change', function() { | |||
| try { | |||
| var coords = JSON.parse(this.value); | |||
| if (Array.isArray(coords)) { | |||
| var path = polygon.getPath(); | |||
| path.clear(); | |||
| coords.forEach(function(coord) { | |||
| path.push(new google.maps.LatLng(coord.lat || coord[0], coord.lng || coord[1])); | |||
| }); | |||
| } | |||
| } catch (e) { | |||
| // Invalid JSON, ignore | |||
| } | |||
| }); | |||
| </script> | |||
| @@ -1,150 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <div class="d-flex justify-content-between align-items-center mb-4"> | |||
| <h1>Territories</h1> | |||
| <a href="/territories/new" class="btn btn-primary">New Territory</a> | |||
| </div> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <!-- Search Form --> | |||
| <div class="card mb-4"> | |||
| <div class="card-body"> | |||
| <form method="get" action="/territories" class="row g-3"> | |||
| <div class="col-md-8"> | |||
| <div class="input-group"> | |||
| <input type="text" class="form-control" name="q" placeholder="Search by name or description..." value="<%= Server.HTMLEncode(TerritoryController.searchTerm) %>"> | |||
| <button class="btn btn-outline-primary" type="submit">Search</button> | |||
| <% If TerritoryController.searchTerm <> "" Then %> | |||
| <a href="/territories" class="btn btn-outline-secondary">Clear</a> | |||
| <% End If %> | |||
| </div> | |||
| </div> | |||
| <div class="col-md-4 text-end"> | |||
| <span class="text-muted"> | |||
| <% If TerritoryController.searchTerm <> "" Then %> | |||
| Found <%= TerritoryController.recordCount %> result(s) | |||
| <% Else %> | |||
| <%= TerritoryController.recordCount %> total territories | |||
| <% End If %> | |||
| </span> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| <div class="table-responsive"> | |||
| <table class="table table-striped table-hover"> | |||
| <thead class="table-dark"> | |||
| <tr> | |||
| <th>ID</th> | |||
| <th>Name</th> | |||
| <th>Description</th> | |||
| <th>Households</th> | |||
| <th>Actions</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <% | |||
| Dim iter, t | |||
| Set iter = TerritoryController.territories.Iterator() | |||
| If Not iter.HasNext() Then | |||
| %> | |||
| <tr> | |||
| <td colspan="5" class="text-center text-muted py-4"> | |||
| <% If TerritoryController.searchTerm <> "" Then %> | |||
| No territories found matching "<%= Server.HTMLEncode(TerritoryController.searchTerm) %>" | |||
| <% Else %> | |||
| No territories found. <a href="/territories/new">Create one</a> | |||
| <% End If %> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Else | |||
| Do While iter.HasNext() | |||
| Set t = iter.GetNext() | |||
| %> | |||
| <tr> | |||
| <td><%= t.Id %></td> | |||
| <td><%= Server.HTMLEncode(t.Name) %></td> | |||
| <td><%= Server.HTMLEncode(Left(t.Description & "", 50)) %><% If Len(t.Description & "") > 50 Then Response.Write "..." End If %></td> | |||
| <% | |||
| Dim householdCount | |||
| householdCount = 0 | |||
| If IsObject(TerritoryController.territoryHouseholdCounts) Then | |||
| If TerritoryController.territoryHouseholdCounts.Exists(CLng(t.Id)) Then | |||
| householdCount = TerritoryController.territoryHouseholdCounts(CLng(t.Id)) | |||
| End If | |||
| End If | |||
| %> | |||
| <td><%= householdCount %></td> | |||
| <td> | |||
| <a href="/territories/<%= t.Id %>" class="btn btn-sm btn-info">View</a> | |||
| <a href="/territories/<%= t.Id %>/edit" class="btn btn-sm btn-warning">Edit</a> | |||
| <form method="post" action="/territories/<%= t.Id %>/delete" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this territory?');"> | |||
| <button type="submit" class="btn btn-sm btn-danger">Delete</button> | |||
| </form> | |||
| </td> | |||
| </tr> | |||
| <% | |||
| Loop | |||
| End If | |||
| %> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| <!-- Pagination --> | |||
| <% If TerritoryController.pageCount > 1 Then %> | |||
| <% | |||
| Dim baseUrl, pg, startPage, endPage | |||
| baseUrl = "/territories?" | |||
| If TerritoryController.searchTerm <> "" Then | |||
| baseUrl = baseUrl & "q=" & Server.URLEncode(TerritoryController.searchTerm) & "&" | |||
| End If | |||
| ' Calculate pagination range (show 5 pages at a time) | |||
| startPage = TerritoryController.currentPage - 2 | |||
| If startPage < 1 Then startPage = 1 | |||
| endPage = startPage + 4 | |||
| If endPage > TerritoryController.pageCount Then | |||
| endPage = TerritoryController.pageCount | |||
| startPage = endPage - 4 | |||
| If startPage < 1 Then startPage = 1 | |||
| End If | |||
| %> | |||
| <nav aria-label="Territory pagination"> | |||
| <ul class="pagination justify-content-center"> | |||
| <!-- First page --> | |||
| <li class="page-item <% If TerritoryController.currentPage = 1 Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=1">« First</a> | |||
| </li> | |||
| <!-- Previous page --> | |||
| <li class="page-item <% If TerritoryController.currentPage = 1 Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= TerritoryController.currentPage - 1 %>">‹ Prev</a> | |||
| </li> | |||
| <!-- Page numbers --> | |||
| <% For pg = startPage To endPage %> | |||
| <li class="page-item <% If pg = TerritoryController.currentPage Then Response.Write "active" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= pg %>"><%= pg %></a> | |||
| </li> | |||
| <% Next %> | |||
| <!-- Next page --> | |||
| <li class="page-item <% If TerritoryController.currentPage >= TerritoryController.pageCount Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= TerritoryController.currentPage + 1 %>">Next ›</a> | |||
| </li> | |||
| <!-- Last page --> | |||
| <li class="page-item <% If TerritoryController.currentPage >= TerritoryController.pageCount Then Response.Write "disabled" End If %>"> | |||
| <a class="page-link" href="<%= baseUrl %>page=<%= TerritoryController.pageCount %>">Last »</a> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| <p class="text-center text-muted"> | |||
| Page <%= TerritoryController.currentPage %> of <%= TerritoryController.pageCount %> | |||
| </p> | |||
| <% End If %> | |||
| </div> | |||
| @@ -1,813 +0,0 @@ | |||
| <div class="container mt-4"> | |||
| <nav aria-label="breadcrumb" class="d-print-none"> | |||
| <ol class="breadcrumb"> | |||
| <li class="breadcrumb-item"><a href="/territories">Territories</a></li> | |||
| <li class="breadcrumb-item active" aria-current="page"><%= Server.HTMLEncode(TerritoryController.territory.Name) %></li> | |||
| </ol> | |||
| </nav> | |||
| <% Flash().ShowSuccessIfPresent %> | |||
| <% Flash().ShowErrorsIfPresent %> | |||
| <div class="card"> | |||
| <div class="card-header d-flex justify-content-between align-items-center"> | |||
| <h2 class="mb-0"><%= Server.HTMLEncode(TerritoryController.territory.Name) %></h2> | |||
| <div class="d-print-none"> | |||
| <button type="button" class="btn btn-secondary" onclick="printTerritory()"><i class="bi bi-printer"></i> Print</button> | |||
| <a href="/territories/<%= TerritoryController.territory.Id %>/edit" class="btn btn-warning">Edit</a> | |||
| <form method="post" action="/territories/<%= TerritoryController.territory.Id %>/delete" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this territory?');"> | |||
| <button type="submit" class="btn btn-danger">Delete</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| <div class="card-body"> | |||
| <div class="row"> | |||
| <div class="col-md-4 territory-details"> | |||
| <dl> | |||
| <dt>ID</dt> | |||
| <dd><%= TerritoryController.territory.Id %></dd> | |||
| <dt>Name</dt> | |||
| <dd><%= Server.HTMLEncode(TerritoryController.territory.Name) %></dd> | |||
| <dt>Description</dt> | |||
| <dd><%= Server.HTMLEncode(TerritoryController.territory.Description & "") %></dd> | |||
| </dl> | |||
| </div> | |||
| <div class="col-md-8 map-container"> | |||
| <div id="map" style="height: 400px; width: 100%; border-radius: 8px;"></div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="mt-3 d-print-none"> | |||
| <a href="/territories" class="btn btn-secondary">Back to List</a> | |||
| </div> | |||
| </div> | |||
| <style> | |||
| @media print { | |||
| /* Hide navigation and non-essential elements */ | |||
| .d-print-none, | |||
| nav.navbar, | |||
| footer, | |||
| .breadcrumb { | |||
| display: none !important; | |||
| } | |||
| /* Set page size and margins */ | |||
| @page { | |||
| size: letter portrait; | |||
| margin: 0.5in; | |||
| } | |||
| /* Reset body for print */ | |||
| body { | |||
| margin: 0 !important; | |||
| padding: 0 !important; | |||
| } | |||
| .container { | |||
| max-width: 100% !important; | |||
| padding: 0 !important; | |||
| margin: 0 !important; | |||
| } | |||
| .card { | |||
| border: none !important; | |||
| box-shadow: none !important; | |||
| } | |||
| .card-header { | |||
| background-color: transparent !important; | |||
| border-bottom: 2px solid #000 !important; | |||
| padding: 0 0 10px 0 !important; | |||
| margin-bottom: 10px !important; | |||
| } | |||
| .card-body { | |||
| padding: 0 !important; | |||
| } | |||
| /* Territory title styling for print */ | |||
| h2 { | |||
| font-size: 24pt !important; | |||
| margin: 0 !important; | |||
| text-align: center !important; | |||
| } | |||
| /* Hide details section in print, show only title and map */ | |||
| .territory-details { | |||
| display: none !important; | |||
| } | |||
| /* Map takes 3/4 of 8.5x11 page (11in - 1in margins = 10in, 3/4 = 7.5in) */ | |||
| #map { | |||
| height: 7.5in !important; | |||
| width: 100% !important; | |||
| page-break-inside: avoid; | |||
| border: 1px solid #ccc !important; | |||
| } | |||
| .map-container { | |||
| flex: 0 0 100% !important; | |||
| max-width: 100% !important; | |||
| } | |||
| .row { | |||
| display: block !important; | |||
| } | |||
| /* Ensure text is black for printing */ | |||
| body, .card-body, dt, dd, h2 { | |||
| color: #000 !important; | |||
| } | |||
| } | |||
| </style> | |||
| <% | |||
| Dim mapProvider, googleMapsKey, mapTilerKey, mapTilerStyle, mapTilerSdkJsUrl, mapTilerSdkCssUrl | |||
| mapProvider = LCase(Trim(GetAppSetting("MapProvider") & "")) | |||
| If mapProvider <> "maptiler" Then mapProvider = "google" | |||
| googleMapsKey = Trim(GetAppSetting("GoogleMapsApiKey") & "") | |||
| mapTilerKey = Trim(GetAppSetting("MapTilerApiKey") & "") | |||
| mapTilerStyle = Trim(GetAppSetting("MapTilerStyle") & "") | |||
| If mapTilerStyle = "" Or LCase(mapTilerStyle) = "nothing" Then mapTilerStyle = "streets-v2" | |||
| mapTilerSdkJsUrl = Trim(GetAppSetting("MapTilerSdkJsUrl") & "") | |||
| mapTilerSdkCssUrl = Trim(GetAppSetting("MapTilerSdkCssUrl") & "") | |||
| If mapTilerSdkJsUrl = "" Or LCase(mapTilerSdkJsUrl) = "nothing" Then mapTilerSdkJsUrl = "https://cdn.maptiler.com/maptiler-sdk-js/v3.0.0/maptiler-sdk.umd.min.js" | |||
| If mapTilerSdkCssUrl = "" Or LCase(mapTilerSdkCssUrl) = "nothing" Then mapTilerSdkCssUrl = "https://cdn.maptiler.com/maptiler-sdk-js/v3.0.0/maptiler-sdk.css" | |||
| %> | |||
| <% | |||
| Dim coordsJson | |||
| coordsJson = Trim(TerritoryController.territory.Coordinates & "") | |||
| If coordsJson = "" Then coordsJson = "[]" | |||
| ' Build street names array for JavaScript | |||
| Dim streetIter, streetName, streetsJson, isFirst | |||
| streetsJson = "[" | |||
| isFirst = True | |||
| If Not TerritoryController.territoryStreets Is Nothing Then | |||
| Set streetIter = TerritoryController.territoryStreets.Iterator() | |||
| Do While streetIter.HasNext() | |||
| streetName = streetIter.GetNext() | |||
| If Not isFirst Then streetsJson = streetsJson & "," | |||
| streetsJson = streetsJson & """" & Replace(streetName, """", "\""") & """" | |||
| isFirst = False | |||
| Loop | |||
| End If | |||
| streetsJson = streetsJson & "]" | |||
| ' Get border streets from the Description field | |||
| Dim borderStreets | |||
| borderStreets = Trim(TerritoryController.territory.Description & "") | |||
| %> | |||
| <script> | |||
| var territoryCoordinates = <%= coordsJson %>; | |||
| var territoryName = "<%= Replace(TerritoryController.territory.Name, """", "\""") %>"; | |||
| var territoryBorderStreets = "<%= Replace(Replace(borderStreets, """", "\"""), vbCrLf, ", ") %>"; | |||
| var territoryStreets = <%= streetsJson %>; | |||
| var mapProvider = "<%= Replace(mapProvider, """", "\""") %>"; | |||
| var googleMapsKey = "<%= Replace(googleMapsKey, """", "\""") %>"; | |||
| var mapTilerKey = "<%= Replace(mapTilerKey, """", "\""") %>"; | |||
| var mapTilerStyle = "<%= Replace(mapTilerStyle, """", "\""") %>"; | |||
| </script> | |||
| <% If mapProvider = "maptiler" Then %> | |||
| <link rel="stylesheet" href="<%= Server.HTMLEncode(mapTilerSdkCssUrl) %>" /> | |||
| <script src="<%= Server.HTMLEncode(mapTilerSdkJsUrl) %>"></script> | |||
| <% Else %> | |||
| <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(googleMapsKey) %>&callback=initMap" async defer></script> | |||
| <% End If %> | |||
| <script> | |||
| var map, polygon; | |||
| var mapIsReady = false; | |||
| function setMapMessage(message) { | |||
| var mapEl = document.getElementById('map'); | |||
| if (mapEl) { | |||
| mapEl.innerHTML = '<div class="alert alert-info">' + message + '</div>'; | |||
| } | |||
| } | |||
| function getLatLng(coord) { | |||
| var lat, lng; | |||
| if (coord && typeof coord === 'object' && (coord.lat !== undefined || coord.lng !== undefined)) { | |||
| lat = parseFloat(coord.lat); | |||
| lng = parseFloat(coord.lng); | |||
| } else if (Array.isArray(coord) && coord.length >= 2) { | |||
| var a = parseFloat(coord[0]); | |||
| var b = parseFloat(coord[1]); | |||
| if (!isNaN(a) && !isNaN(b)) { | |||
| if (Math.abs(a) > 90 && Math.abs(b) <= 90) { | |||
| lng = a; | |||
| lat = b; | |||
| } else { | |||
| lat = a; | |||
| lng = b; | |||
| } | |||
| } | |||
| } | |||
| return { lat: lat, lng: lng }; | |||
| } | |||
| function initMap() { | |||
| try { | |||
| var coords = territoryCoordinates; | |||
| if (!coords || !Array.isArray(coords) || coords.length === 0) { | |||
| setMapMessage('No coordinates available for this territory.'); | |||
| return; | |||
| } | |||
| if (mapProvider === 'maptiler') { | |||
| if (!mapTilerKey || mapTilerKey === 'nothing') { | |||
| setMapMessage('MapTiler API key is missing.'); | |||
| return; | |||
| } | |||
| if (typeof maptilersdk === 'undefined') { | |||
| setMapMessage('MapTiler SDK failed to load.'); | |||
| return; | |||
| } | |||
| maptilersdk.config.apiKey = mapTilerKey; | |||
| var polygonCoords = coords.map(function(coord) { | |||
| var ll = getLatLng(coord); | |||
| return [ll.lng, ll.lat]; | |||
| }).filter(function(p) { | |||
| return isFinite(p[0]) && isFinite(p[1]); | |||
| }); | |||
| if (polygonCoords.length === 0) { | |||
| setMapMessage('No valid coordinates available for this territory.'); | |||
| return; | |||
| } | |||
| if (polygonCoords.length > 0) { | |||
| var firstCoord = polygonCoords[0]; | |||
| var lastCoord = polygonCoords[polygonCoords.length - 1]; | |||
| if (firstCoord[0] !== lastCoord[0] || firstCoord[1] !== lastCoord[1]) { | |||
| polygonCoords.push([firstCoord[0], firstCoord[1]]); | |||
| } | |||
| } | |||
| var bounds = new maptilersdk.LngLatBounds(); | |||
| polygonCoords.forEach(function(coord) { | |||
| bounds.extend(coord); | |||
| }); | |||
| var styleUrl = 'https://api.maptiler.com/maps/' + encodeURIComponent(mapTilerStyle) + '/style.json?key=' + encodeURIComponent(mapTilerKey); | |||
| map = new maptilersdk.Map({ | |||
| container: 'map', | |||
| style: styleUrl, | |||
| center: bounds.getCenter(), | |||
| zoom: 14, | |||
| preserveDrawingBuffer: true | |||
| }); | |||
| map.on('styleimagemissing', function(e) { | |||
| if (map.hasImage(e.id)) return; | |||
| var emptyCanvas = document.createElement('canvas'); | |||
| emptyCanvas.width = 1; | |||
| emptyCanvas.height = 1; | |||
| map.addImage(e.id, emptyCanvas); | |||
| }); | |||
| map.on('load', function() { | |||
| map.addSource('territory', { | |||
| type: 'geojson', | |||
| data: { | |||
| type: 'Feature', | |||
| geometry: { | |||
| type: 'Polygon', | |||
| coordinates: [polygonCoords] | |||
| } | |||
| } | |||
| }); | |||
| map.addLayer({ | |||
| id: 'territory-fill', | |||
| type: 'fill', | |||
| source: 'territory', | |||
| paint: { | |||
| 'fill-color': '#ff0000', | |||
| 'fill-opacity': 0.35 | |||
| } | |||
| }); | |||
| map.addLayer({ | |||
| id: 'territory-line', | |||
| type: 'line', | |||
| source: 'territory', | |||
| paint: { | |||
| 'line-color': '#ff0000', | |||
| 'line-width': 2 | |||
| } | |||
| }); | |||
| map.fitBounds(bounds, { padding: 20 }); | |||
| }); | |||
| map.on('idle', function() { | |||
| mapIsReady = true; | |||
| }); | |||
| return; | |||
| } | |||
| // Convert coordinates to Google Maps format | |||
| var polygonCoords = coords.map(function(coord) { | |||
| var ll = getLatLng(coord); | |||
| return { lat: ll.lat, lng: ll.lng }; | |||
| }).filter(function(p) { | |||
| return isFinite(p.lat) && isFinite(p.lng); | |||
| }); | |||
| if (polygonCoords.length === 0) { | |||
| setMapMessage('No valid coordinates available for this territory.'); | |||
| return; | |||
| } | |||
| // Calculate bounds to center the map | |||
| var bounds = new google.maps.LatLngBounds(); | |||
| polygonCoords.forEach(function(coord) { | |||
| bounds.extend(coord); | |||
| }); | |||
| map = new google.maps.Map(document.getElementById('map'), { | |||
| zoom: 14, | |||
| center: bounds.getCenter(), | |||
| mapTypeId: 'roadmap' | |||
| }); | |||
| // Draw the polygon | |||
| polygon = new google.maps.Polygon({ | |||
| paths: polygonCoords, | |||
| strokeColor: '#FF0000', | |||
| strokeOpacity: 0.8, | |||
| strokeWeight: 2, | |||
| fillColor: '#FF0000', | |||
| fillOpacity: 0.35 | |||
| }); | |||
| polygon.setMap(map); | |||
| map.fitBounds(bounds); | |||
| } catch (e) { | |||
| setMapMessage('Map error: ' + e.message); | |||
| } | |||
| } | |||
| var printWindow = null; | |||
| function printTerritory() { | |||
| var coords = territoryCoordinates; | |||
| if (!coords || !Array.isArray(coords) || coords.length === 0) { | |||
| alert('No coordinates available to print.'); | |||
| return; | |||
| } | |||
| if (mapProvider === 'maptiler') { | |||
| function openPrintWindowShell() { | |||
| // Build street lists HTML | |||
| var borderStreetsHtml = ''; | |||
| if (territoryBorderStreets && territoryBorderStreets.trim() !== '') { | |||
| borderStreetsHtml = '<div class="streets-section"><strong>Border Streets:</strong> ' + territoryBorderStreets + '</div>'; | |||
| } | |||
| var insideStreetsHtml = ''; | |||
| if (territoryStreets && territoryStreets.length > 0) { | |||
| insideStreetsHtml = '<div class="streets-section"><strong>Streets Inside:</strong> ' + territoryStreets.join(', ') + '</div>'; | |||
| } | |||
| var printContent = '<!DOCTYPE html>' + | |||
| '<html><head><title>Territory: ' + territoryName + '</title>' + | |||
| '<style>' + | |||
| '@page { size: letter portrait; margin: 0.25in; }' + | |||
| 'body { margin: 0; padding: 0; font-family: Arial, sans-serif; }' + | |||
| '.header { text-align: center; border-bottom: 2px solid #000; padding-bottom: 5px; margin-bottom: 10px; }' + | |||
| '.header h1 { margin: 0; font-size: 20pt; }' + | |||
| '.streets-section { font-size: 10pt; margin: 5px 0; text-align: left; }' + | |||
| '.map-container { text-align: center; min-height: 200px; }' + | |||
| '.map-container img { max-width: 100%; height: auto; max-height: 7in; }' + | |||
| '.map-link { display: inline-block; margin-top: 10px; font-size: 12px; word-break: break-all; }' + | |||
| '.print-btn { margin-top: 15px; padding: 10px 30px; font-size: 16px; cursor: pointer; }' + | |||
| '.loading { color: #666; font-size: 14px; }' + | |||
| '@media print { .no-print { display: none !important; } }' + | |||
| '</style></head><body>' + | |||
| '<div class="header"><h1>' + territoryName + '</h1></div>' + | |||
| borderStreetsHtml + | |||
| insideStreetsHtml + | |||
| '<div class="map-container">' + | |||
| '<div class="loading">Preparing map for print...</div>' + | |||
| '<img id="print-map-img" src="" alt="Territory Map" style="display:none;">' + | |||
| '<div class="map-link no-print"><a id="print-map-link" href="#" target="_blank" rel="noopener">Open image in new tab</a></div>' + | |||
| '</div>' + | |||
| '<div class="no-print" style="text-align: center; margin-top: 15px;">' + | |||
| '<button class="print-btn" onclick="window.print();">Print</button> ' + | |||
| '<button class="print-btn" onclick="window.close();">Close</button>' + | |||
| '</div>' + | |||
| '</body></html>'; | |||
| printWindow = window.open('', '_blank', 'width=900,height=1000'); | |||
| printWindow.document.write(printContent); | |||
| printWindow.document.close(); | |||
| } | |||
| function setPrintWindowImage(src) { | |||
| if (!printWindow || printWindow.closed) { | |||
| alert('Print window was blocked. Please allow popups and try again.'); | |||
| return; | |||
| } | |||
| var attempts = 0; | |||
| function trySet() { | |||
| attempts = attempts + 1; | |||
| var doc = printWindow.document; | |||
| if (!doc) { | |||
| if (attempts < 20) return setTimeout(trySet, 100); | |||
| alert('Print window did not finish loading.'); | |||
| return; | |||
| } | |||
| var img = doc.getElementById('print-map-img'); | |||
| var loading = doc.querySelector('.loading'); | |||
| var link = doc.getElementById('print-map-link'); | |||
| if (!img) { | |||
| if (attempts < 20) return setTimeout(trySet, 100); | |||
| alert('Print image element not found.'); | |||
| return; | |||
| } | |||
| if (loading) loading.style.display = 'none'; | |||
| img.style.display = 'block'; | |||
| img.src = src; | |||
| if (link) link.href = src; | |||
| } | |||
| trySet(); | |||
| } | |||
| openPrintWindowShell(); | |||
| function loadImage(url) { | |||
| return new Promise(function(resolve, reject) { | |||
| var img = new Image(); | |||
| img.crossOrigin = 'anonymous'; | |||
| img.onload = function() { resolve(img); }; | |||
| img.onerror = function() { reject(url); }; | |||
| img.src = url; | |||
| }); | |||
| } | |||
| function renderMapTilerImage() { | |||
| return new Promise(function(resolve, reject) { | |||
| if (!mapTilerKey || mapTilerKey === 'nothing') { | |||
| reject('MapTiler API key is missing.'); | |||
| return; | |||
| } | |||
| var tileCoordSize = 512; | |||
| if (map && typeof map.getStyle === 'function') { | |||
| var styleObj = map.getStyle(); | |||
| if (styleObj && styleObj.sources) { | |||
| for (var srcKey in styleObj.sources) { | |||
| if (styleObj.sources.hasOwnProperty(srcKey)) { | |||
| var src = styleObj.sources[srcKey]; | |||
| if (src && src.tileSize) { | |||
| tileCoordSize = src.tileSize; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| function buildTileUrl(z, x, y) { | |||
| return 'https://api.maptiler.com/maps/' + | |||
| encodeURIComponent(mapTilerStyle) + '/' + | |||
| z + '/' + x + '/' + y + '.png?key=' + | |||
| encodeURIComponent(mapTilerKey); | |||
| } | |||
| function buildImage(tileImageSize) { | |||
| function lngToWorldX(lng, zoom) { | |||
| var scale = tileCoordSize * Math.pow(2, zoom); | |||
| return (lng + 180) / 360 * scale; | |||
| } | |||
| function latToWorldY(lat, zoom) { | |||
| var rad = lat * Math.PI / 180; | |||
| var scale = tileCoordSize * Math.pow(2, zoom); | |||
| return (1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2 * scale; | |||
| } | |||
| var minLat = Infinity, maxLat = -Infinity; | |||
| var minLng = Infinity, maxLng = -Infinity; | |||
| coords.forEach(function(coord) { | |||
| var ll = getLatLng(coord); | |||
| var lat = ll.lat; | |||
| var lng = ll.lng; | |||
| if (!isFinite(lat) || !isFinite(lng)) return; | |||
| if (lat < minLat) minLat = lat; | |||
| if (lat > maxLat) maxLat = lat; | |||
| if (lng < minLng) minLng = lng; | |||
| if (lng > maxLng) maxLng = lng; | |||
| }); | |||
| if (!isFinite(minLat) || !isFinite(minLng) || !isFinite(maxLat) || !isFinite(maxLng)) { | |||
| reject('No valid coordinates available to print.'); | |||
| return; | |||
| } | |||
| var centerLat = (minLat + maxLat) / 2; | |||
| var centerLng = (minLng + maxLng) / 2; | |||
| var isWide = (maxLng - minLng) > (maxLat - minLat) * 1.2; | |||
| var baseWidth = isWide ? 800 : 640; | |||
| var baseHeight = isWide ? 640 : 800; | |||
| var outputScale = tileImageSize > 0 ? (tileImageSize / tileCoordSize) : 1; | |||
| var width = baseWidth * outputScale; | |||
| var height = baseHeight * outputScale; | |||
| var x1 = lngToWorldX(minLng, 0); | |||
| var x2 = lngToWorldX(maxLng, 0); | |||
| var y1 = latToWorldY(maxLat, 0); | |||
| var y2 = latToWorldY(minLat, 0); | |||
| var spanX0 = Math.abs(x2 - x1); | |||
| var spanY0 = Math.abs(y2 - y1); | |||
| var paddingFactor = 1.02; | |||
| var zoomX = spanX0 > 0 ? Math.log2(baseWidth / (spanX0 * paddingFactor)) : 20; | |||
| var zoomY = spanY0 > 0 ? Math.log2(baseHeight / (spanY0 * paddingFactor)) : 20; | |||
| var zoom = Math.min(zoomX, zoomY); | |||
| if (!isFinite(zoom)) zoom = 1; | |||
| if (zoom < 1) zoom = 1; | |||
| if (zoom > 20) zoom = 20; | |||
| var zInt = Math.floor(zoom); | |||
| var zScale = Math.pow(2, zoom - zInt); | |||
| var worldCenterXint = lngToWorldX(centerLng, zInt); | |||
| var worldCenterYint = latToWorldY(centerLat, zInt); | |||
| var worldWidth = width / (outputScale * zScale); | |||
| var worldHeight = height / (outputScale * zScale); | |||
| var topLeftXint = worldCenterXint - worldWidth / 2; | |||
| var topLeftYint = worldCenterYint - worldHeight / 2; | |||
| var topLeftX = topLeftXint * outputScale * zScale; | |||
| var topLeftY = topLeftYint * outputScale * zScale; | |||
| var n = Math.pow(2, zInt); | |||
| var tileXStart = Math.floor(topLeftXint / tileCoordSize); | |||
| var tileYStart = Math.floor(topLeftYint / tileCoordSize); | |||
| var tileXEnd = Math.floor((topLeftXint + worldWidth) / tileCoordSize); | |||
| var tileYEnd = Math.floor((topLeftYint + worldHeight) / tileCoordSize); | |||
| var promises = []; | |||
| var tiles = []; | |||
| var tileX, tileY; | |||
| for (tileY = tileYStart; tileY <= tileYEnd; tileY++) { | |||
| if (tileY < 0 || tileY >= n) continue; | |||
| for (tileX = tileXStart; tileX <= tileXEnd; tileX++) { | |||
| var wrappedX = ((tileX % n) + n) % n; | |||
| var url = buildTileUrl(zInt, wrappedX, tileY); | |||
| (function(x, y, u) { | |||
| var p = loadImage(u).then(function(img) { | |||
| tiles.push({ x: x, y: y, img: img }); | |||
| }); | |||
| promises.push(p); | |||
| })(tileX, tileY, url); | |||
| } | |||
| } | |||
| Promise.all(promises).then(function() { | |||
| var canvas = document.createElement('canvas'); | |||
| canvas.width = width; | |||
| canvas.height = height; | |||
| var ctx = canvas.getContext('2d'); | |||
| var tileDrawSize = tileCoordSize * zScale * outputScale; | |||
| tiles.forEach(function(t) { | |||
| var dx = (t.x * tileCoordSize * zScale * outputScale) - topLeftX; | |||
| var dy = (t.y * tileCoordSize * zScale * outputScale) - topLeftY; | |||
| ctx.drawImage(t.img, dx, dy, tileDrawSize, tileDrawSize); | |||
| }); | |||
| if (coords.length > 1) { | |||
| ctx.save(); | |||
| ctx.strokeStyle = 'rgba(255,0,0,0.8)'; | |||
| ctx.fillStyle = 'rgba(255,0,0,0.25)'; | |||
| ctx.lineWidth = 3; | |||
| ctx.beginPath(); | |||
| coords.forEach(function(coord, idx) { | |||
| var ll = getLatLng(coord); | |||
| var lat = ll.lat; | |||
| var lng = ll.lng; | |||
| if (!isFinite(lat) || !isFinite(lng)) return; | |||
| var px = (lngToWorldX(lng, zInt) * zScale * outputScale) - topLeftX; | |||
| var py = (latToWorldY(lat, zInt) * zScale * outputScale) - topLeftY; | |||
| if (idx === 0) { | |||
| ctx.moveTo(px, py); | |||
| } else { | |||
| ctx.lineTo(px, py); | |||
| } | |||
| }); | |||
| ctx.closePath(); | |||
| ctx.fill(); | |||
| ctx.stroke(); | |||
| ctx.restore(); | |||
| } | |||
| var dataUrl = ""; | |||
| try { | |||
| dataUrl = canvas.toDataURL('image/png'); | |||
| } catch (e) { | |||
| reject('Unable to generate print image.'); | |||
| return; | |||
| } | |||
| if (!dataUrl || dataUrl.length < 1000) { | |||
| reject('Generated print image is empty.'); | |||
| return; | |||
| } | |||
| resolve(dataUrl); | |||
| }).catch(function() { | |||
| reject('Failed to load map tiles for printing.'); | |||
| }); | |||
| } | |||
| var testUrl = buildTileUrl(0, 0, 0); | |||
| loadImage(testUrl).then(function(img) { | |||
| var tileImageSize = (img && img.width) ? img.width : 256; | |||
| buildImage(tileImageSize); | |||
| }).catch(function() { | |||
| buildImage(256); | |||
| }); | |||
| }); | |||
| } | |||
| renderMapTilerImage().then(function(dataUrl) { | |||
| setPrintWindowImage(dataUrl); | |||
| }).catch(function(errMsg) { | |||
| alert(errMsg); | |||
| }); | |||
| return; | |||
| } | |||
| // Build polygon path for Static Maps API | |||
| var pathPoints = coords.map(function(coord) { | |||
| var ll = getLatLng(coord); | |||
| if (!isFinite(ll.lat) || !isFinite(ll.lng)) return null; | |||
| return ll.lat + ',' + ll.lng; | |||
| }).filter(function(p) { return p !== null; }); | |||
| // Close the polygon | |||
| pathPoints.push(pathPoints[0]); | |||
| var pathStr = pathPoints.join('|'); | |||
| // Calculate bounding box to determine optimal zoom | |||
| var minLat = Infinity, maxLat = -Infinity; | |||
| var minLng = Infinity, maxLng = -Infinity; | |||
| coords.forEach(function(coord) { | |||
| var ll = getLatLng(coord); | |||
| var lat = ll.lat; | |||
| var lng = ll.lng; | |||
| if (!isFinite(lat) || !isFinite(lng)) return; | |||
| if (lat < minLat) minLat = lat; | |||
| if (lat > maxLat) maxLat = lat; | |||
| if (lng < minLng) minLng = lng; | |||
| if (lng > maxLng) maxLng = lng; | |||
| }); | |||
| var centerLat = (minLat + maxLat) / 2; | |||
| var centerLng = (minLng + maxLng) / 2; | |||
| // Calculate the span of the polygon | |||
| var latSpan = maxLat - minLat; | |||
| var lngSpan = maxLng - minLng; | |||
| // Calculate optimal zoom level to maximize polygon size | |||
| // Map dimensions: 640x800 (width x height for portrait letter) | |||
| // Each zoom level doubles the scale | |||
| var zoom = 20; // Start with max zoom | |||
| // Degrees per pixel at zoom level 0 is approximately 360/256 for lng | |||
| // For latitude it varies, but we use equirectangular approximation | |||
| var mapWidth = 640; | |||
| var mapHeight = 800; | |||
| // Add 10% padding around the polygon | |||
| var paddedLatSpan = latSpan * 1.1; | |||
| var paddedLngSpan = lngSpan * 1.1; | |||
| // Calculate zoom based on longitude span | |||
| if (paddedLngSpan > 0) { | |||
| var lngZoom = Math.floor(Math.log2(360 / paddedLngSpan * mapWidth / 256)); | |||
| if (lngZoom < zoom) zoom = lngZoom; | |||
| } | |||
| // Calculate zoom based on latitude span | |||
| if (paddedLatSpan > 0) { | |||
| var latZoom = Math.floor(Math.log2(180 / paddedLatSpan * mapHeight / 256)); | |||
| if (latZoom < zoom) zoom = latZoom; | |||
| } | |||
| // Clamp zoom between 1 and 20 | |||
| if (zoom < 1) zoom = 1; | |||
| if (zoom > 20) zoom = 20; | |||
| var staticMapUrl; | |||
| if (mapProvider === 'maptiler') { | |||
| if (!mapTilerKey || mapTilerKey === 'nothing') { | |||
| alert('MapTiler API key is missing.'); | |||
| return; | |||
| } | |||
| var mapWidth = 640; | |||
| var mapHeight = 800; | |||
| var pathParam = 'stroke:#ff0000|width:3|fill:none|' + pathStr; | |||
| staticMapUrl = 'https://api.maptiler.com/maps/' + | |||
| encodeURIComponent(mapTilerStyle) + | |||
| '/static/auto/' + | |||
| mapWidth + 'x' + mapHeight + '@2x.png' + | |||
| '?path=' + encodeURIComponent(pathParam) + | |||
| '&key=' + encodeURIComponent(mapTilerKey); | |||
| } else { | |||
| // Build Google Static Maps URL with scale=2 for higher resolution (1280x1600 actual pixels) | |||
| staticMapUrl = 'https://maps.googleapis.com/maps/api/staticmap?' + | |||
| 'size=640x800' + | |||
| '&scale=2' + | |||
| '¢er=' + centerLat + ',' + centerLng + | |||
| '&zoom=' + zoom + | |||
| '&maptype=roadmap' + | |||
| '&path=color:0xFF0000CC|weight:3|fillcolor:0xFF000044|' + pathStr + | |||
| '&key=' + encodeURIComponent(googleMapsKey); | |||
| } | |||
| // Open print window with static map - landscape for wider polygons, portrait for taller | |||
| var isWide = lngSpan > latSpan * 1.2; | |||
| var pageSize = isWide ? 'letter landscape' : 'letter portrait'; | |||
| var imgMaxHeight = isWide ? '5in' : '7in'; | |||
| // Build street lists HTML | |||
| var borderStreetsHtml = ''; | |||
| if (territoryBorderStreets && territoryBorderStreets.trim() !== '') { | |||
| borderStreetsHtml = '<div class="streets-section"><strong>Border Streets:</strong> ' + territoryBorderStreets + '</div>'; | |||
| } | |||
| var insideStreetsHtml = ''; | |||
| if (territoryStreets && territoryStreets.length > 0) { | |||
| insideStreetsHtml = '<div class="streets-section"><strong>Streets Inside:</strong> ' + territoryStreets.join(', ') + '</div>'; | |||
| } | |||
| var printContent = '<!DOCTYPE html>' + | |||
| '<html><head><title>Territory: ' + territoryName + '</title>' + | |||
| '<style>' + | |||
| '@page { size: ' + pageSize + '; margin: 0.25in; }' + | |||
| 'body { margin: 0; padding: 0; font-family: Arial, sans-serif; }' + | |||
| '.header { text-align: center; border-bottom: 2px solid #000; padding-bottom: 5px; margin-bottom: 10px; }' + | |||
| '.header h1 { margin: 0; font-size: 20pt; }' + | |||
| '.streets-section { font-size: 10pt; margin: 5px 0; text-align: left; }' + | |||
| '.map-container { text-align: center; }' + | |||
| '.map-container img { max-width: 100%; height: auto; max-height: ' + imgMaxHeight + '; }' + | |||
| '.print-btn { margin-top: 15px; padding: 10px 30px; font-size: 16px; cursor: pointer; }' + | |||
| '@media print { .no-print { display: none !important; } }' + | |||
| '</style></head><body>' + | |||
| '<div class="header"><h1>' + territoryName + '</h1></div>' + | |||
| borderStreetsHtml + | |||
| insideStreetsHtml + | |||
| '<div class="map-container">' + | |||
| '<img src="' + staticMapUrl + '" alt="Territory Map" onerror="document.body.innerHTML=\'<p>Error loading map. Please try again.</p>\';">' + | |||
| '</div>' + | |||
| '<div class="no-print" style="text-align: center; margin-top: 15px;">' + | |||
| '<button class="print-btn" onclick="window.print();">Print</button> ' + | |||
| '<button class="print-btn" onclick="window.close();">Close</button>' + | |||
| '</div>' + | |||
| '</body></html>'; | |||
| printWindow = window.open('', '_blank', 'width=900,height=1000'); | |||
| printWindow.document.write(printContent); | |||
| printWindow.document.close(); | |||
| } | |||
| if (mapProvider === 'maptiler') { | |||
| if (document.readyState === 'loading') { | |||
| document.addEventListener('DOMContentLoaded', initMap); | |||
| } else { | |||
| initMap(); | |||
| } | |||
| setTimeout(function() { | |||
| if (!map) { | |||
| setMapMessage('Map failed to load.'); | |||
| } | |||
| }, 2000); | |||
| } else { | |||
| setTimeout(function() { | |||
| if (typeof google === 'undefined') { | |||
| setMapMessage('Google Maps failed to load.'); | |||
| } | |||
| }, 2000); | |||
| } | |||
| </script> | |||
| @@ -15,7 +15,7 @@ If IsObject(CurrentController) Then | |||
| On Error GoTo 0 | |||
| End If | |||
| If Len(pageTitle) = 0 Then pageTitle = "Classic ASP Unified Framework" | |||
| If Len(pageTitle) = 0 Then pageTitle = "Classic ASP Starter Template" | |||
| %> | |||
| <html lang="en"> | |||
| <head> | |||
| @@ -50,8 +50,8 @@ If Len(pageTitle) = 0 Then pageTitle = "Classic ASP Unified Framework" | |||
| <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> | |||
| <div class="container-fluid"> | |||
| <a class="navbar-brand rk-navbar-brand" href="/"> | |||
| Classic ASP Unified | |||
| <span class="text-secondary small">Framework</span> | |||
| Classic ASP | |||
| <span class="text-secondary small">Starter</span> | |||
| </a> | |||
| <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#rkMainNav" aria-controls="rkMainNav" aria-expanded="false" aria-label="Toggle navigation"> | |||
| @@ -61,13 +61,7 @@ If Len(pageTitle) = 0 Then pageTitle = "Classic ASP Unified Framework" | |||
| <div class="collapse navbar-collapse" id="rkMainNav"> | |||
| <ul class="navbar-nav me-auto mb-2 mb-lg-0"> | |||
| <li class="nav-item"> | |||
| <a class="nav-link" href="/territories">Territories</a> | |||
| </li> | |||
| <li class="nav-item"> | |||
| <a class="nav-link" href="/households">Households</a> | |||
| </li> | |||
| <li class="nav-item"> | |||
| <a class="nav-link" href="/householder-names">Householder Names</a> | |||
| <a class="nav-link" href="/home">Home</a> | |||
| </li> | |||
| </ul> | |||
| @@ -15,9 +15,6 @@ Class ControllerRegistry_Class | |||
| ' Format: m_controllers.Add "controllername", True | |||
| RegisterController "homecontroller" | |||
| RegisterController "errorcontroller" | |||
| RegisterController "territorycontroller" | |||
| RegisterController "householdcontroller" | |||
| RegisterController "householdernamecontroller" | |||
| End Sub | |||
| Private Sub Class_Terminate() | |||
| @@ -1,40 +1,12 @@ | |||
| <!--#include file="..\core\autoload_core.asp" --> | |||
| <% | |||
| ' Define application routes | |||
| router.AddRoute "GET", "/home", "homeController", "Index" | |||
| router.AddRoute "GET", "/", "TerritoryController", "Index" | |||
| router.AddRoute "GET", "", "TerritoryController", "Index" | |||
| ' Define starter application routes | |||
| router.AddRoute "GET", "/home", "HomeController", "Index" | |||
| router.AddRoute "GET", "/", "HomeController", "Index" | |||
| router.AddRoute "GET", "", "HomeController", "Index" | |||
| router.AddRoute "GET", "/404", "ErrorController", "NotFound" | |||
| ' Territory routes | |||
| router.AddRoute "GET", "/territories", "TerritoryController", "Index" | |||
| router.AddRoute "GET", "/territories/new", "TerritoryController", "Create" | |||
| router.AddRoute "POST", "/territories", "TerritoryController", "Store" | |||
| router.AddRoute "GET", "/territories/:id", "TerritoryController", "Show" | |||
| router.AddRoute "GET", "/territories/:id/edit", "TerritoryController", "Edit" | |||
| router.AddRoute "POST", "/territories/:id", "TerritoryController", "Update" | |||
| router.AddRoute "POST", "/territories/:id/delete", "TerritoryController", "Delete" | |||
| ' Household routes | |||
| router.AddRoute "GET", "/households", "HouseholdController", "Index" | |||
| router.AddRoute "GET", "/households/new", "HouseholdController", "Create" | |||
| router.AddRoute "POST", "/households", "HouseholdController", "Store" | |||
| router.AddRoute "GET", "/households/:id", "HouseholdController", "Show" | |||
| router.AddRoute "GET", "/households/:id/edit", "HouseholdController", "Edit" | |||
| router.AddRoute "POST", "/households/:id", "HouseholdController", "Update" | |||
| router.AddRoute "POST", "/households/:id/delete", "HouseholdController", "Delete" | |||
| router.AddRoute "POST", "/households/:id/mark-returned", "HouseholdController", "MarkReturned" | |||
| ' Householder Name routes | |||
| router.AddRoute "GET", "/householder-names", "HouseholderNameController", "Index" | |||
| router.AddRoute "GET", "/householder-names/new", "HouseholderNameController", "Create" | |||
| router.AddRoute "POST", "/householder-names", "HouseholderNameController", "Store" | |||
| router.AddRoute "GET", "/householder-names/:id", "HouseholderNameController", "Show" | |||
| router.AddRoute "GET", "/householder-names/:id/edit", "HouseholderNameController", "Edit" | |||
| router.AddRoute "POST", "/householder-names/:id", "HouseholderNameController", "Update" | |||
| router.AddRoute "POST", "/householder-names/:id/delete", "HouseholderNameController", "Delete" | |||
| ' Dispatch the request (resolves route and executes controller action) | |||
| MVC.DispatchRequest Request.ServerVariables("REQUEST_METHOD"), _ | |||
| TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL")) | |||
| @@ -0,0 +1,4 @@ | |||
| @echo off | |||
| setlocal | |||
| set "ASPC_STARTER_ROOT=%~dp0" | |||
| "C:\Program Files\IIS Express\iisexpress.exe" /config:"%~dp0applicationhost.config" | |||
Powered by TurnKey Linux.