Co-Authored-By: Abacus.AI CLI <agent@abacus.ai>master
| @@ -0,0 +1,397 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,284 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,226 @@ | |||
| <% | |||
| ' 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,2 +1,5 @@ | |||
| <!--#include file="ErrorController.asp" --> | |||
| <!--#include file="HomeController.asp" --> | |||
| <!--#include file="ErrorController.asp" --> | |||
| <!--#include file="TerritoryController.asp" --> | |||
| <!--#include file="HouseholdController.asp" --> | |||
| <!--#include file="HouseholderNameController.asp" --> | |||
| @@ -0,0 +1,176 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,243 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,133 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,172 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,103 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,176 @@ | |||
| <% | |||
| ' 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 | |||
| %> | |||
| @@ -0,0 +1,153 @@ | |||
| <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> | |||
| @@ -0,0 +1,190 @@ | |||
| <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> | |||
| @@ -0,0 +1,187 @@ | |||
| <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> | |||
| @@ -0,0 +1,291 @@ | |||
| <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 %> | |||
| @@ -0,0 +1,86 @@ | |||
| <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> | |||
| @@ -0,0 +1,95 @@ | |||
| <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> | |||
| @@ -0,0 +1,171 @@ | |||
| <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> | |||
| @@ -0,0 +1,90 @@ | |||
| <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> | |||
| @@ -0,0 +1,134 @@ | |||
| <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> | |||
| @@ -0,0 +1,164 @@ | |||
| <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> | |||
| @@ -0,0 +1,150 @@ | |||
| <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> | |||
| @@ -0,0 +1,813 @@ | |||
| <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 = "RouteKit Classic ASP" | |||
| If Len(pageTitle) = 0 Then pageTitle = "Classic ASP Unified Framework" | |||
| %> | |||
| <html lang="en"> | |||
| <head> | |||
| @@ -50,8 +50,8 @@ If Len(pageTitle) = 0 Then pageTitle = "RouteKit Classic ASP" | |||
| <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> | |||
| <div class="container-fluid"> | |||
| <a class="navbar-brand rk-navbar-brand" href="/"> | |||
| RouteKit | |||
| <span class="text-secondary small">Classic ASP</span> | |||
| Classic ASP Unified | |||
| <span class="text-secondary small">Framework</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,10 +61,14 @@ If Len(pageTitle) = 0 Then pageTitle = "RouteKit Classic ASP" | |||
| <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="/">Home</a> | |||
| <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> | |||
| </li> | |||
| <!-- Add more shared nav items here --> | |||
| ' <li class="nav-item"><a class="nav-link" href="/docs">Docs</a></li> | |||
| </ul> | |||
| ' Right-side area (e.g., user info / login) | |||
| @@ -19,3 +19,5 @@ | |||
| <!--#include file="../Core/lib.helpers.asp"--> | |||
| <!--#include file="../Core/lib.crypto.helper.asp"--> | |||
| <!--#include file="../Core/lib.Enumerable.asp"--> | |||
| <!--#include file="../Core/lib.ad.auth.asp"--> | |||
| <!--#include file="../Core/databaseConnection.asp"--> | |||
| @@ -0,0 +1,127 @@ | |||
| <% | |||
| '------------------------------------------------------------------------------ | |||
| ' DatabaseConnection.Class.inc | |||
| ' A singleton VBScript "factory" for ADODB.Connection to multiple databases | |||
| ' Enhanced with separate error handling for creation and open operations | |||
| '------------------------------------------------------------------------------ | |||
| ' Singleton holder | |||
| Dim DatabaseConnection__Singleton | |||
| Set DatabaseConnection__Singleton = Nothing | |||
| ' Factory function | |||
| Function DatabaseConnection() | |||
| If DatabaseConnection__Singleton Is Nothing Then | |||
| Set DatabaseConnection__Singleton = New DatabaseConnection_Class | |||
| End If | |||
| Set DatabaseConnection = DatabaseConnection__Singleton | |||
| End Function | |||
| '------------------------------------------------------------------------------ | |||
| ' Class definition | |||
| '------------------------------------------------------------------------------ | |||
| Class DatabaseConnection_Class | |||
| Private conn ' holds the ADODB.Connection instance | |||
| '---------------------------------------- | |||
| ' Connect to an Access (.mdb/.accdb) file | |||
| '---------------------------------------- | |||
| Public Function ConnectToAccessDatabase(dataSource, provider) | |||
| If IsEmpty(provider) Or provider = "" Then | |||
| provider = "Microsoft.Jet.OLEDB.4.0" | |||
| End If | |||
| Dim connStr | |||
| connStr = "Provider=" & provider & ";" & _ | |||
| "Data Source=" & dataSource & ";" & _ | |||
| "Persist Security Info=False;" | |||
| Set ConnectToAccessDatabase = Me.Connect(connStr) | |||
| End Function | |||
| '---------------------------------------- | |||
| ' Connect to SQL Server | |||
| '---------------------------------------- | |||
| Public Function ConnectToSQLServer(server, database, uid, pwd, useTrusted) | |||
| Dim connStr | |||
| If useTrusted = True Then | |||
| connStr = "Provider=SQLOLEDB;" & _ | |||
| "Server=" & server & ";" & _ | |||
| "Database=" & database & ";" & _ | |||
| "Trusted_Connection=Yes;" | |||
| Else | |||
| connStr = "Provider=SQLOLEDB;" & _ | |||
| "Server=" & server & ";" & _ | |||
| "Database=" & database & ";" & _ | |||
| "User ID=" & uid & ";" & _ | |||
| "Password=" & pwd & ";" | |||
| End If | |||
| Set ConnectToSQLServer = Me.Connect(connStr) | |||
| End Function | |||
| '---------------------------------------- | |||
| ' Connect via ODBC DSN | |||
| '---------------------------------------- | |||
| Public Function ConnectToODBC(dsnName, uid, pwd) | |||
| Dim connStr | |||
| connStr = "DSN=" & dsnName & ";" | |||
| If Not IsEmpty(uid) Then connStr = connStr & "UID=" & uid & ";" | |||
| If Not IsEmpty(pwd) Then connStr = connStr & "PWD=" & pwd & ";" | |||
| Set ConnectToODBC = Me.Connect(connStr) | |||
| End Function | |||
| '---------------------------------------- | |||
| ' Generic Connect: opens and returns an ADODB.Connection | |||
| ' Includes separate handling for creation and open errors | |||
| '---------------------------------------- | |||
| Public Function Connect(connectionString) | |||
| On Error Resume Next | |||
| ' Dispose previous connection if any | |||
| If Not conn Is Nothing Then | |||
| conn.Close | |||
| Set conn = Nothing | |||
| End If | |||
| ' Create ADO Connection object | |||
| Set conn = Server.CreateObject("ADODB.Connection") | |||
| If conn Is Nothing Then | |||
| Err.Clear | |||
| On Error GoTo 0 | |||
| Err.Raise 50000, _ | |||
| "DatabaseConnection_Class.Connect", _ | |||
| "Could not create ADODB.Connection. Ensure ADO is installed and registered." | |||
| End If | |||
| ' Clear any prior errors before opening | |||
| Err.Clear | |||
| ' Open database connection | |||
| conn.Open connectionString | |||
| If Err.Number <> 0 Then | |||
| Dim lastErrNum, lastErrDesc | |||
| lastErrNum = Err.Number | |||
| lastErrDesc = Err.Description | |||
| Err.Clear | |||
| On Error GoTo 0 | |||
| Err.Raise lastErrNum, _ | |||
| "DatabaseConnection_Class.Connect", _ | |||
| "Failed to open connection (" & connectionString & _ | |||
| ") - Error " & lastErrNum & ": " & lastErrDesc | |||
| End If | |||
| On Error GoTo 0 | |||
| Set Connect = conn | |||
| End Function | |||
| '---------------------------------------- | |||
| ' Close & clean up | |||
| '---------------------------------------- | |||
| Public Sub Close() | |||
| On Error Resume Next | |||
| If Not conn Is Nothing Then | |||
| conn.Close | |||
| Set conn = Nothing | |||
| End If | |||
| On Error GoTo 0 | |||
| End Sub | |||
| End Class | |||
| %> | |||
| @@ -15,6 +15,9 @@ 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() | |||
| @@ -0,0 +1,52 @@ | |||
| <% | |||
| '========================== | |||
| ' AD_Auth_Class.asp | |||
| '========================== | |||
| Class AD_Auth_Class | |||
| Public DomainName ' e.g. yourdomain.local | |||
| Public ContainerDN ' e.g. DC=yourdomain,DC=local | |||
| Public Username | |||
| Public Password | |||
| Public ErrorMessage | |||
| Public IsAuthenticated | |||
| Public UserObject | |||
| Private Sub Class_Initialize() | |||
| IsAuthenticated = False | |||
| ErrorMessage = "" | |||
| Set UserObject = Nothing | |||
| End Sub | |||
| Public Function Authenticate() | |||
| On Error Resume Next | |||
| Dim ldapPath, userCredential | |||
| ldapPath = "LDAP://" & ContainerDN ' Must be DC=yourdomain,DC=local | |||
| userCredential = DomainName & "\" & Username | |||
| ' Try to bind to Active Directory | |||
| Dim rootDSE : Set rootDSE = GetObject("LDAP:") | |||
| Set UserObject = rootDSE.OpenDSObject(ldapPath, userCredential, Password, 1) | |||
| If Err.Number <> 0 Then | |||
| ErrorMessage = "Authentication failed: " & Err.Description | |||
| IsAuthenticated = False | |||
| Set UserObject = Nothing | |||
| Else | |||
| IsAuthenticated = True | |||
| ErrorMessage = "" | |||
| End If | |||
| Authenticate = IsAuthenticated | |||
| On Error GoTo 0 | |||
| End Function | |||
| End Class | |||
| dim AD_Auth_Class__Singleton | |||
| Function AdAuth() | |||
| if IsEmpty(AD_Auth_Class__Singleton) then | |||
| set AD_Auth_Class__Singleton = new AD_Auth_Class | |||
| end if | |||
| set AdAuth = AD_Auth_Class__Singleton | |||
| End Function | |||
| %> | |||
| @@ -0,0 +1,23 @@ | |||
| <% | |||
| '======================================================================================================================= | |||
| ' MIGRATION: add_do_not_call_fields_to_households | |||
| '======================================================================================================================= | |||
| Sub Migration_Up(migration) | |||
| migration.AddColumn "Households", "DoNotCall", "SMALLINT" | |||
| migration.AddColumn "Households", "DoNotCallDate", "DATETIME" | |||
| migration.AddColumn "Households", "DoNotCallNotes", "LONGTEXT" | |||
| migration.AddColumn "Households", "DoNotCallPrivateNotes", "LONGTEXT" | |||
| migration.ExecuteSQL "UPDATE Households SET DoNotCall = 0 WHERE DoNotCall IS NULL" | |||
| migration.CreateIndex "IX_Households_DoNotCall", "Households", "DoNotCall" | |||
| End Sub | |||
| Sub Migration_Down(migration) | |||
| migration.DropIndex "IX_Households_DoNotCall", "Households" | |||
| migration.DropColumn "Households", "DoNotCallPrivateNotes" | |||
| migration.DropColumn "Households", "DoNotCallNotes" | |||
| migration.DropColumn "Households", "DoNotCallDate" | |||
| migration.DropColumn "Households", "DoNotCall" | |||
| End Sub | |||
| %> | |||
| @@ -3,10 +3,38 @@ | |||
| <% | |||
| ' Define application routes | |||
| router.AddRoute "GET", "/home", "homeController", "Index" | |||
| router.AddRoute "GET", "/", "homeController", "Index" | |||
| router.AddRoute "GET", "", "homeController", "Index" | |||
| router.AddRoute "GET", "/", "TerritoryController", "Index" | |||
| router.AddRoute "GET", "", "TerritoryController", "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,324 @@ | |||
| <# | |||
| Deploy asp-territory to an existing IIS site, locally or over SSH. | |||
| Remote mode: | |||
| - Copies this script to the remote Windows host with scp | |||
| - Executes it remotely via ssh in -RunRemoteCore mode | |||
| - Preserves the remote site's current DB path unless -DbPath is passed | |||
| - Can run standard migrations and an optional legacy migration script | |||
| Local / remote core behavior: | |||
| - Infers IIS site/app pool/work dir from the existing site when possible | |||
| - Stops the site/app pool while deploying | |||
| - Clones/pulls and hard-resets to origin/<Branch> | |||
| - Points IIS at <WorkDir>\public | |||
| - Reapplies the effective DB path in public\web.config | |||
| - Grants IIS AppPool rights to the DB folder | |||
| - Runs migrations | |||
| - Restarts the site/app pool and smoke tests key routes | |||
| #> | |||
| param( | |||
| [string]$Repo = 'git@onefortheroadgit.sytes.net:dcovington/asp-classic-unified-framework.git', | |||
| [string]$Branch = 'main', | |||
| [string]$SiteName = 'ttasp', | |||
| [string]$AppPool = '', | |||
| [string]$WorkDir = '', | |||
| [string]$PublicDir = '', | |||
| [string]$BaseUrl = '', | |||
| [string]$DbPath = '', | |||
| [switch]$RunMigrations = $true, | |||
| [switch]$SkipLegacyIsBusinessMigration, | |||
| [string]$LegacyMigrationScript = 'scripts\migrate_isbusiness_to_households.vbs', | |||
| [switch]$UseRemoteSsh, | |||
| [string]$RemoteTarget = '', | |||
| [int]$RemotePort = 22, | |||
| [string]$SshExe = 'ssh', | |||
| [string]$ScpExe = 'scp', | |||
| [switch]$RunRemoteCore | |||
| ) | |||
| $ErrorActionPreference = 'Stop' | |||
| function Ensure-Dir { | |||
| param([string]$Path) | |||
| if([string]::IsNullOrWhiteSpace($Path)){ return } | |||
| if(!(Test-Path $Path)){ | |||
| New-Item -ItemType Directory -Force -Path $Path | Out-Null | |||
| } | |||
| } | |||
| function Ensure-Command { | |||
| param([string]$Name) | |||
| if(!(Get-Command $Name -ErrorAction SilentlyContinue)){ | |||
| throw "$Name not found on PATH" | |||
| } | |||
| } | |||
| function Get-DefaultRemoteTargetFromInfo { | |||
| $infoPath = Join-Path $PSScriptRoot 'depolyinfo.txt' | |||
| if(!(Test-Path $infoPath)){ return '' } | |||
| $sshLine = Get-Content $infoPath | Where-Object { $_ -match '^\s*ssh\s+' } | Select-Object -First 1 | |||
| if([string]::IsNullOrWhiteSpace($sshLine)){ return '' } | |||
| return ($sshLine -replace '^\s*ssh\s+', '').Trim() | |||
| } | |||
| function ConvertTo-PowerShellLiteral { | |||
| param([AllowNull()][string]$Value) | |||
| if($null -eq $Value){ return "''" } | |||
| return "'" + ($Value -replace "'", "''") + "'" | |||
| } | |||
| function ConvertTo-CmdDoubleQuoted { | |||
| param([AllowNull()][string]$Value) | |||
| if($null -eq $Value){ return '""' } | |||
| return '"' + ($Value -replace '"', '""') + '"' | |||
| } | |||
| function Get-DataSourceFromConfig { | |||
| param([string]$ConfigPath) | |||
| if(!(Test-Path $ConfigPath)){ return '' } | |||
| $raw = Get-Content $ConfigPath -Raw | |||
| $match = [regex]::Match($raw, 'Data Source=([^;]+);', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) | |||
| if($match.Success){ | |||
| return $match.Groups[1].Value.Trim() | |||
| } | |||
| return '' | |||
| } | |||
| function Set-DataSourceInConfig { | |||
| param( | |||
| [string]$ConfigPath, | |||
| [string]$EffectiveDbPath | |||
| ) | |||
| if(!(Test-Path $ConfigPath)){ return } | |||
| $raw = Get-Content $ConfigPath -Raw | |||
| $updated = [regex]::Replace( | |||
| $raw, | |||
| 'Data Source=[^;]*;', | |||
| ('Data Source=' + $EffectiveDbPath + ';'), | |||
| [System.Text.RegularExpressions.RegexOptions]::IgnoreCase | |||
| ) | |||
| if($updated -ne $raw){ | |||
| Set-Content -Path $ConfigPath -Value $updated -Encoding UTF8 | |||
| Write-Host "Updated ConnectionString Data Source to $EffectiveDbPath" | |||
| } | |||
| } | |||
| function Get-BaseUrlFromSite { | |||
| param($Site) | |||
| $httpBind = $Site.Bindings.Collection | Where-Object { $_.protocol -eq 'http' } | Select-Object -First 1 | |||
| if($httpBind){ | |||
| $parts = $httpBind.bindingInformation.Split(':') | |||
| $port = $parts[1] | |||
| if([string]::IsNullOrWhiteSpace($port)){ $port = '80' } | |||
| return ('http://127.0.0.1:' + $port) | |||
| } | |||
| return 'http://127.0.0.1' | |||
| } | |||
| function Invoke-DeployCore { | |||
| Ensure-Command git | |||
| Import-Module WebAdministration | |||
| $site = Get-Website -Name $SiteName | |||
| if(!$site){ throw "IIS site not found: $SiteName" } | |||
| if([string]::IsNullOrWhiteSpace($AppPool)){ | |||
| $AppPool = $site.applicationPool | |||
| } | |||
| if([string]::IsNullOrWhiteSpace($PublicDir)){ | |||
| $PublicDir = $site.physicalPath | |||
| } | |||
| if([string]::IsNullOrWhiteSpace($WorkDir)){ | |||
| $pd = [Environment]::ExpandEnvironmentVariables($PublicDir) | |||
| $pd = $pd.Trim().Trim('"') | |||
| $pd = $pd.TrimEnd('\','/') | |||
| if((Split-Path $pd -Leaf).ToLower() -eq 'public'){ | |||
| $WorkDir = Split-Path $pd -Parent | |||
| } else { | |||
| $WorkDir = $pd | |||
| } | |||
| } | |||
| if([string]::IsNullOrWhiteSpace($BaseUrl)){ | |||
| $BaseUrl = Get-BaseUrlFromSite -Site $site | |||
| } | |||
| $currentPublicDir = $PublicDir | |||
| $currentConfigPath = Join-Path $currentPublicDir 'web.config' | |||
| $effectiveDbPath = $DbPath | |||
| if([string]::IsNullOrWhiteSpace($effectiveDbPath)){ | |||
| $effectiveDbPath = Get-DataSourceFromConfig -ConfigPath $currentConfigPath | |||
| } | |||
| if([string]::IsNullOrWhiteSpace($effectiveDbPath)){ | |||
| throw 'No database path was provided and no existing Data Source could be read from the current web.config' | |||
| } | |||
| Write-Host "Stopping IIS site $SiteName and app pool $AppPool" | |||
| try { Stop-Website -Name $SiteName } catch { } | |||
| try { Stop-WebAppPool -Name $AppPool } catch { } | |||
| Ensure-Dir (Split-Path $WorkDir -Parent) | |||
| if((Test-Path $WorkDir) -and !(Test-Path (Join-Path $WorkDir '.git'))){ | |||
| $bak = ($WorkDir.TrimEnd('\') + '_pre_git_' + (Get-Date -Format 'yyyyMMdd_HHmmss')) | |||
| Write-Host "Existing non-git folder detected. Moving to $bak" | |||
| Move-Item -Force $WorkDir $bak | |||
| } | |||
| if(!(Test-Path $WorkDir)){ | |||
| Write-Host "Cloning $Repo -> $WorkDir" | |||
| git clone $Repo $WorkDir | |||
| } | |||
| Push-Location $WorkDir | |||
| try { | |||
| Write-Host "Updating to origin/$Branch" | |||
| git fetch origin | |||
| git checkout $Branch | |||
| & git reset --hard ("origin/" + $Branch) | |||
| } finally { | |||
| Pop-Location | |||
| } | |||
| if((Split-Path $WorkDir -Leaf).ToLower() -eq 'public'){ | |||
| $WorkDir = Split-Path $WorkDir -Parent | |||
| } | |||
| $PublicDir = Join-Path $WorkDir 'public' | |||
| $cfg = Join-Path $PublicDir 'web.config' | |||
| Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $PublicDir | |||
| Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name applicationPool -Value $AppPool | |||
| Set-ItemProperty ('IIS:\AppPools\' + $AppPool) -Name processModel.identityType -Value NetworkService | |||
| Set-DataSourceInConfig -ConfigPath $cfg -EffectiveDbPath $effectiveDbPath | |||
| $dbFolder = Split-Path $effectiveDbPath -Parent | |||
| if(!(Test-Path $dbFolder)){ | |||
| Ensure-Dir $dbFolder | |||
| } | |||
| icacls $dbFolder /grant ("IIS AppPool\" + $AppPool + ":(OI)(CI)(M)") /T | Out-Null | |||
| Push-Location $WorkDir | |||
| try { | |||
| if($RunMigrations){ | |||
| Write-Host 'Running standard migrations' | |||
| cscript //nologo scripts\runMigrations.vbs up | |||
| } | |||
| if(-not $SkipLegacyIsBusinessMigration){ | |||
| $legacyPath = Join-Path $WorkDir $LegacyMigrationScript | |||
| if(!(Test-Path $legacyPath)){ | |||
| throw "Legacy migration script not found: $legacyPath" | |||
| } | |||
| Write-Host 'Running legacy IsBusiness migration' | |||
| cscript //nologo $legacyPath $effectiveDbPath | |||
| } | |||
| } finally { | |||
| Pop-Location | |||
| } | |||
| if((Get-WebAppPoolState -Name $AppPool).Value -eq 'Started'){ | |||
| Restart-WebAppPool -Name $AppPool | |||
| } else { | |||
| Start-WebAppPool -Name $AppPool | |||
| } | |||
| Start-Website $SiteName | |||
| Start-Sleep -Seconds 1 | |||
| $paths = @('/','/territories','/households','/householder-names') | |||
| foreach($path in $paths){ | |||
| $url = $BaseUrl + $path | |||
| $response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 30 | |||
| Write-Host ("OK " + $path + ' -> ' + $response.StatusCode) | |||
| } | |||
| Write-Host 'Deploy complete.' | |||
| } | |||
| if($UseRemoteSsh -and !$RunRemoteCore -and [string]::IsNullOrWhiteSpace($RemoteTarget)){ | |||
| $RemoteTarget = Get-DefaultRemoteTargetFromInfo | |||
| } | |||
| if($UseRemoteSsh -and !$RunRemoteCore -and -not [string]::IsNullOrWhiteSpace($RemoteTarget)){ | |||
| Ensure-Command $SshExe | |||
| Ensure-Command $ScpExe | |||
| $remoteScriptPath = 'C:\Windows\Temp\deploy-test-territory-git.ps1' | |||
| $scpDestination = "${RemoteTarget}:C:/Windows/Temp/deploy-test-territory-git.ps1" | |||
| Write-Host "Copying deploy script to $RemoteTarget" | |||
| & $ScpExe -P $RemotePort $PSCommandPath $scpDestination | |||
| if($LASTEXITCODE -ne 0){ throw 'scp failed' } | |||
| $remoteCommand = New-Object System.Collections.Generic.List[string] | |||
| @( | |||
| 'powershell', | |||
| '-NoProfile', | |||
| '-ExecutionPolicy', 'Bypass', | |||
| '-File', (ConvertTo-CmdDoubleQuoted $remoteScriptPath), | |||
| '-RunRemoteCore', | |||
| '-Repo', (ConvertTo-CmdDoubleQuoted $Repo), | |||
| '-Branch', (ConvertTo-CmdDoubleQuoted $Branch), | |||
| '-SiteName', (ConvertTo-CmdDoubleQuoted $SiteName) | |||
| ) | ForEach-Object { [void]$remoteCommand.Add($_) } | |||
| if(-not [string]::IsNullOrWhiteSpace($AppPool)){ | |||
| [void]$remoteCommand.Add('-AppPool') | |||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $AppPool)) | |||
| } | |||
| if(-not [string]::IsNullOrWhiteSpace($WorkDir)){ | |||
| [void]$remoteCommand.Add('-WorkDir') | |||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $WorkDir)) | |||
| } | |||
| if(-not [string]::IsNullOrWhiteSpace($PublicDir)){ | |||
| [void]$remoteCommand.Add('-PublicDir') | |||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $PublicDir)) | |||
| } | |||
| if(-not [string]::IsNullOrWhiteSpace($BaseUrl)){ | |||
| [void]$remoteCommand.Add('-BaseUrl') | |||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $BaseUrl)) | |||
| } | |||
| if(-not [string]::IsNullOrWhiteSpace($DbPath)){ | |||
| [void]$remoteCommand.Add('-DbPath') | |||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $DbPath)) | |||
| } | |||
| if(-not [string]::IsNullOrWhiteSpace($LegacyMigrationScript)){ | |||
| [void]$remoteCommand.Add('-LegacyMigrationScript') | |||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $LegacyMigrationScript)) | |||
| } | |||
| if($RunMigrations){ $remoteCommand += '-RunMigrations' } | |||
| if($SkipLegacyIsBusinessMigration){ $remoteCommand += '-SkipLegacyIsBusinessMigration' } | |||
| Write-Host "Executing remote deploy on $RemoteTarget" | |||
| & $SshExe -p $RemotePort $RemoteTarget ($remoteCommand -join ' ') | |||
| if($LASTEXITCODE -ne 0){ throw 'remote deploy failed' } | |||
| exit 0 | |||
| } | |||
| Invoke-DeployCore | |||
| @@ -0,0 +1,90 @@ | |||
| ' migrate_isbusiness_to_households.vbs | |||
| ' Moves IsBusiness from HouseholderNames to Households. | |||
| ' | |||
| ' Usage: | |||
| ' cscript //nologo scripts\migrate_isbusiness_to_households.vbs "C:\path\to\myAccessFile.accdb" | |||
| ' | |||
| ' What it does: | |||
| ' 1) Adds Households.IsBusiness (SMALLINT) if missing | |||
| ' 2) Copies data: sets Households.IsBusiness=1 if any related HouseholderNames.IsBusiness<>0 | |||
| ' 3) Sets NULLs to 0 | |||
| ' 4) Drops HouseholderNames.IsBusiness if present | |||
| ' | |||
| Option Explicit | |||
| Dim dbPath | |||
| If WScript.Arguments.Count < 1 Then | |||
| WScript.Echo "ERROR: missing db path." | |||
| WScript.Echo "Usage: cscript //nologo scripts\migrate_isbusiness_to_households.vbs ""C:\path\to\db.accdb""" | |||
| WScript.Quit 1 | |||
| End If | |||
| dbPath = WScript.Arguments(0) | |||
| Dim conn | |||
| Set conn = CreateObject("ADODB.Connection") | |||
| conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath & ";Persist Security Info=False;" | |||
| On Error Resume Next | |||
| If Not ColumnExists(conn, "Households", "IsBusiness") Then | |||
| Exec conn, "ALTER TABLE [Households] ADD COLUMN [IsBusiness] SMALLINT" | |||
| If Err.Number <> 0 Then | |||
| WScript.Echo "ERROR adding Households.IsBusiness: " & Err.Description | |||
| WScript.Quit 1 | |||
| End If | |||
| WScript.Echo "Added Households.IsBusiness" | |||
| Else | |||
| WScript.Echo "Households.IsBusiness already exists" | |||
| End If | |||
| ' Copy data (only if the old column exists) | |||
| If ColumnExists(conn, "HouseholderNames", "IsBusiness") Then | |||
| ' Normalize all existing households first so the column is never left NULL. | |||
| Exec conn, "UPDATE [Households] SET [IsBusiness]=0" | |||
| If Err.Number <> 0 Then | |||
| WScript.Echo "ERROR initializing Households.IsBusiness: " & Err.Description | |||
| WScript.Quit 1 | |||
| End If | |||
| ' Promote households to business when any related name was previously marked as a business. | |||
| Exec conn, "UPDATE [Households] SET [IsBusiness]=1 WHERE [Id] IN (SELECT [HouseholdId] FROM [HouseholderNames] WHERE [IsBusiness]<>0)" | |||
| If Err.Number <> 0 Then | |||
| WScript.Echo "ERROR copying IsBusiness to Households: " & Err.Description | |||
| WScript.Quit 1 | |||
| End If | |||
| WScript.Echo "Copied IsBusiness values to Households" | |||
| Exec conn, "ALTER TABLE [HouseholderNames] DROP COLUMN [IsBusiness]" | |||
| If Err.Number <> 0 Then | |||
| WScript.Echo "ERROR dropping HouseholderNames.IsBusiness: " & Err.Description | |||
| WScript.Quit 1 | |||
| End If | |||
| WScript.Echo "Dropped HouseholderNames.IsBusiness" | |||
| Else | |||
| WScript.Echo "HouseholderNames.IsBusiness does not exist; nothing to drop" | |||
| End If | |||
| conn.Close | |||
| Set conn = Nothing | |||
| WScript.Echo "Done." | |||
| ' --- helpers --- | |||
| Sub Exec(c, sql) | |||
| Err.Clear | |||
| c.Execute sql | |||
| End Sub | |||
| Function ColumnExists(c, tableName, colName) | |||
| Dim rs | |||
| ColumnExists = False | |||
| Err.Clear | |||
| Set rs = c.OpenSchema(4, Array(Empty, Empty, tableName, colName)) ' adSchemaColumns=4 | |||
| If Err.Number <> 0 Then | |||
| Err.Clear | |||
| Exit Function | |||
| End If | |||
| If Not rs.EOF Then ColumnExists = True | |||
| rs.Close | |||
| Set rs = Nothing | |||
| End Function | |||
Powered by TurnKey Linux.