Bläddra i källkod

version 1 is done

main^2
Daniel Covington 6 dagar sedan
förälder
incheckning
7816b51957
21 ändrade filer med 745 tillägg och 197 borttagningar
  1. +37
    -10
      app/controllers/BoardsController.asp
  2. +70
    -12
      app/controllers/CardsController.asp
  3. +26
    -3
      app/controllers/ColumnsController.asp
  4. +26
    -3
      app/controllers/SwimLanesController.asp
  5. +18
    -8
      app/models/POBO_boards.asp
  6. +39
    -12
      app/models/POBO_cards.asp
  7. +7
    -7
      app/repositories/boards_Repository.asp
  8. +23
    -5
      app/repositories/cards_Repository.asp
  9. +23
    -3
      app/views/Boards/Create.asp
  10. +25
    -0
      app/views/Boards/Edit.asp
  11. +22
    -2
      app/views/Boards/Show.asp
  12. +22
    -0
      app/views/Cards/_modal.asp
  13. +15
    -0
      db/migrations/20260423100000_add_printstream_to_boards.asp
  14. +19
    -0
      db/migrations/20260424100000_add_printstream_fields_to_cards.asp
  15. +13
    -0
      db/migrations/20260424200000_add_full_note_to_cards.asp
  16. Binär
      db/webdata.accdb
  17. +163
    -15
      public/css/kanban.css
  18. +162
    -18
      public/js/kanban-board.js
  19. +35
    -9
      public/js/kanban-modal.js
  20. Binär
      scripts/importPrintStreamJobs.vbs
  21. +0
    -90
      scripts/migrate_isbusiness_to_households.vbs

+ 37
- 10
app/controllers/BoardsController.asp Visa fil

@@ -50,12 +50,14 @@ Class BoardsController_Class
slug = boards_Repository().UniqueSlug(GenerateSlug(boardName), 0)

Dim board : Set board = New POBO_boards
board.name = boardName
board.slug = slug
board.created_at = Now()
board.created_by = currentUsername
board.updated_at = Now()
board.updated_by = currentUsername
board.name = boardName
board.slug = slug
board.import_from_printstream = (Request.Form("import_from_printstream") = "on")
board.printstream_job_name = Trim(CStr(Request.Form("printstream_job_name")))
board.created_at = Now()
board.created_by = currentUsername
board.updated_at = Now()
board.updated_by = currentUsername

boards_Repository().AddNew board

@@ -117,6 +119,11 @@ Class BoardsController_Class
"""swim_lane_id"":" & cardItem.swim_lane_id & "," & _
"""job_number"":" & JsonStr(cardItem.job_number) & "," & _
"""job_name"":" & JsonStr(cardItem.job_name) & "," & _
"""customer_name"":" & JsonStr(cardItem.customer_name) & "," & _
"""delivery_date"":" & JsonDateStr(cardItem.delivery_date) & "," & _
"""quantity"":" & JsonStr(cardItem.quantity) & "," & _
"""notes"":" & JsonStr(cardItem.notes) & "," & _
"""full_note"":" & JsonStr(cardItem.full_note) & "," & _
"""position"":" & cardItem.position & "}"
firstCard = False
Loop
@@ -168,10 +175,12 @@ Class BoardsController_Class

Dim newSlug : newSlug = boards_Repository().UniqueSlug(GenerateSlug(newName), CLng(board.id))

board.name = newName
board.slug = newSlug
board.updated_at = Now()
board.updated_by = GetCurrentUsername()
board.name = newName
board.slug = newSlug
board.import_from_printstream = (Request.Form("import_from_printstream") = "on")
board.printstream_job_name = Trim(CStr(Request.Form("printstream_job_name")))
board.updated_at = Now()
board.updated_by = GetCurrentUsername()

boards_Repository().Update board

@@ -226,6 +235,24 @@ Class BoardsController_Class
JsonStr = """" & v & """"
End Function

Private Function JsonDateStr(d)
If IsNull(d) Or IsEmpty(d) Then
JsonDateStr = "null"
Exit Function
End If
On Error Resume Next
Dim dt, s
dt = CDate(d)
s = Year(dt) & "-" & Right("0" & Month(dt), 2) & "-" & Right("0" & Day(dt), 2)
If Err.Number <> 0 Then
Err.Clear
JsonDateStr = "null"
Else
JsonDateStr = """" & s & """"
End If
On Error GoTo 0
End Function

End Class

Dim BoardsController_Class__Singleton


+ 70
- 12
app/controllers/CardsController.asp Visa fil

@@ -37,22 +37,39 @@ Class CardsController_Class
Dim username : username = GetCurrentUsername()

Dim card : Set card = New POBO_cards
card.board_id = boardId
card.column_id = columnId
card.swim_lane_id = swimLaneId
card.job_number = jobNum
card.job_name = jobName
card.position = nextPos
card.created_at = Now()
card.created_by = username
card.updated_at = Now()
card.updated_by = username
card.board_id = boardId
card.column_id = columnId
card.swim_lane_id = swimLaneId
card.job_number = jobNum
card.job_name = jobName
card.customer_name = Trim(CStr(Request.Form("customer_name") & ""))
card.delivery_date = Trim(CStr(Request.Form("delivery_date") & ""))
card.quantity = Trim(CStr(Request.Form("quantity") & ""))
card.notes = Trim(CStr(Request.Form("notes") & ""))
card.full_note = CStr(Request.Form("full_note") & "")
card.position = nextPos
card.created_at = Now()
card.created_by = username
card.updated_at = Now()
card.updated_by = username

On Error Resume Next
cards_Repository().AddNew card
If Err.Number <> 0 Then
Response.Write "{""ok"":false,""error"":" & JsonString(Err.Description) & "}"
Err.Clear
Exit Sub
End If
On Error GoTo 0

Response.Write "{""ok"":true,""id"":" & card.id & "," & _
"""job_number"":" & JsonString(card.job_number) & "," & _
"""job_name"":" & JsonString(card.job_name) & "," & _
"""customer_name"":" & JsonString(card.customer_name) & "," & _
"""delivery_date"":" & JsonDateStr(card.delivery_date) & "," & _
"""quantity"":" & JsonString(card.quantity) & "," & _
"""notes"":" & JsonString(card.notes) & "," & _
"""full_note"":" & JsonString(card.full_note) & "," & _
"""column_id"":" & card.column_id & "," & _
"""swim_lane_id"":" & card.swim_lane_id & "," & _
"""position"":" & card.position & "}"
@@ -77,14 +94,31 @@ Class CardsController_Class

card.job_number = Trim(CStr(Request.Form("job_number")))
card.job_name = Trim(CStr(Request.Form("job_name")))
card.customer_name = Trim(CStr(Request.Form("customer_name") & ""))
card.delivery_date = Trim(CStr(Request.Form("delivery_date") & ""))
card.quantity = Trim(CStr(Request.Form("quantity") & ""))
card.notes = Trim(CStr(Request.Form("notes") & ""))
card.full_note = CStr(Request.Form("full_note") & "")
card.updated_at = Now()
card.updated_by = GetCurrentUsername()

On Error Resume Next
cards_Repository().Update card
If Err.Number <> 0 Then
Response.Write "{""ok"":false,""error"":" & JsonString(Err.Description) & "}"
Err.Clear
Exit Sub
End If
On Error GoTo 0

Response.Write "{""ok"":true," & _
"""job_number"":" & JsonString(card.job_number) & "," & _
"""job_name"":" & JsonString(card.job_name) & "}"
"""job_name"":" & JsonString(card.job_name) & "," & _
"""customer_name"":" & JsonString(card.customer_name) & "," & _
"""delivery_date"":" & JsonDateStr(card.delivery_date) & "," & _
"""quantity"":" & JsonString(card.quantity) & "," & _
"""notes"":" & JsonString(card.notes) & "," & _
"""full_note"":" & JsonString(card.full_note) & "}"
End Sub

' POST /cards/:id/move — form: column_id, swim_lane_id, position, [sibling_ids CSV for reorder]
@@ -147,7 +181,31 @@ Class CardsController_Class
End Function

Private Function JsonString(s)
JsonString = """" & Replace(Replace(CStr(s), "\", "\\"), """", "\""") & """"
Dim v : v = CStr(s & "")
v = Replace(v, "\", "\\")
v = Replace(v, """", "\""")
v = Replace(v, vbCrLf, "\n")
v = Replace(v, vbLf, "\n")
v = Replace(v, vbCr, "\n")
JsonString = """" & v & """"
End Function

Private Function JsonDateStr(d)
If IsNull(d) Or IsEmpty(d) Then
JsonDateStr = "null"
Exit Function
End If
On Error Resume Next
Dim dt, s
dt = CDate(d)
s = Year(dt) & "-" & Right("0" & Month(dt), 2) & "-" & Right("0" & Day(dt), 2)
If Err.Number <> 0 Then
Err.Clear
JsonDateStr = "null"
Else
JsonDateStr = """" & s & """"
End If
On Error GoTo 0
End Function

End Class


+ 26
- 3
app/controllers/ColumnsController.asp Visa fil

@@ -102,19 +102,42 @@ Class ColumnsController_Class
End If

Dim rawJson : rawJson = GetRawJsonFromRequest()
Dim parsed : Set parsed = JSON.parse(rawJson)
Dim parser : Set parser = New aspJSON
Dim parsed

If IsNull(parsed) Or IsEmpty(parsed) Then
Response.Write "{""ok"":false,""error"":""Invalid JSON""}"
On Error Resume Next
parser.loadJSON rawJson
If Err.Number <> 0 Then
Err.Clear
Set parser = Nothing
Response.Write "{""ok"":false,""error"":""Invalid JSON payload""}"
Exit Sub
End If
Set parsed = parser.data
On Error GoTo 0

If parsed Is Nothing Or parsed.Count = 0 Then
Set parser = Nothing
Response.Write "{""ok"":false,""error"":""Invalid JSON payload""}"
Exit Sub
End If

Dim username : username = GetCurrentUsername()
Dim i, item
On Error Resume Next
For i = 0 To parsed.Count - 1
Set item = parsed.Item(i)
board_columns_Repository().UpdatePosition CLng(item.Item("id")), CLng(item.Item("position")), Now(), username
If Err.Number <> 0 Then
Err.Clear
On Error GoTo 0
Set parser = Nothing
Response.Write "{""ok"":false,""error"":""Invalid reorder item at index " & i & """}"
Exit Sub
End If
Next
On Error GoTo 0
Set parser = Nothing

Response.Write "{""ok"":true}"
End Sub


+ 26
- 3
app/controllers/SwimLanesController.asp Visa fil

@@ -102,19 +102,42 @@ Class SwimLanesController_Class
End If

Dim rawJson : rawJson = GetRawJsonFromRequest()
Dim parsed : Set parsed = JSON.parse(rawJson)
Dim parser : Set parser = New aspJSON
Dim parsed

If IsNull(parsed) Or IsEmpty(parsed) Then
Response.Write "{""ok"":false,""error"":""Invalid JSON""}"
On Error Resume Next
parser.loadJSON rawJson
If Err.Number <> 0 Then
Err.Clear
Set parser = Nothing
Response.Write "{""ok"":false,""error"":""Invalid JSON payload""}"
Exit Sub
End If
Set parsed = parser.data
On Error GoTo 0

If parsed Is Nothing Or parsed.Count = 0 Then
Set parser = Nothing
Response.Write "{""ok"":false,""error"":""Invalid JSON payload""}"
Exit Sub
End If

Dim username : username = GetCurrentUsername()
Dim i, item
On Error Resume Next
For i = 0 To parsed.Count - 1
Set item = parsed.Item(i)
swim_lanes_Repository().UpdatePosition CLng(item.Item("id")), CLng(item.Item("position")), Now(), username
If Err.Number <> 0 Then
Err.Clear
On Error GoTo 0
Set parser = Nothing
Response.Write "{""ok"":false,""error"":""Invalid reorder item at index " & i & """}"
Exit Sub
End If
Next
On Error GoTo 0
Set parser = Nothing

Response.Write "{""ok"":true}"
End Sub


+ 18
- 8
app/models/POBO_boards.asp Visa fil

@@ -5,20 +5,24 @@ Class POBO_boards
Private p_id
Private p_name
Private p_slug
Private p_import_from_printstream
Private p_printstream_job_name
Private p_created_at
Private p_created_by
Private p_updated_at
Private p_updated_by

Private Sub Class_Initialize()
p_id = 0
p_name = ""
p_slug = ""
p_created_at = #1/1/1970#
p_created_by = ""
p_updated_at = #1/1/1970#
p_updated_by = ""
Properties = Array("id","name","slug","created_at","created_by","updated_at","updated_by")
p_id = 0
p_name = ""
p_slug = ""
p_import_from_printstream = False
p_printstream_job_name = ""
p_created_at = #1/1/1970#
p_created_by = ""
p_updated_at = #1/1/1970#
p_updated_by = ""
Properties = Array("id","name","slug","import_from_printstream","printstream_job_name","created_at","created_by","updated_at","updated_by")
End Sub

Public Property Get PrimaryKey() : PrimaryKey = "id" : End Property
@@ -33,6 +37,12 @@ Class POBO_boards
Public Property Get slug() : slug = p_slug : End Property
Public Property Let slug(v) : p_slug = CStr(v) : End Property

Public Property Get import_from_printstream() : import_from_printstream = p_import_from_printstream : End Property
Public Property Let import_from_printstream(v) : p_import_from_printstream = CBool(v) : End Property

Public Property Get printstream_job_name() : printstream_job_name = p_printstream_job_name : End Property
Public Property Let printstream_job_name(v) : p_printstream_job_name = CStr(v) : End Property

Public Property Get created_at() : created_at = p_created_at : End Property
Public Property Let created_at(v) : p_created_at = CDate(v) : End Property



+ 39
- 12
app/models/POBO_cards.asp Visa fil

@@ -8,6 +8,11 @@ Class POBO_cards
Private p_swim_lane_id
Private p_job_number
Private p_job_name
Private p_customer_name
Private p_delivery_date
Private p_quantity
Private p_notes
Private p_full_note
Private p_position
Private p_created_at
Private p_created_by
@@ -15,18 +20,23 @@ Class POBO_cards
Private p_updated_by

Private Sub Class_Initialize()
p_id = 0
p_board_id = 0
p_column_id = 0
p_swim_lane_id = 0
p_job_number = ""
p_job_name = ""
p_position = 0
p_created_at = #1/1/1970#
p_created_by = ""
p_updated_at = #1/1/1970#
p_updated_by = ""
Properties = Array("id","board_id","column_id","swim_lane_id","job_number","job_name","position","created_at","created_by","updated_at","updated_by")
p_id = 0
p_board_id = 0
p_column_id = 0
p_swim_lane_id = 0
p_job_number = ""
p_job_name = ""
p_customer_name = ""
p_delivery_date = Null
p_quantity = ""
p_notes = ""
p_full_note = ""
p_position = 0
p_created_at = #1/1/1970#
p_created_by = ""
p_updated_at = #1/1/1970#
p_updated_by = ""
Properties = Array("id","board_id","column_id","swim_lane_id","job_number","job_name","customer_name","delivery_date","quantity","notes","full_note","position","created_at","created_by","updated_at","updated_by")
End Sub

Public Property Get PrimaryKey() : PrimaryKey = "id" : End Property
@@ -50,6 +60,23 @@ Class POBO_cards
Public Property Get job_name() : job_name = p_job_name : End Property
Public Property Let job_name(v) : p_job_name = CStr(v) : End Property

Public Property Get customer_name() : customer_name = p_customer_name : End Property
Public Property Let customer_name(v) : p_customer_name = CStr(v & "") : End Property

Public Property Get delivery_date() : delivery_date = p_delivery_date : End Property
Public Property Let delivery_date(v)
If IsDate(v) Then p_delivery_date = CDate(v) Else p_delivery_date = Null
End Property

Public Property Get quantity() : quantity = p_quantity : End Property
Public Property Let quantity(v) : p_quantity = CStr(v & "") : End Property

Public Property Get notes() : notes = p_notes : End Property
Public Property Let notes(v) : p_notes = CStr(v & "") : End Property

Public Property Get full_note() : full_note = p_full_note : End Property
Public Property Let full_note(v) : p_full_note = CStr(v & "") : End Property

Public Property Get position() : position = p_position : End Property
Public Property Let position(v) : p_position = CDbl(v) : End Property



+ 7
- 7
app/repositories/boards_Repository.asp Visa fil

@@ -2,7 +2,7 @@
Class boards_Repository_Class

Public Function FindByID(id)
Dim sql : sql = "SELECT [id],[name],[slug],[created_at],[created_by],[updated_at],[updated_by] FROM [boards] WHERE [id] = ?"
Dim sql : sql = "SELECT [id],[name],[slug],[import_from_printstream],[printstream_job_name],[created_at],[created_by],[updated_at],[updated_by] FROM [boards] WHERE [id] = ?"
Dim rs : Set rs = DAL.Query(sql, Array(id))
If rs.EOF Then
Err.Raise 1, "boards_Repository_Class", "Board not found with id = " & id
@@ -13,7 +13,7 @@ Class boards_Repository_Class
End Function

Public Function FindBySlug(slug)
Dim sql : sql = "SELECT [id],[name],[slug],[created_at],[created_by],[updated_at],[updated_by] FROM [boards] WHERE [slug] = ?"
Dim sql : sql = "SELECT [id],[name],[slug],[import_from_printstream],[printstream_job_name],[created_at],[created_by],[updated_at],[updated_by] FROM [boards] WHERE [slug] = ?"
Dim rs : Set rs = DAL.Query(sql, Array(slug))
If rs.EOF Then
Set FindBySlug = Nothing
@@ -24,7 +24,7 @@ Class boards_Repository_Class
End Function

Public Function GetAll()
Dim sql : sql = "SELECT [id],[name],[slug],[created_at],[created_by],[updated_at],[updated_by] FROM [boards] ORDER BY [name] ASC"
Dim sql : sql = "SELECT [id],[name],[slug],[import_from_printstream],[printstream_job_name],[created_at],[created_by],[updated_at],[updated_by] FROM [boards] ORDER BY [name] ASC"
Dim rs : Set rs = DAL.Query(sql, Empty)
Dim list : Set list = New LinkedList_Class
Do Until rs.EOF
@@ -60,8 +60,8 @@ Class boards_Repository_Class
End Function

Public Sub AddNew(ByRef model)
Dim sql : sql = "INSERT INTO [boards] ([name],[slug],[created_at],[created_by],[updated_at],[updated_by]) VALUES (?,?,?,?,?,?)"
DAL.Execute sql, Array(model.name, model.slug, model.created_at, model.created_by, model.updated_at, model.updated_by)
Dim sql : sql = "INSERT INTO [boards] ([name],[slug],[import_from_printstream],[printstream_job_name],[created_at],[created_by],[updated_at],[updated_by]) VALUES (?,?,?,?,?,?,?,?)"
DAL.Execute sql, Array(model.name, model.slug, model.import_from_printstream, model.printstream_job_name, model.created_at, model.created_by, model.updated_at, model.updated_by)
Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty)
If Not rsId.EOF Then
If Not IsNull(rsId(0)) Then model.id = rsId(0)
@@ -70,8 +70,8 @@ Class boards_Repository_Class
End Sub

Public Sub Update(model)
Dim sql : sql = "UPDATE [boards] SET [name]=?,[slug]=?,[updated_at]=?,[updated_by]=? WHERE [id]=?"
DAL.Execute sql, Array(model.name, model.slug, model.updated_at, model.updated_by, model.id)
Dim sql : sql = "UPDATE [boards] SET [name]=?,[slug]=?,[import_from_printstream]=?,[printstream_job_name]=?,[updated_at]=?,[updated_by]=? WHERE [id]=?"
DAL.Execute sql, Array(model.name, model.slug, model.import_from_printstream, model.printstream_job_name, model.updated_at, model.updated_by, model.id)
End Sub

Public Sub Delete(id)


+ 23
- 5
app/repositories/cards_Repository.asp Visa fil

@@ -2,7 +2,7 @@
Class cards_Repository_Class

Private Function SelectBase()
SelectBase = "SELECT [id],[board_id],[column_id],[swim_lane_id],[job_number],[job_name],[position],[created_at],[created_by],[updated_at],[updated_by] FROM [cards]"
SelectBase = "SELECT [id],[board_id],[column_id],[swim_lane_id],[job_number],[job_name],[customer_name],[delivery_date],[quantity],[notes],[full_note],[position],[created_at],[created_by],[updated_at],[updated_by] FROM [cards]"
End Function

Public Function FindByID(id)
@@ -52,8 +52,15 @@ Class cards_Repository_Class
End Function

Public Sub AddNew(ByRef model)
Dim sql : sql = "INSERT INTO [cards] ([board_id],[column_id],[swim_lane_id],[job_number],[job_name],[position],[created_at],[created_by],[updated_at],[updated_by]) VALUES (?,?,?,?,?,?,?,?,?,?)"
DAL.Execute sql, Array(model.board_id, model.column_id, model.swim_lane_id, model.job_number, model.job_name, model.position, model.created_at, model.created_by, model.updated_at, model.updated_by)
Dim sql, params
If QuantityIsBlank(model.quantity) Then
sql = "INSERT INTO [cards] ([board_id],[column_id],[swim_lane_id],[job_number],[job_name],[customer_name],[delivery_date],[quantity],[notes],[full_note],[position],[created_at],[created_by],[updated_at],[updated_by]) VALUES (?,?,?,?,?,?,?,NULL,?,?,?,?,?,?,?)"
params = Array(model.board_id, model.column_id, model.swim_lane_id, model.job_number, model.job_name, model.customer_name, model.delivery_date, model.notes, model.full_note, model.position, model.created_at, model.created_by, model.updated_at, model.updated_by)
Else
sql = "INSERT INTO [cards] ([board_id],[column_id],[swim_lane_id],[job_number],[job_name],[customer_name],[delivery_date],[quantity],[notes],[full_note],[position],[created_at],[created_by],[updated_at],[updated_by]) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
params = Array(model.board_id, model.column_id, model.swim_lane_id, model.job_number, model.job_name, model.customer_name, model.delivery_date, model.quantity, model.notes, model.full_note, model.position, model.created_at, model.created_by, model.updated_at, model.updated_by)
End If
DAL.Execute sql, params
Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty)
If Not rsId.EOF Then
If Not IsNull(rsId(0)) Then model.id = rsId(0)
@@ -62,10 +69,21 @@ Class cards_Repository_Class
End Sub

Public Sub Update(model)
Dim sql : sql = "UPDATE [cards] SET [job_number]=?,[job_name]=?,[updated_at]=?,[updated_by]=? WHERE [id]=?"
DAL.Execute sql, Array(model.job_number, model.job_name, model.updated_at, model.updated_by, model.id)
Dim sql, params
If QuantityIsBlank(model.quantity) Then
sql = "UPDATE [cards] SET [job_number]=?,[job_name]=?,[customer_name]=?,[delivery_date]=?,[quantity]=NULL,[notes]=?,[full_note]=?,[updated_at]=?,[updated_by]=? WHERE [id]=?"
params = Array(model.job_number, model.job_name, model.customer_name, model.delivery_date, model.notes, model.full_note, model.updated_at, model.updated_by, model.id)
Else
sql = "UPDATE [cards] SET [job_number]=?,[job_name]=?,[customer_name]=?,[delivery_date]=?,[quantity]=?,[notes]=?,[full_note]=?,[updated_at]=?,[updated_by]=? WHERE [id]=?"
params = Array(model.job_number, model.job_name, model.customer_name, model.delivery_date, model.quantity, model.notes, model.full_note, model.updated_at, model.updated_by, model.id)
End If
DAL.Execute sql, params
End Sub

Private Function QuantityIsBlank(v)
QuantityIsBlank = (Len(Trim(CStr(v & ""))) = 0)
End Function

Public Sub Move(id, columnId, swimLaneId, position, updatedAt, updatedBy)
Dim sql : sql = "UPDATE [cards] SET [column_id]=?,[swim_lane_id]=?,[position]=?,[updated_at]=?,[updated_by]=? WHERE [id]=?"
DAL.Execute sql, Array(columnId, swimLaneId, position, updatedAt, updatedBy, id)


+ 23
- 3
app/views/Boards/Create.asp Visa fil

@@ -19,6 +19,18 @@
<label class="form-label text-muted small">URL Slug <span class="text-secondary">(auto-generated)</span></label>
<div class="form-control bg-light text-muted" id="slug-preview" style="min-height:38px;">&nbsp;</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="import_from_printstream"
name="import_from_printstream" />
<label class="form-check-label" for="import_from_printstream">Import Jobs from Printstream</label>
</div>
</div>
<div class="mb-3" id="printstream-job-name-group" style="display:none;">
<label for="printstream_job_name" class="form-label">Job Name to Import</label>
<textarea class="form-control" id="printstream_job_name" name="printstream_job_name"
rows="4" placeholder="Enter job name(s) to import"></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Create Board</button>
<a href="/boards" class="btn btn-outline-secondary">Cancel</a>
@@ -31,11 +43,19 @@

<script>
(function () {
var nameEl = document.getElementById('name');
var preview = document.getElementById('slug-preview');
var nameEl = document.getElementById('name');
var preview = document.getElementById('slug-preview');
var chk = document.getElementById('import_from_printstream');
var jobGroup = document.getElementById('printstream-job-name-group');

nameEl.addEventListener('input', function () {
preview.textContent = slugify(nameEl.value) || ' ';
preview.textContent = slugify(nameEl.value) || ' ';
});

chk.addEventListener('change', function () {
jobGroup.style.display = this.checked ? '' : 'none';
});

function slugify(s) {
return s.toLowerCase()
.replace(/&/g, 'and')


+ 25
- 0
app/views/Boards/Edit.asp Visa fil

@@ -19,6 +19,20 @@
<label class="form-label text-muted small">Current Slug</label>
<div class="form-control bg-light text-muted"><%= H(board.slug) %></div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="import_from_printstream"
name="import_from_printstream"
<%= IIf(board.import_from_printstream, "checked", "") %> />
<label class="form-check-label" for="import_from_printstream">Import Jobs from Printstream</label>
</div>
</div>
<div class="mb-3" id="printstream-job-name-group"
style="<%= IIf(board.import_from_printstream, "", "display:none;") %>">
<label for="printstream_job_name" class="form-label">Job Name to Import</label>
<textarea class="form-control" id="printstream_job_name" name="printstream_job_name"
rows="4" placeholder="Enter job name(s) to import"><%= H(board.printstream_job_name) %></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="/board/<%= H(board.slug) %>" class="btn btn-outline-secondary">Cancel</a>
@@ -39,3 +53,14 @@
</div>
</div>
</div>

<script>
(function () {
var chk = document.getElementById('import_from_printstream');
var jobGroup = document.getElementById('printstream-job-name-group');

chk.addEventListener('change', function () {
jobGroup.style.display = this.checked ? '' : 'none';
});
})();
</script>

+ 22
- 2
app/views/Boards/Show.asp Visa fil

@@ -10,8 +10,11 @@ Response.CodePage = 65001
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" />
<link href="/css/site.css?v=20260423a" rel="stylesheet" />
<link href="/css/kanban.css?v=20260423a" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Fraunces:opsz,wght@9..144,600&display=swap" rel="stylesheet" />
<link href="/css/site.css?v=20260423b" rel="stylesheet" />
<link href="/css/kanban.css?v=20260423b" rel="stylesheet" />
</head>
<body class="kanban-page">

@@ -23,6 +26,20 @@ Response.CodePage = 65001
</a>
<span class="navbar-brand mb-0 h5 kanban-board-title"><%= H(board.name) %></span>
</div>
<div class="board-header-search">
<label for="job-search-input" class="visually-hidden">Search jobs</label>
<div class="input-group input-group-sm">
<span class="input-group-text">
<i class="bi bi-search"></i>
</span>
<input
type="search"
id="job-search-input"
class="form-control"
placeholder="Search job #, name, customer..."
autocomplete="off" />
</div>
</div>
<div class="d-flex align-items-center gap-2 board-header-actions">
<button class="btn btn-sm btn-outline-light" id="btn-add-card"
data-board-id="<%= board.id %>">
@@ -60,6 +77,9 @@ Response.CodePage = 65001

<!-- Lane header -->
<div class="kanban-lane-header" data-lane-id="<%= vLaneItem.id %>">
<button type="button" class="lane-toggle" title="Collapse or expand swim lane" aria-label="Collapse or expand swim lane" aria-expanded="true">
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
<span class="lane-label"><%= H(vLaneItem.name) %></span>
</div>



+ 22
- 0
app/views/Cards/_modal.asp Visa fil

@@ -19,6 +19,28 @@
<label for="card-job-name" class="form-label">Job Name</label>
<input type="text" class="form-control" id="card-job-name" placeholder="e.g. Smith Residence" />
</div>
<div class="mb-3">
<label for="card-customer-name" class="form-label">Customer</label>
<input type="text" class="form-control" id="card-customer-name" />
</div>
<div class="row g-2 mb-3">
<div class="col">
<label for="card-delivery-date" class="form-label">Delivery Date</label>
<input type="date" class="form-control" id="card-delivery-date" />
</div>
<div class="col">
<label for="card-quantity" class="form-label">Quantity</label>
<input type="text" class="form-control" id="card-quantity" />
</div>
</div>
<div class="mb-3">
<label for="card-notes" class="form-label">Notes</label>
<textarea class="form-control" id="card-notes" rows="3"></textarea>
</div>
<div class="mb-3" id="card-full-note-wrap">
<label for="card-full-note" class="form-label">PrintStream Notes</label>
<textarea class="form-control" id="card-full-note" rows="4" readonly></textarea>
</div>

<div id="card-modal-error" class="alert alert-danger d-none"></div>
</div>


+ 15
- 0
db/migrations/20260423100000_add_printstream_to_boards.asp Visa fil

@@ -0,0 +1,15 @@
<%
'=======================================================================================================================
' MIGRATION: add_printstream_to_boards
'=======================================================================================================================

Sub Migration_Up(migration)
migration.ExecuteSQL "ALTER TABLE [boards] ADD COLUMN [import_from_printstream] YESNO"
migration.ExecuteSQL "ALTER TABLE [boards] ADD COLUMN [printstream_job_name] MEMO"
End Sub

Sub Migration_Down(migration)
migration.ExecuteSQL "ALTER TABLE [boards] DROP COLUMN [printstream_job_name]"
migration.ExecuteSQL "ALTER TABLE [boards] DROP COLUMN [import_from_printstream]"
End Sub
%>

+ 19
- 0
db/migrations/20260424100000_add_printstream_fields_to_cards.asp Visa fil

@@ -0,0 +1,19 @@
<%
'=======================================================================================================================
' MIGRATION: add_printstream_fields_to_cards
'=======================================================================================================================

Sub Migration_Up(migration)
migration.ExecuteSQL "ALTER TABLE [cards] ADD COLUMN [customer_name] VARCHAR(255)"
migration.ExecuteSQL "ALTER TABLE [cards] ADD COLUMN [delivery_date] DATETIME"
migration.ExecuteSQL "ALTER TABLE [cards] ADD COLUMN [quantity] VARCHAR(50)"
migration.ExecuteSQL "ALTER TABLE [cards] ADD COLUMN [notes] MEMO"
End Sub

Sub Migration_Down(migration)
migration.ExecuteSQL "ALTER TABLE [cards] DROP COLUMN [notes]"
migration.ExecuteSQL "ALTER TABLE [cards] DROP COLUMN [quantity]"
migration.ExecuteSQL "ALTER TABLE [cards] DROP COLUMN [delivery_date]"
migration.ExecuteSQL "ALTER TABLE [cards] DROP COLUMN [customer_name]"
End Sub
%>

+ 13
- 0
db/migrations/20260424200000_add_full_note_to_cards.asp Visa fil

@@ -0,0 +1,13 @@
<%
'=======================================================================================================================
' MIGRATION: add_full_note_to_cards
'=======================================================================================================================

Sub Migration_Up(migration)
migration.ExecuteSQL "ALTER TABLE [cards] ADD COLUMN [full_note] MEMO"
End Sub

Sub Migration_Down(migration)
migration.ExecuteSQL "ALTER TABLE [cards] DROP COLUMN [full_note]"
End Sub
%>

Binär
db/webdata.accdb Visa fil


+ 163
- 15
public/css/kanban.css Visa fil

@@ -27,7 +27,6 @@ body.kanban-page .navbar {

body.kanban-page .navbar-brand {
color: #f4f8ff !important;
font-family: "Fraunces", Georgia, serif;
letter-spacing: -0.01em;
}

@@ -39,6 +38,33 @@ body.kanban-page .navbar-brand {
flex-shrink: 0;
}

.board-header-search {
width: min(440px, 36vw);
margin: 0 0.75rem;
}

.board-header-search .input-group-text {
border-color: rgba(214, 229, 250, 0.7);
background: rgba(255, 255, 255, 0.15);
color: #eff6ff;
}

.board-header-search .form-control {
border-color: rgba(214, 229, 250, 0.7);
background: rgba(255, 255, 255, 0.17);
color: #f5f9ff;
}

.board-header-search .form-control::placeholder {
color: rgba(235, 243, 255, 0.72);
}

.board-header-search .form-control:focus {
border-color: rgba(235, 245, 255, 0.95);
box-shadow: 0 0 0 0.2rem rgba(157, 194, 245, 0.25);
background: rgba(255, 255, 255, 0.23);
}

.kanban-board-title {
display: block;
min-width: 0;
@@ -67,21 +93,27 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
width: 100%;
margin: 0 auto;
height: calc(100vh - 65px);
overflow: auto;
overflow-x: auto;
overflow-y: auto;
padding: 0.9rem 1rem 1.1rem;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
touch-action: pan-x pan-y;
overscroll-behavior: contain;
scrollbar-width: thin;
scrollbar-color: #8fb0e0 #dce8f8;
scrollbar-gutter: stable;
}

.kanban-grid {
display: grid;
min-width: max(75vw, max-content);
width: max-content;
min-width: 100%;
border: 1px solid var(--line, #d9e3f5);
border-radius: 14px;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 12px 34px rgba(22, 48, 92, 0.12);
overflow: hidden;
overflow: clip;
}

/* Sticky corner and headers */
@@ -121,6 +153,9 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
left: 0;
z-index: 20;
min-width: 240px;
display: flex;
align-items: center;
gap: 0.42rem;
padding: 0.85rem 0.82rem;
font-size: clamp(0.7rem, 0.14vw + 0.66rem, 0.78rem);
font-weight: 700;
@@ -139,6 +174,33 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
overflow-wrap: anywhere;
}

.kanban-lane-header .lane-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.34rem;
height: 1.34rem;
padding: 0;
border: 0;
border-radius: 999px;
background: rgba(26, 74, 145, 0.12);
color: #1f4f96;
cursor: pointer;
flex: 0 0 auto;
}

.kanban-lane-header .lane-toggle:hover {
background: rgba(26, 74, 145, 0.22);
}

.kanban-lane-header .lane-toggle i {
transition: transform 140ms ease;
}

.kanban-lane-header.lane-collapsed .lane-toggle i {
transform: rotate(-90deg);
}

/* Cells */
.kanban-cell {
min-width: 230px;
@@ -154,6 +216,20 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
linear-gradient(180deg, rgba(255, 255, 255, 0.88) 0%, rgba(244, 248, 255, 0.93) 100%);
}

.kanban-cell.lane-collapsed {
min-height: 0;
max-height: 0;
padding-top: 0;
padding-bottom: 0;
border-top-color: transparent;
overflow: hidden;
pointer-events: none;
}

.kanban-cell.lane-collapsed .kanban-card {
display: none !important;
}

.kanban-cell.drag-over {
background: linear-gradient(180deg, #e9f2ff 0%, #deecff 100%);
box-shadow: inset 0 0 0 2px rgba(19, 99, 223, 0.24);
@@ -187,9 +263,19 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
box-shadow: 0 14px 30px rgba(17, 46, 94, 0.22);
}

.kanban-card-hidden {
display: none !important;
}

.card-headline {
display: flex;
align-items: center;
gap: 0.38rem;
min-width: 0;
}

.card-job-number {
display: inline-block;
margin-bottom: 0.36rem;
padding: 0.08rem 0.42rem;
border-radius: 999px;
font-size: 0.66rem;
@@ -198,12 +284,45 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
text-transform: uppercase;
color: #0e4fae;
background: #e7f0ff;
flex: 0 0 auto;
}

.card-customer {
font-size: 0.78rem;
font-weight: 600;
color: #2b4a80;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.card-job-name {
color: #1f2b43;
font-size: 0.86rem;
line-height: 1.32;
.card-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.25rem;
}

.card-meta-label {
font-size: 0.63rem;
font-weight: 700;
color: #7a90b2;
text-transform: uppercase;
letter-spacing: 0.05em;
}

.card-delivery,
.card-qty {
font-size: 0.73rem;
color: #3a5080;
}

.card-notes {
margin-top: 0.22rem;
font-size: 0.71rem;
color: #647899;
line-height: 1.3;
}

/* Settings panel */
@@ -294,25 +413,30 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
/* Scrollbars */
.kanban-wrapper::-webkit-scrollbar,
.settings-body::-webkit-scrollbar {
width: 10px;
height: 10px;
width: 12px;
height: 12px;
}

.kanban-wrapper::-webkit-scrollbar-track,
.settings-body::-webkit-scrollbar-track {
background: #eaf0fb;
background: #dce8f8;
border-radius: 999px;
}

.kanban-wrapper::-webkit-scrollbar-thumb,
.settings-body::-webkit-scrollbar-thumb {
background: #b8c9e6;
background: #8fb0e0;
border-radius: 999px;
border: 2px solid #eaf0fb;
border: 2px solid #dce8f8;
}

.kanban-wrapper::-webkit-scrollbar-thumb:hover,
.settings-body::-webkit-scrollbar-thumb:hover {
background: #97afd8;
background: #5e8ecb;
}

.kanban-wrapper::-webkit-scrollbar-corner {
background: #dce8f8;
}

/* Small screens */
@@ -340,6 +464,11 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
gap: 0.35rem !important;
}

.board-header-search {
width: min(270px, 44vw);
margin: 0 0.35rem;
}

.board-header-actions .btn {
padding-left: 0.48rem;
padding-right: 0.48rem;
@@ -347,6 +476,25 @@ body.kanban-page .navbar .btn-outline-secondary:hover {
}

@media (max-width: 640px) {
.board-header-search {
width: 100%;
margin: 0.5rem 0 0;
order: 3;
}

body.kanban-page .navbar {
flex-wrap: wrap;
align-items: flex-start !important;
}

.board-header-main {
flex: 1 1 auto;
}

.board-header-actions {
flex: 0 0 auto;
}

.kanban-settings-panel {
width: 100vw;
border-left: 0;


+ 162
- 18
public/js/kanban-board.js Visa fil

@@ -3,6 +3,11 @@
'use strict';

var boardId = KANBAN.boardId;
var laneCollapseStorageKey = 'kanban_lane_collapsed_' + String(boardId);
var collapsedLaneIds = loadCollapsedLaneIds();
var searchState = {
query: ''
};
var dragState = {
active: false,
x: 0,
@@ -35,27 +40,123 @@
var grid = document.getElementById('kanban-grid');
var colHs = grid.querySelectorAll('.kanban-col-header');
var cols = '240px';
colHs.forEach(function () { cols += ' 220px'; });
colHs.forEach(function () { cols += ' 230px'; });
grid.style.gridTemplateColumns = cols;
}

function loadCollapsedLaneIds() {
var laneMap = {};
try {
var raw = window.localStorage.getItem(laneCollapseStorageKey);
if (!raw) return laneMap;
var arr = JSON.parse(raw);
if (!Array.isArray(arr)) return laneMap;
arr.forEach(function (laneId) {
laneMap[String(laneId)] = true;
});
} catch (e) {
console.warn('Failed to load lane collapse state', e);
}
return laneMap;
}

function saveCollapsedLaneIds() {
try {
window.localStorage.setItem(laneCollapseStorageKey, JSON.stringify(Object.keys(collapsedLaneIds)));
} catch (e) {
console.warn('Failed to save lane collapse state', e);
}
}

function setLaneCollapsed(laneId, isCollapsed) {
var laneKey = String(laneId);
var header = document.querySelector('.kanban-lane-header[data-lane-id="' + laneKey + '"]');
if (!header) return;

var laneCells = document.querySelectorAll('.kanban-cell[data-lane-id="' + laneKey + '"]');
header.classList.toggle('lane-collapsed', isCollapsed);
laneCells.forEach(function (cell) {
cell.classList.toggle('lane-collapsed', isCollapsed);
});

var toggleBtn = header.querySelector('.lane-toggle');
if (toggleBtn) {
toggleBtn.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true');
toggleBtn.title = isCollapsed ? 'Expand swim lane' : 'Collapse swim lane';
toggleBtn.setAttribute('aria-label', toggleBtn.title);
}

if (isCollapsed) {
collapsedLaneIds[laneKey] = true;
} else {
delete collapsedLaneIds[laneKey];
}
saveCollapsedLaneIds();
}

function toggleLaneCollapsed(laneId) {
var laneKey = String(laneId);
setLaneCollapsed(laneKey, !collapsedLaneIds[laneKey]);
}

function bindLaneHeaderToggle(headerEl) {
if (!headerEl) return;
var toggleBtn = headerEl.querySelector('.lane-toggle');
if (!toggleBtn) return;

if (!toggleBtn.dataset.boundToggle) {
toggleBtn.addEventListener('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
toggleLaneCollapsed(headerEl.dataset.laneId);
});
toggleBtn.dataset.boundToggle = '1';
}
}

function initLaneHeaderToggles() {
document.querySelectorAll('.kanban-lane-header').forEach(function (headerEl) {
bindLaneHeaderToggle(headerEl);
if (collapsedLaneIds[String(headerEl.dataset.laneId)]) {
setLaneCollapsed(headerEl.dataset.laneId, true);
}
});
}

function cardBodyHtml(card) {
var html = '<div class="card-headline">' +
'<span class="card-job-number">' + esc(card.job_number || '') + '</span>';

if (card.customer_name) {
html += '<span class="card-customer">' + esc(card.customer_name) + '</span>';
}

html += '</div>';

return html;
}

function buildCardSearchText(card) {
return [
card.job_number || '',
card.job_name || '',
card.customer_name || '',
card.notes || ''
].join(' ').toLowerCase();
}

function buildCardEl(card) {
var div = document.createElement('div');
div.className = 'kanban-card';
div.dataset.id = card.id;
div.dataset.columnId = card.column_id;
div.dataset.laneId = card.swim_lane_id;
div.innerHTML =
'<div class="card-job-number">' + esc(card.job_number || '') + '</div>' +
'<div class="card-job-name">' + esc(card.job_name || '') + '</div>';
div.dataset.searchText = buildCardSearchText(card);
div.innerHTML = cardBodyHtml(card);
div.addEventListener('click', function () {
window.KanbanModal.openEdit(
card.id,
card.column_id,
card.swim_lane_id,
card.job_number,
card.job_name
);
var c = KANBAN.cards.find(function (x) { return String(x.id) === String(div.dataset.id); });
if (!c) return;
window.KanbanModal.openEdit(c.id, c.column_id, c.swim_lane_id, c.job_number, c.job_name, c.customer_name, c.delivery_date, c.quantity, c.notes, c.full_note);
});
return div;
}
@@ -69,6 +170,26 @@
cell.appendChild(buildCardEl(card));
}
});
applyCardFilter();
}

function applyCardFilter() {
var activeQuery = searchState.query;
document.querySelectorAll('.kanban-card').forEach(function (el) {
var searchableText = (el.dataset.searchText || '').toLowerCase();
var isMatch = activeQuery === '' || searchableText.indexOf(activeQuery) > -1;
el.classList.toggle('kanban-card-hidden', !isMatch);
});
}

function initJobSearch() {
var searchInput = document.getElementById('job-search-input');
if (!searchInput) return;

searchInput.addEventListener('input', function () {
searchState.query = String(searchInput.value || '').toLowerCase().trim();
applyCardFilter();
});
}

function handleDragEnd(evt) {
@@ -206,23 +327,31 @@
if (cell) {
cell.appendChild(buildCardEl(card));
}
applyCardFilter();
},
onCardUpdated: function (id, jobNumber, jobName) {
onCardUpdated: function (id, data) {
var card = KANBAN.cards.find(function (c) { return String(c.id) === String(id); });
if (card) {
card.job_number = jobNumber;
card.job_name = jobName;
card.job_number = data.job_number || '';
card.job_name = data.job_name || '';
card.customer_name = data.customer_name || '';
card.delivery_date = data.delivery_date || null;
card.quantity = data.quantity || '';
card.notes = data.notes || '';
card.full_note = data.full_note !== undefined ? data.full_note : (card.full_note || '');
}
var el = document.querySelector('.kanban-card[data-id="' + id + '"]');
if (el) {
el.querySelector('.card-job-number').textContent = jobNumber;
el.querySelector('.card-job-name').textContent = jobName;
if (el && card) {
el.innerHTML = cardBodyHtml(card);
el.dataset.searchText = buildCardSearchText(card);
}
applyCardFilter();
},
onCardDeleted: function (id) {
KANBAN.cards = KANBAN.cards.filter(function (c) { return String(c.id) !== String(id); });
var el = document.querySelector('.kanban-card[data-id="' + id + '"]');
if (el) el.remove();
applyCardFilter();
},
addColumn: function (col) {
var grid = document.getElementById('kanban-grid');
@@ -263,8 +392,13 @@
var lh = document.createElement('div');
lh.className = 'kanban-lane-header';
lh.dataset.laneId = lane.id;
lh.innerHTML = '<span class="lane-label">' + esc(lane.name) + '</span>';
lh.innerHTML =
'<button type="button" class="lane-toggle" title="Collapse swim lane" aria-label="Collapse swim lane" aria-expanded="true">' +
'<i class="bi bi-chevron-down" aria-hidden="true"></i>' +
'</button>' +
'<span class="lane-label">' + esc(lane.name) + '</span>';
grid.appendChild(lh);
bindLaneHeaderToggle(lh);

colHeaders.forEach(function (ch) {
var cell = document.createElement('div');
@@ -275,12 +409,20 @@
createCellSortable(cell);
});

if (collapsedLaneIds[String(lane.id)]) {
setLaneCollapsed(lane.id, true);
}

applyGridTemplate();
},
removeLane: function (laneId) {
document.querySelector('.kanban-lane-header[data-lane-id="' + laneId + '"]').remove();
document.querySelectorAll('.kanban-cell[data-lane-id="' + laneId + '"]').forEach(function (el) { el.remove(); });
KANBAN.cards = KANBAN.cards.filter(function (c) { return String(c.swim_lane_id) !== String(laneId); });
if (collapsedLaneIds[String(laneId)]) {
delete collapsedLaneIds[String(laneId)];
saveCollapsedLaneIds();
}
},
renameColumn: function (colId, name) {
var hdr = document.querySelector('.kanban-col-header[data-col-id="' + colId + '"] .col-label');
@@ -295,4 +437,6 @@
applyGridTemplate();
renderCards();
initSortables();
initJobSearch();
initLaneHeaderToggles();
})();

+ 35
- 9
public/js/kanban-modal.js Visa fil

@@ -14,6 +14,12 @@
var btnSave = document.getElementById('btn-save-card');
var btnDelete = document.getElementById('btn-delete-card');

var custNameEl = document.getElementById('card-customer-name');
var delivDateEl = document.getElementById('card-delivery-date');
var qtyEl = document.getElementById('card-quantity');
var notesEl = document.getElementById('card-notes');
var fullNoteEl = document.getElementById('card-full-note');

var boardId = KANBAN.boardId;

/* ── Helpers ─────────────────────────────────────────────── */
@@ -44,6 +50,11 @@
laneIdEl.value = laneId || '';
jobNumEl.value = '';
jobNameEl.value = '';
custNameEl.value = '';
delivDateEl.value = '';
qtyEl.value = '';
notesEl.value = '';
fullNoteEl.value = '';
btnDelete.classList.add('d-none');
clearError();
bsModal.show();
@@ -51,13 +62,18 @@
}

/* ── Open for edit ───────────────────────────────────────── */
function openEdit(id, colId, laneId, jobNum, jobName) {
function openEdit(id, colId, laneId, jobNum, jobName, custName, delivDate, qty, notes, fullNote) {
titleEl.textContent = 'Edit Card';
cardIdEl.value = id;
colIdEl.value = colId;
laneIdEl.value = laneId;
jobNumEl.value = jobNum || '';
jobNameEl.value = jobName || '';
jobNumEl.value = jobNum || '';
jobNameEl.value = jobName || '';
custNameEl.value = custName || '';
delivDateEl.value = delivDate || '';
qtyEl.value = qty || '';
notesEl.value = notes || '';
fullNoteEl.value = fullNote || '';
btnDelete.classList.remove('d-none');
clearError();
bsModal.show();
@@ -72,6 +88,11 @@
var laneId = laneIdEl.value;
var jNum = jobNumEl.value.trim();
var jName = jobNameEl.value.trim();
var cust = custNameEl.value.trim();
var dDate = delivDateEl.value;
var qty = qtyEl.value.trim();
var notes = notesEl.value.trim();
var fullNote = fullNoteEl.value;

if (!jNum && !jName) {
showError('Enter at least a job number or job name.');
@@ -80,10 +101,10 @@

if (id) {
// Update existing
post('/cards/' + id, { job_number: jNum, job_name: jName }, function (res) {
post('/cards/' + id, { job_number: jNum, job_name: jName, customer_name: cust, delivery_date: dDate, quantity: qty, notes: notes, full_note: fullNote }, function (res) {
if (res.ok) {
bsModal.hide();
window.KanbanBoard.onCardUpdated(id, res.job_number, res.job_name);
window.KanbanBoard.onCardUpdated(id, res);
} else {
showError(res.error || 'Save failed.');
}
@@ -95,11 +116,16 @@
return;
}
post('/cards', {
board_id: boardId,
column_id: colId,
board_id: boardId,
column_id: colId,
swim_lane_id: laneId,
job_number: jNum,
job_name: jName
job_number: jNum,
job_name: jName,
customer_name: cust,
delivery_date: dDate,
quantity: qty,
notes: notes,
full_note: fullNote
}, function (res) {
if (res.ok) {
bsModal.hide();


Binär
scripts/importPrintStreamJobs.vbs Visa fil


+ 0
- 90
scripts/migrate_isbusiness_to_households.vbs Visa fil

@@ -1,90 +0,0 @@
' 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

Laddar…
Avbryt
Spara

Powered by TurnKey Linux.