fix(web): make printed cut instructions legible
Build CutList image / build-and-push (push) Successful in 19s
Build CutList image / build-and-push (push) Successful in 19s
Keep part numbers and their dividers visible in monochrome print output, and include the selected cutting tool and kerf on the printed report.
This commit is contained in:
@@ -12,9 +12,9 @@ The solution contains four projects:
|
||||
|
||||
| Project | Framework | Purpose |
|
||||
|---------|-----------|---------|
|
||||
| **CutList** | .NET 8.0 Windows Forms | Original desktop UI (MVP pattern) |
|
||||
| **CutList.Core** | .NET 8.0 Class Library | Domain models and packing algorithms (platform-agnostic) |
|
||||
| **CutList.Web** | .NET 8.0 Blazor Server | Web-based UI + REST API, EF Core + SQL Server |
|
||||
| **CutList** | .NET 10.0 Windows Forms | Original desktop UI (MVP pattern) |
|
||||
| **CutList.Core** | .NET 10.0 Class Library | Domain models and packing algorithms (platform-agnostic) |
|
||||
| **CutList.Web** | .NET 10.0 Blazor Server | Web-based UI + REST API, EF Core + SQL Server |
|
||||
| **CutList.Mcp** | .NET 10.0 Console (stdio) | MCP server exposing CutList.Web's REST API as tools for Claude |
|
||||
|
||||
**Key Dependencies**: Math-Expression-Evaluator (input parsing), Newtonsoft.Json (serialization), Entity Framework Core (data access), Bootstrap 5 + Bootstrap Icons (UI), ModelContextProtocol SDK (CutList.Mcp)
|
||||
@@ -217,6 +217,7 @@ Abstract base with TPC (Table Per Concrete type) mapping — each shape gets its
|
||||
- **Job stock** — Jobs must have stock explicitly configured (catalog-sourced `StockItem` rows or custom-length rows); there is no fallback to auto-discovered inventory
|
||||
- **Optimization persistence** — Results saved as JSON in `Job.OptimizationResultJson`; DTO layer (`SavedOptimizationResult` etc.) handles serialization since Core types use encapsulated collections; results auto-cleared when parts, stock, or cutting tool change
|
||||
- **Job lock flow** — Optimize job -> review/print results -> Lock Job (manual action beside Print Report on the Results tab, available whether or not purchases are needed) -> job becomes read-only until Unlock
|
||||
- **Printed cut badges** — Print styles intentionally remove color fills; cut badges therefore force black text and a black part-number/length divider so both remain legible on paper. The print-only tool line shows the selected cut method and kerf immediately below the summary.
|
||||
- **Timestamps** — `CreatedAt` defaults to `GETUTCDATE()`; `UpdatedAt` set on modifications
|
||||
- **Collections** — Encapsulated in Core; use `AsReadOnly()`, access via `Add*` methods
|
||||
- **Priority system** — Lower priority bins used first in packing algorithm
|
||||
|
||||
@@ -1060,6 +1060,14 @@ else
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (job.CuttingTool != null)
|
||||
{
|
||||
<div class="print-cut-method">
|
||||
<strong>Cut Method:</strong> @job.CuttingTool.Name
|
||||
<span class="ms-2">Kerf: @FormatResultLength((double)job.CuttingTool.KerfInches)</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Material List: required stock lengths not covered by job stock -->
|
||||
<div class="card mb-4 print-material-list">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
|
||||
@@ -141,6 +141,11 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* The selected tool belongs on the paper cut report, not in the interactive results layout. */
|
||||
.print-cut-method {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Print styles - Compact layout to save paper */
|
||||
@media print {
|
||||
body {
|
||||
@@ -312,6 +317,14 @@
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.print-cut-method {
|
||||
display: block !important;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 9pt;
|
||||
margin: -0.25rem 0 0.5rem;
|
||||
padding: 0.3rem 0.75rem;
|
||||
}
|
||||
|
||||
/* Keep material list with cut lists to save paper */
|
||||
.print-material-list {
|
||||
break-inside: avoid;
|
||||
@@ -337,6 +350,15 @@
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
/* Badges lose their blue fills in print, so force their white screen text and divider to black. */
|
||||
.cut-part-badge {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.cut-part-badge-divider {
|
||||
border-left: 1px solid #000 !important;
|
||||
}
|
||||
|
||||
/* Cut list tables: hide screen header, show repeating print header in thead */
|
||||
.cutlist-material-screen-header {
|
||||
display: none !important;
|
||||
|
||||
@@ -33,6 +33,15 @@ class CutBadgeFormatTests(unittest.TestCase):
|
||||
self.assertIn('.cut-part-badge-length {', css)
|
||||
self.assertIn('background-color: #4c6680;', css)
|
||||
|
||||
def test_printed_cut_badges_keep_part_numbers_and_separators_legible(self) -> None:
|
||||
report_css = (REPO_ROOT / "CutList.Web/wwwroot/css/report.css").read_text()
|
||||
print_styles = report_css[report_css.index("@media print {"):]
|
||||
|
||||
self.assertIn(".cut-part-badge {", print_styles)
|
||||
self.assertIn("color: #000 !important;", print_styles)
|
||||
self.assertIn(".cut-part-badge-divider {", print_styles)
|
||||
self.assertIn("border-left: 1px solid #000 !important;", print_styles)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -45,6 +45,18 @@ class JobResultsActionsTests(unittest.TestCase):
|
||||
self.assertIn("background: transparent !important;", print_styles)
|
||||
self.assertIn("box-shadow: none !important;", print_styles)
|
||||
|
||||
def test_print_report_shows_the_selected_cutting_method_and_kerf(self) -> None:
|
||||
markup = JOB_EDITOR.read_text()
|
||||
results_start = markup.index("private RenderFragment RenderResultsTab()")
|
||||
summary_start = markup.index('<div class="row mb-4 print-summary">', results_start)
|
||||
material_list_start = markup.index('<!-- Material List:', summary_start)
|
||||
report_summary = markup[summary_start:material_list_start]
|
||||
|
||||
self.assertIn('class="print-cut-method"', report_summary)
|
||||
self.assertIn("<strong>Cut Method:</strong>", report_summary)
|
||||
self.assertIn("@job.CuttingTool.Name", report_summary)
|
||||
self.assertIn("Kerf:", report_summary)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user