feat: Add CutList.Web Blazor Server application

Add a new web-based frontend for cut list optimization using:
- Blazor Server with .NET 8
- Entity Framework Core with MSSQL LocalDB
- Full CRUD for Materials, Suppliers, Projects, and Cutting Tools
- Supplier stock length management for quick project setup
- Integration with CutList.Core for bin packing optimization
- Print-friendly HTML reports with efficiency statistics

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 21:56:21 -05:00
parent 6db8ab21f4
commit 9868df162d
43 changed files with 4452 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjswNiA1Mi4yNjg4TDI5MS4xMDUgODUuMjEzOEMyOTIuMTA0IDg2LjYwMDUgMjkxLjgwNyA4OC41NDA4IDI5MC40MjEgODkuNTM5N0wyNjguMTc2IDk5Ljk0MzlDMjY2Ljc5IDEwMC45NDMgMjY0Ljg0OSAxMDAuNjQ1IDI2My44NDkgOTkuMjU5NEwyMzkuNTcyIDY2LjMwNjVDMjM4LjU3MiA2NC45MiAyMzguODY5IDYyLjk3OTcgMjQwLjI1NiA2MS45ODA0TDI2MC44MDYgNTEuNjM5OUMyNjEuNjAyIDUxLjE4MzggMjYyLjU1IDUxLjAwMzYgMjYzLjUwNiA1MVoiIGZpbGw9IiNGRjgwODAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGQ9Ik0yNjMuMzQzIDY1LjM5OEMyNjQuMjY4IDY1LjM5OCAyNjUuMDA4IDY2LjEzOCAyNjUuMDA4IDY3LjA2M0MyNjUuMDA4IDY3Ljk4OCAyNjQuMjY4IDY4LjcyOCAyNjMuMzQzIDY4LjcyOEMyNjIuNDE4IDY4LjcyOCAyNjEuNjc4IDY3Ljk4OCAyNjEuNjc4IDY3LjA2M0MyNjEuNjc4IDY2LjEzOCAyNjIuNDE4IDY1LjM5OCAyNjMuMzQzIDY1LjM5OFoiIGZpbGw9IiNGRkZGRkYiLz48cGF0aCBkPSJNMjYzLjM0MyA3MS41OTEyQzI2My45NSA3MS41OTEyIDI2NC40NDQgNzIuMDg1NSAyNjQuNDQ0IDcyLjY5MjVMMjY0LjQ0NCA4NS4zMTg1QzI2NC40NDQgODUuOTI1NiAyNjMuOTUgODYuNDE5OCAyNjMuMzQzIDg2LjQxOTgiLz48L2c+PC9zdmc+) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
/* Custom styles for length input */
.length-input {
position: relative;
}
/* Table improvements */
.table th {
white-space: nowrap;
}
/* Card improvements */
.card-header {
background-color: #f8f9fa;
}
/* Form improvements */
.form-label {
font-weight: 500;
margin-bottom: 0.25rem;
}
.form-text {
font-size: 0.8rem;
}
/* Better mobile responsiveness for tables */
@media (max-width: 768px) {
.table-responsive-stack td,
.table-responsive-stack th {
display: block;
width: 100%;
text-align: left;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,200 @@
/* CutList Report Styles - Print Friendly */
.cut-list-report {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.report-header {
margin-bottom: 2rem;
border-bottom: 2px solid #333;
padding-bottom: 1rem;
}
.report-header h1 {
font-size: 2rem;
margin-bottom: 1rem;
color: #333;
}
.meta-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.meta-row {
font-size: 0.95rem;
}
.meta-row span:first-child {
font-weight: 600;
min-width: 120px;
display: inline-block;
color: #555;
}
.bin-section {
border: 1px solid #ccc;
border-radius: 4px;
margin: 1rem 0;
padding: 1rem;
page-break-inside: avoid;
break-inside: avoid;
}
.bin-section h2 {
font-size: 1.1rem;
margin: 0 0 0.75rem 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid #eee;
color: #333;
}
.cuts-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 0.75rem;
}
.cuts-table th,
.cuts-table td {
padding: 0.5rem;
text-align: left;
border-bottom: 1px solid #eee;
}
.cuts-table th {
background: #f5f5f5;
font-weight: 600;
font-size: 0.85rem;
text-transform: uppercase;
color: #666;
}
.cuts-table tbody tr:hover {
background-color: #f9f9f9;
}
.drop {
font-size: 0.9rem;
color: #666;
font-style: italic;
}
.summary {
background: #f0f0f0;
padding: 1.5rem;
margin-top: 2rem;
border-radius: 4px;
page-break-inside: avoid;
}
.summary h2 {
font-size: 1.2rem;
margin: 0 0 1rem 0;
color: #333;
}
.summary-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem 2rem;
}
.summary-row {
display: flex;
justify-content: space-between;
}
.summary-row span {
color: #555;
}
.summary-row strong {
color: #333;
}
.notes-section {
margin-top: 2rem;
padding: 1rem;
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
}
.notes-section h3 {
font-size: 1rem;
margin: 0 0 0.5rem 0;
color: #555;
}
.notes-section p {
margin: 0;
white-space: pre-wrap;
}
/* Print styles */
@media print {
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.sidebar,
.top-row,
.page > main > .top-row,
.btn,
button,
nav,
.navbar {
display: none !important;
}
.page {
display: block !important;
}
.page > main {
margin-left: 0 !important;
padding: 0 !important;
}
.content {
padding: 0 !important;
}
.cut-list-report {
max-width: 100%;
padding: 0;
margin: 0;
}
.bin-section {
break-inside: avoid;
page-break-inside: avoid;
}
.summary {
break-inside: avoid;
page-break-inside: avoid;
}
.alert {
display: none !important;
}
.card {
display: none !important;
}
h1:not(.report-header h1) {
display: none !important;
}
.text-muted:not(.cut-list-report .text-muted) {
display: none !important;
}
}