Initial commit: Clean Mold Cost Calculator application

- Flask-based mold cost calculation application
- PostgreSQL database with SQLAlchemy ORM
- Docker/Podman containerization
- Comprehensive documentation in docs/
- Clean, organized codebase without obsolete files
- Production-ready deployment configuration
- Enhanced pytest configuration with coverage reporting
- Master/Development branch structure
This commit is contained in:
Gan, Jimmy
2025-06-24 02:30:09 +08:00
commit 6c0d5a4e63
108 changed files with 15911 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
<div class="list-group">
{% for quote in quote_history %}
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-primary me-2">#{{ quote.id }}</span>
{{ quote.created_at.strftime('%Y-%m-%d %H:%M') }}
</div>
<div class="fw-bold">¥{{ "{:,.0f}".format(quote.total_cost) }}</div>
</div>
{% else %}
<div class="list-group-item text-muted">
No calculations yet
</div>
{% endfor %}
</div>
+298
View File
@@ -0,0 +1,298 @@
{% macro render_quotation_details(quotation, show_actions=true) %}
<div class="quotation-details">
<div class="detail-card">
<h3>Basic Information</h3>
<div class="detail-grid">
<div class="detail-item">
<label>ID</label>
<span>{{ quotation.id }}</span>
</div>
<div class="detail-item">
<label>User</label>
<span>{{ quotation.user.username }}</span>
</div>
<div class="detail-item">
<label>Created</label>
<span>{{ quotation.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
</div>
<div class="detail-item">
<label>Status</label>
<span class="badge badge-success">Completed</span>
</div>
</div>
</div>
<div class="detail-card">
<h3>Project Details</h3>
<div class="detail-grid">
<div class="detail-item">
<label>Model Name</label>
<span>{{ quotation.model_name }}</span>
</div>
<div class="detail-item">
<label>Tooling ID</label>
<span>{{ quotation.tooling_id }}</span>
</div>
<div class="detail-item">
<label>Season</label>
<span>{{ quotation.season }}</span>
</div>
<div class="detail-item">
<label>Stage</label>
<span>{{ quotation.stage }}</span>
</div>
<div class="detail-item">
<label>Country</label>
<span>{{ quotation.country }}</span>
</div>
<div class="detail-item">
<label>Issue Date</label>
<span>{{ quotation.issue_date.strftime('%Y-%m-%d') }}</span>
</div>
</div>
</div>
<div class="detail-card">
<h3>Mold Specifications</h3>
<div class="detail-grid">
<div class="detail-item">
<label>Mold Main Type</label>
<span>{{ quotation.mold_main_type }}</span>
</div>
<div class="detail-item">
<label>Mold Sub Type</label>
<span>{{ quotation.mold_sub_type }}</span>
</div>
<div class="detail-item">
<label>High Sidewall Complexity</label>
<span>{{ 'Yes' if quotation.complexity_high_sidewall else 'No' }}</span>
</div>
<div class="detail-item">
<label>Number of Sliders</label>
<span>{{ quotation.sliders_count }}</span>
</div>
<div class="detail-item">
<label>Number of Cavities</label>
<span>{{ quotation.cavity_count }}</span>
</div>
<div class="detail-item">
<label>Digital Texture Type</label>
<span>{{ quotation.digital_texture_type }}</span>
</div>
</div>
</div>
<div class="detail-card">
<h3>Cost Breakdown</h3>
<div class="cost-breakdown">
<div class="cost-item">
<label>Net Base Price</label>
<span>${{ "%.2f"|format(quotation.net_base_price) }}</span>
</div>
<div class="cost-item">
<label>Tax Rate</label>
<span>{{ "%.1f"|format(quotation.tax_rate * 100) }}%</span>
</div>
<div class="cost-item total">
<label>Total Cost</label>
<span>${{ "%.2f"|format(quotation.total_cost) }}</span>
</div>
</div>
</div>
<div class="detail-card">
<h3>Factory Information</h3>
<div class="detail-grid">
<div class="detail-item">
<label>Mold Shop Name</label>
<span>{{ quotation.mold_shop_name }}</span>
</div>
<div class="detail-item">
<label>T1 Factory Name</label>
<span>{{ quotation.t1_factory_name or 'N/A' }}</span>
</div>
<div class="detail-item">
<label>T2 Component Factory</label>
<span>{{ quotation.t2_component_factory or 'N/A' }}</span>
</div>
</div>
</div>
{% if show_actions %}
<div class="detail-actions">
<a href="{{ url_for('admin.quotations') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Quotations
</a>
<button class="btn btn-primary" onclick="window.print()">
<i class="fas fa-print"></i> Print Quotation
</button>
</div>
{% endif %}
</div>
<style>
.quotation-details {
display: flex;
flex-direction: column;
gap: 30px;
}
.detail-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.detail-card h3 {
margin: 0 0 20px;
color: #2c3e50;
font-size: 1.2rem;
font-weight: 600;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.detail-item {
display: flex;
flex-direction: column;
gap: 5px;
}
.detail-item label {
color: #6c757d;
font-size: 0.9rem;
}
.detail-item span {
font-size: 1.1rem;
color: #2c3e50;
}
.cost-breakdown {
display: flex;
flex-direction: column;
gap: 15px;
}
.cost-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #dee2e6;
}
.cost-item:last-child {
border-bottom: none;
}
.cost-item label {
color: #6c757d;
font-size: 1rem;
}
.cost-item span {
font-size: 1.1rem;
color: #2c3e50;
font-weight: 500;
}
.cost-item.total {
margin-top: 10px;
padding-top: 20px;
border-top: 2px solid #dee2e6;
}
.cost-item.total label {
font-size: 1.2rem;
font-weight: 600;
color: #2c3e50;
}
.cost-item.total span {
font-size: 1.4rem;
font-weight: 700;
color: #28a745;
}
.detail-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 5px;
text-decoration: none;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.badge {
padding: 5px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.badge-success {
background-color: #28a745;
color: white;
}
@media (max-width: 768px) {
.detail-grid {
grid-template-columns: 1fr;
}
.detail-actions {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
@media print {
.detail-actions {
display: none;
}
.detail-card {
break-inside: avoid;
box-shadow: none;
border: 1px solid #dee2e6;
}
}
</style>
{% endmacro %}
+130
View File
@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Admin Panel{% endblock %}</title>
{% block styles %}
<!-- Bootstrap CSS -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-icons.css') }}">
<!-- Admin Styles -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
<!-- Custom Styles -->
<style>
:root {
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--light-color: #f8f9fa;
--dark-color: #212529;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f8f9fa;
}
.navbar {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.075);
transition: box-shadow 0.15s ease-in-out;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
}
.btn {
border-radius: 0.375rem;
}
.table {
border-radius: 0.375rem;
overflow: hidden;
}
.alert {
border: none;
border-radius: 0.375rem;
}
</style>
{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('admin.dashboard') }}">
<i class="bi bi-gear-fill me-2"></i>Admin Panel
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'admin.dashboard' %}active{% endif %}"
href="{{ url_for('admin.dashboard') }}">
<i class="bi bi-graph-up me-1"></i>Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'admin.users' %}active{% endif %}"
href="{{ url_for('admin.users') }}">
<i class="bi bi-people me-1"></i>Users
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'admin.quotations' %}active{% endif %}"
href="{{ url_for('admin.quotations') }}">
<i class="bi bi-file-earmark-text me-1"></i>Quotations
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'admin.cost_factors' %}active{% endif %}"
href="{{ url_for('admin.cost_factors') }}">
<i class="bi bi-calculator me-1"></i>Cost Factors
</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right me-1"></i>Logout
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid py-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
{% block scripts %}
<!-- Bootstrap Bundle with Popper -->
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<!-- jQuery -->
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
{% endblock %}
</body>
</html>
@@ -0,0 +1,198 @@
{% extends "admin/base.html" %}
{% block admin_title %}Cost Factor History{% endblock %}
{% block admin_header %}Change History: {{ factor.name }}{% endblock %}
{% block admin_actions %}
<a href="{{ url_for('admin.cost_factors') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Cost Factors
</a>
<a href="{{ url_for('admin.edit_cost_factor', factor_id=factor.id) }}" class="btn btn-primary">
<i class="fas fa-edit"></i> Edit Factor
</a>
{% endblock %}
{% block admin_content %}
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Factor Information</h5>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-4">Name</dt>
<dd class="col-sm-8">{{ factor.name }}</dd>
<dt class="col-sm-4">Category</dt>
<dd class="col-sm-8">
<span class="badge bg-primary">{{ factor.category }}</span>
</dd>
<dt class="col-sm-4">Current Value</dt>
<dd class="col-sm-8">
<span class="badge bg-success">{{ "%.4f"|format(factor.factor_value) }}</span>
</dd>
<dt class="col-sm-4">Status</dt>
<dd class="col-sm-8">
{% if factor.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-danger">Inactive</span>
{% endif %}
</dd>
<dt class="col-sm-4">Created</dt>
<dd class="col-sm-8">{{ factor.created_at.strftime('%Y-%m-%d %H:%M') if factor.created_at else 'Unknown' }}</dd>
<dt class="col-sm-4">Last Updated</dt>
<dd class="col-sm-8">{{ factor.updated_at.strftime('%Y-%m-%d %H:%M') if factor.updated_at else 'Never' }}</dd>
</dl>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Change History</h5>
</div>
<div class="card-body">
{% if history %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Date & Time</th>
<th>Changed By</th>
<th>Old Value</th>
<th>New Value</th>
<th>Change</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
{% for change in history %}
<tr>
<td>{{ change.changed_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
{% if change.user %}
<span class="badge bg-info">{{ change.user.username }}</span>
{% else %}
<span class="text-muted">Unknown</span>
{% endif %}
</td>
<td>
{% if change.old_value is not none %}
<span class="badge bg-secondary">{{ "%.4f"|format(change.old_value) }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
<td>
<span class="badge bg-success">{{ "%.4f"|format(change.new_value) }}</span>
</td>
<td>
{% if change.old_value is not none %}
{% set diff = change.new_value - change.old_value %}
{% if diff > 0 %}
<span class="badge bg-danger">+{{ "%.4f"|format(diff) }}</span>
{% elif diff < 0 %}
<span class="badge bg-warning">{{ "%.4f"|format(diff) }}</span>
{% else %}
<span class="badge bg-secondary">No change</span>
{% endif %}
{% else %}
<span class="badge bg-info">Initial</span>
{% endif %}
</td>
<td>
{% if change.change_reason %}
<small class="text-muted">{{ change.change_reason }}</small>
{% else %}
<span class="text-muted">No reason provided</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="fas fa-history fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No Changes Yet</h5>
<p class="text-muted">This factor hasn't been modified since it was created.</p>
</div>
{% endif %}
</div>
</div>
{% if history %}
<div class="card mt-3">
<div class="card-header">
<h5 class="mb-0">Summary</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<div class="text-center">
<h4 class="text-primary">{{ history|length }}</h4>
<small class="text-muted">Total Changes</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="text-success">{{ history|selectattr('change_reason')|list|length }}</h4>
<small class="text-muted">With Reasons</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="text-info">{{ history|selectattr('user')|list|length }}</h4>
<small class="text-muted">By Known Users</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="text-warning">{{ (history|last).changed_at.strftime('%Y-%m-%d') if history else 'N/A' }}</h4>
<small class="text-muted">Last Modified</small>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block styles %}
{{ super() }}
<style>
.badge {
font-size: 0.875em;
}
dl.row dt {
font-weight: 600;
color: #6c757d;
}
dl.row dd {
margin-bottom: 0.5rem;
}
.table th {
background-color: #f8f9fa;
border-top: none;
font-weight: 600;
}
.text-center h4 {
margin-bottom: 0.25rem;
}
</style>
{% endblock %}
+558
View File
@@ -0,0 +1,558 @@
{% extends "admin/base.html" %}
{% block title %}Cost Factors Management{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0">
<i class="bi bi-gear-fill me-2 text-primary"></i>Cost Factors Management
</h1>
<p class="text-muted mb-0">Configure calculation parameters and factors</p>
</div>
<div class="d-flex gap-2">
<button onclick="resetFactors()" class="btn btn-warning">
<i class="bi bi-arrow-clockwise me-1"></i>Reset to Defaults
</button>
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Back to Dashboard
</a>
</div>
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
<i class="bi bi-info-circle me-2"></i>{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Calculation Logic Diagram -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light border-0">
<h3 class="card-title mb-0">
<i class="bi bi-diagram-3 me-2 text-dark"></i>Calculation Logic
</h3>
</div>
<div class="card-body">
<div class="mermaid">
graph TD
A[Base Cost] --> B[Base Factors]
B --> C[Mold Factors]
C --> D[Complexity Factors]
D --> E[Texture Factors]
E --> F[Tax Factors]
F --> G[Final Cost]
B --> B1[Base Factor 1]
B --> B2[Base Factor 2]
C --> C1[Mold Factor 1]
C --> C2[Mold Factor 2]
D --> D1[Complexity Factor 1]
D --> D2[Complexity Factor 2]
E --> E1[Texture Factor 1]
E --> E2[Texture Factor 2]
F --> F1[Tax Rate]
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-2 col-md-4 col-sm-6 mb-3">
<div class="card statistics-card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="icon-wrapper bg-primary bg-opacity-10 mx-auto mb-3">
<i class="bi bi-calculator text-primary fs-1"></i>
</div>
<h4 class="mb-1">{{ base_factors|length if base_factors else 0 }}</h4>
<p class="text-muted mb-0 small">Base Factors</p>
</div>
</div>
</div>
<div class="col-xl-2 col-md-4 col-sm-6 mb-3">
<div class="card statistics-card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="icon-wrapper bg-success bg-opacity-10 mx-auto mb-3">
<i class="bi bi-building text-success fs-1"></i>
</div>
<h4 class="mb-1">{{ mold_factors|length if mold_factors else 0 }}</h4>
<p class="text-muted mb-0 small">Mold Factors</p>
</div>
</div>
</div>
<div class="col-xl-2 col-md-4 col-sm-6 mb-3">
<div class="card statistics-card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="icon-wrapper bg-warning bg-opacity-10 mx-auto mb-3">
<i class="bi bi-puzzle text-warning fs-1"></i>
</div>
<h4 class="mb-1">{{ complexity_factors|length if complexity_factors else 0 }}</h4>
<p class="text-muted mb-0 small">Complexity</p>
</div>
</div>
</div>
<div class="col-xl-2 col-md-4 col-sm-6 mb-3">
<div class="card statistics-card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="icon-wrapper bg-info bg-opacity-10 mx-auto mb-3">
<i class="bi bi-palette text-info fs-1"></i>
</div>
<h4 class="mb-1">{{ texture_factors|length if texture_factors else 0 }}</h4>
<p class="text-muted mb-0 small">Texture</p>
</div>
</div>
</div>
<div class="col-xl-2 col-md-4 col-sm-6 mb-3">
<div class="card statistics-card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="icon-wrapper bg-danger bg-opacity-10 mx-auto mb-3">
<i class="bi bi-percent text-danger fs-1"></i>
</div>
<h4 class="mb-1">{{ tax_factors|length if tax_factors else 0 }}</h4>
<p class="text-muted mb-0 small">Tax Factors</p>
</div>
</div>
</div>
</div>
<!-- Cost Factors Sections -->
<div class="row">
<div class="col-12">
<!-- Base Factors -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary bg-opacity-10 border-0">
<h3 class="card-title mb-0">
<i class="bi bi-layers me-2 text-primary"></i>Base Factors
<span class="badge bg-primary ms-2">{{ base_factors|length if base_factors else 0 }}</span>
</h3>
</div>
<div class="card-body">
{% if base_factors %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="border-0">Factor Name</th>
<th class="border-0">Value</th>
<th class="border-0">Description</th>
<th class="border-0 text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for factor in base_factors %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
<i class="bi bi-calculator text-primary"></i>
</div>
<div>
<div class="fw-medium">{{ factor.name }}</div>
<small class="text-muted">{{ factor.category }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-primary fs-6">{{ "%.2f"|format(factor.factor_value) }}</span>
</td>
<td>{{ factor.description or 'Base calculation factor' }}</td>
<td class="text-end">
<a href="{{ url_for('admin.edit_cost_factor', factor_id=factor.id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil me-1"></i>Edit
</a>
<a href="{{ url_for('admin.cost_factor_history', factor_id=factor.id) }}" class="btn btn-sm btn-outline-info">
<i class="bi bi-clock-history me-1"></i>History
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
<p class="text-muted mb-0">No base factors found.</p>
</div>
{% endif %}
</div>
</div>
<!-- Mold Factors -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success bg-opacity-10 border-0">
<h3 class="card-title mb-0">
<i class="bi bi-building me-2 text-success"></i>Mold Factors
<span class="badge bg-success ms-2">{{ mold_factors|length if mold_factors else 0 }}</span>
</h3>
</div>
<div class="card-body">
{% if mold_factors %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="border-0">Factor Name</th>
<th class="border-0">Value</th>
<th class="border-0">Description</th>
<th class="border-0 text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for factor in mold_factors %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
<i class="bi bi-box text-success"></i>
</div>
<div>
<div class="fw-medium">{{ factor.name }}</div>
<small class="text-muted">{{ factor.category }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-success fs-6">{{ "%.2f"|format(factor.factor_value) }}</span>
</td>
<td>{{ factor.description or 'Mold-related factor' }}</td>
<td class="text-end">
<a href="{{ url_for('admin.edit_cost_factor', factor_id=factor.id) }}" class="btn btn-sm btn-outline-success">
<i class="bi bi-pencil me-1"></i>Edit
</a>
<a href="{{ url_for('admin.cost_factor_history', factor_id=factor.id) }}" class="btn btn-sm btn-outline-info">
<i class="bi bi-clock-history me-1"></i>History
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
<p class="text-muted mb-0">No mold factors found.</p>
</div>
{% endif %}
</div>
</div>
<!-- Complexity Factors -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-warning bg-opacity-10 border-0">
<h3 class="card-title mb-0">
<i class="bi bi-puzzle me-2 text-warning"></i>Complexity Factors
<span class="badge bg-warning ms-2">{{ complexity_factors|length if complexity_factors else 0 }}</span>
</h3>
</div>
<div class="card-body">
{% if complexity_factors %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="border-0">Factor Name</th>
<th class="border-0">Value</th>
<th class="border-0">Description</th>
<th class="border-0 text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for factor in complexity_factors %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
<i class="bi bi-gear text-warning"></i>
</div>
<div>
<div class="fw-medium">{{ factor.name }}</div>
<small class="text-muted">{{ factor.category }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-warning fs-6">{{ "%.2f"|format(factor.factor_value) }}</span>
</td>
<td>{{ factor.description or 'Complexity-related factor' }}</td>
<td class="text-end">
<a href="{{ url_for('admin.edit_cost_factor', factor_id=factor.id) }}" class="btn btn-sm btn-outline-warning">
<i class="bi bi-pencil me-1"></i>Edit
</a>
<a href="{{ url_for('admin.cost_factor_history', factor_id=factor.id) }}" class="btn btn-sm btn-outline-info">
<i class="bi bi-clock-history me-1"></i>History
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
<p class="text-muted mb-0">No complexity factors found.</p>
</div>
{% endif %}
</div>
</div>
<!-- Texture Factors -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-info bg-opacity-10 border-0">
<h3 class="card-title mb-0">
<i class="bi bi-brush me-2 text-info"></i>Texture Factors
<span class="badge bg-info ms-2">{{ texture_factors|length if texture_factors else 0 }}</span>
</h3>
</div>
<div class="card-body">
{% if texture_factors %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="border-0">Factor Name</th>
<th class="border-0">Value</th>
<th class="border-0">Description</th>
<th class="border-0 text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for factor in texture_factors %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
<i class="bi bi-palette text-info"></i>
</div>
<div>
<div class="fw-medium">{{ factor.name }}</div>
<small class="text-muted">{{ factor.category }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-info fs-6">{{ "%.2f"|format(factor.factor_value) }}</span>
</td>
<td>{{ factor.description or 'Texture-related factor' }}</td>
<td class="text-end">
<a href="{{ url_for('admin.edit_cost_factor', factor_id=factor.id) }}" class="btn btn-sm btn-outline-info">
<i class="bi bi-pencil me-1"></i>Edit
</a>
<a href="{{ url_for('admin.cost_factor_history', factor_id=factor.id) }}" class="btn btn-sm btn-outline-info">
<i class="bi bi-clock-history me-1"></i>History
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
<p class="text-muted mb-0">No texture factors found.</p>
</div>
{% endif %}
</div>
</div>
<!-- Tax Factors -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-danger bg-opacity-10 border-0">
<h3 class="card-title mb-0">
<i class="bi bi-percent me-2 text-danger"></i>Tax Factors
<span class="badge bg-danger ms-2">{{ tax_factors|length if tax_factors else 0 }}</span>
</h3>
</div>
<div class="card-body">
{% if tax_factors %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="border-0">Country/Region</th>
<th class="border-0">Tax Rate</th>
<th class="border-0 text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for factor in tax_factors %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-danger bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
<i class="bi bi-flag text-danger"></i>
</div>
<div>
<div class="fw-medium">{{ factor.name }}</div>
<small class="text-muted">{{ factor.description or 'Tax rate' }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-danger fs-6">{{ "%.1f"|format(factor.factor_value * 100) }}%</span>
</td>
<td class="text-end">
<a href="{{ url_for('admin.edit_cost_factor', factor_id=factor.id) }}" class="btn btn-sm btn-outline-danger">
<i class="bi bi-pencil me-1"></i>Edit
</a>
<a href="{{ url_for('admin.cost_factor_history', factor_id=factor.id) }}" class="btn btn-sm btn-outline-info">
<i class="bi bi-clock-history me-1"></i>History
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
<p class="text-muted mb-0">No tax factors found.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Add Mermaid.js for diagram rendering -->
<script src="{{ url_for('static', filename='js/mermaid.min.js') }}"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose',
fontFamily: '"Helvetica Neue", Arial, sans-serif'
});
document.addEventListener('DOMContentLoaded', function() {
console.log('Cost Factors Template Loaded');
try {
const debugInfo = {
baseFactors: {{ base_factors|length if base_factors else 0 }},
moldFactors: {{ mold_factors|length if mold_factors else 0 }},
complexityFactors: {{ complexity_factors|length if complexity_factors else 0 }},
textureFactors: {{ texture_factors|length if texture_factors else 0 }},
taxFactors: {{ tax_factors|length if tax_factors else 0 }}
};
console.log('Debug Info:', debugInfo);
} catch (error) {
console.error('Error in debug logging:', error);
}
});
function resetFactors() {
if (confirm('Are you sure you want to reset all cost factors to their default values? This action cannot be undone.')) {
fetch('{{ url_for("admin.reset_cost_factors") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
if (response.ok) {
window.location.reload();
} else {
alert('Error resetting factors. Please try again.');
}
}).catch(error => {
console.error('Error:', error);
alert('Error resetting factors. Please try again.');
});
}
}
</script>
{% block styles %}
{{ super() }}
<style>
/* Card Styles */
.card {
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
/* Table Styles */
.table th {
font-weight: 600;
color: #495057;
}
.table td {
vertical-align: middle;
}
/* Badge Styles */
.badge {
padding: 0.5em 0.8em;
}
.badge.fs-6 {
font-size: 0.875rem !important;
}
/* Icon Styles */
.avatar-sm i {
font-size: 1.25rem;
}
/* Animation */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fadeIn 0.3s ease-out;
}
/* Statistics Cards */
.statistics-card {
border-radius: 1rem;
overflow: hidden;
}
.statistics-card .icon-wrapper {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
/* Mermaid Diagram */
.mermaid {
background: white;
padding: 1rem;
border-radius: 0.5rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.statistics-card {
margin-bottom: 1rem;
}
.table-responsive {
margin: 0 -1rem;
}
}
</style>
{% endblock %}
{% endblock %}
+245
View File
@@ -0,0 +1,245 @@
{% extends "admin/base.html" %}
{% block title %}Admin Dashboard{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0">
<i class="bi bi-speedometer2 me-2 text-primary"></i>Admin Dashboard
</h1>
<p class="text-muted mb-0">Overview of system statistics and recent activity</p>
</div>
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
<i class="bi bi-info-circle me-2"></i>{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Total Users</h6>
<h3 class="mb-0">{{ total_users }}</h3>
</div>
<div class="bg-primary bg-opacity-10 rounded-circle p-3">
<i class="bi bi-people text-primary fs-1"></i>
</div>
</div>
<div class="mt-3">
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i>{{ active_users }} active
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Total Quotations</h6>
<h3 class="mb-0">{{ total_quotations }}</h3>
</div>
<div class="bg-success bg-opacity-10 rounded-circle p-3">
<i class="bi bi-file-earmark-text text-success fs-1"></i>
</div>
</div>
<div class="mt-3">
<span class="badge bg-info">
<i class="bi bi-graph-up me-1"></i>All time
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Active Regions</h6>
<h3 class="mb-0">{{ active_regions }}</h3>
</div>
<div class="bg-warning bg-opacity-10 rounded-circle p-3">
<i class="bi bi-globe text-warning fs-1"></i>
</div>
</div>
<div class="mt-3">
<span class="badge bg-warning">
<i class="bi bi-flag me-1"></i>Active regions
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Recent Quotes</h6>
<h3 class="mb-0">{{ recent_quotations|length }}</h3>
</div>
<div class="bg-info bg-opacity-10 rounded-circle p-3">
<i class="bi bi-clock text-info fs-1"></i>
</div>
</div>
<div class="mt-3">
<span class="badge bg-info">
<i class="bi bi-calendar me-1"></i>Last 5 quotes
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Recent Activity -->
<div class="col-lg-8 mb-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light border-0">
<h5 class="card-title mb-0">
<i class="bi bi-graph-up me-2 text-primary"></i>Recent Activity
</h5>
</div>
<div class="card-body">
{% if recent_quotations %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>User</th>
<th>Project</th>
<th>Amount</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for quotation in recent_quotations %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 32px; height: 32px;">
<i class="bi bi-person text-primary"></i>
</div>
<div>
<div class="fw-medium">{{ quotation.user.username }}</div>
<small class="text-muted">{{ quotation.user.email }}</small>
</div>
</div>
</td>
<td>{{ quotation.project_name }}</td>
<td>
<span class="badge bg-success">${{ "%.2f"|format(quotation.total_cost) }}</span>
</td>
<td>{{ quotation.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<span class="badge bg-primary">Completed</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="text-center mt-3">
<a href="{{ url_for('admin.quotations') }}" class="btn btn-outline-primary">
<i class="bi bi-eye me-1"></i>View All Quotations
</a>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-file-earmark-text fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">No recent quotations found.</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="col-lg-4 mb-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light border-0">
<h5 class="card-title mb-0">
<i class="bi bi-lightning me-2 text-warning"></i>Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-primary">
<i class="bi bi-people me-2"></i>Manage Users
</a>
<a href="{{ url_for('admin.quotations') }}" class="btn btn-outline-success">
<i class="bi bi-file-earmark-text me-2"></i>View Quotations
</a>
<a href="{{ url_for('admin.cost_factors') }}" class="btn btn-outline-warning">
<i class="bi bi-gear me-2"></i>Cost Factors
</a>
<a href="{{ url_for('admin.cost_factors') }}" class="btn btn-outline-info">
<i class="bi bi-calculator me-2"></i>Calculator Settings
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.card {
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.avatar-sm {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.table th {
font-weight: 600;
color: #495057;
}
.table td {
vertical-align: middle;
}
.badge {
font-size: 0.875em;
}
.btn {
border-radius: 0.375rem;
}
</style>
{% endblock %}
+238
View File
@@ -0,0 +1,238 @@
{% extends "admin/base.html" %}
{% block title %}Edit Cost Factor: {{ factor.name }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0">
<i class="bi bi-pencil-square me-2 text-primary"></i>Edit Cost Factor
</h1>
<p class="text-muted mb-0">{{ factor.name }} ({{ factor.category }})</p>
</div>
<div class="d-flex gap-2">
<a href="{{ url_for('admin.cost_factors') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i> Back to Cost Factors
</a>
</div>
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
<i class="bi bi-info-circle me-2"></i>{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row">
<div class="col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light border-0">
<h5 class="card-title mb-0">
<i class="bi bi-gear me-2"></i>Edit Factor Details
</h5>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="category" class="form-label fw-semibold">Category</label>
<input type="text" class="form-control bg-light" id="category" value="{{ factor.category }}" readonly>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="name" class="form-label fw-semibold">Name</label>
<input type="text" class="form-control bg-light" id="name" value="{{ factor.name }}" readonly>
</div>
</div>
</div>
<div class="mb-3">
<label for="factor_value" class="form-label fw-semibold">
Factor Value <span class="text-danger">*</span>
</label>
<input type="number" class="form-control form-control-lg" id="factor_value" name="factor_value"
value="{{ "%.4f"|format(factor.factor_value) }}" step="0.0001" min="0" required>
<div class="form-text">
{% if factor.category == 'tax' %}
<i class="bi bi-info-circle me-1"></i>Enter as decimal (e.g., 0.06 for 6%)
{% elif factor.category == 'base' %}
<i class="bi bi-info-circle me-1"></i>Enter the base price value
{% else %}
<i class="bi bi-info-circle me-1"></i>Enter the multiplier factor
{% endif %}
</div>
</div>
{% if factor.calculation_formula %}
<div class="mb-3">
<label for="calculation_formula" class="form-label fw-semibold">Calculation Formula</label>
<input type="text" class="form-control bg-light" id="calculation_formula"
value="{{ factor.calculation_formula }}" readonly>
</div>
{% endif %}
{% if factor.percentage %}
<div class="mb-3">
<label for="percentage" class="form-label fw-semibold">Percentage</label>
<input type="text" class="form-control bg-light" id="percentage"
value="{{ factor.percentage }}" readonly>
</div>
{% endif %}
<div class="mb-3">
<label for="description" class="form-label fw-semibold">Description</label>
<textarea class="form-control bg-light" id="description" rows="3" readonly>{{ factor.description }}</textarea>
</div>
<div class="mb-4">
<label for="change_reason" class="form-label fw-semibold">
<i class="bi bi-chat-text me-1"></i>Reason for Change
</label>
<textarea class="form-control" id="change_reason" name="change_reason" rows="3"
placeholder="Please provide a reason for this change (optional but recommended)"></textarea>
<div class="form-text">
<i class="bi bi-clock-history me-1"></i>This will be recorded in the change history for audit purposes.
</div>
</div>
<div class="d-flex justify-content-between">
<a href="{{ url_for('admin.cost_factors') }}" class="btn btn-outline-secondary">
<i class="bi bi-x-circle me-1"></i> Cancel
</a>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-1"></i> Save Changes
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-light border-0">
<h5 class="card-title mb-0">
<i class="bi bi-info-circle me-2"></i>Current Information
</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4 fw-semibold">Category</dt>
<dd class="col-sm-8">
<span class="badge bg-primary">{{ factor.category }}</span>
</dd>
<dt class="col-sm-4 fw-semibold">Name</dt>
<dd class="col-sm-8">{{ factor.name }}</dd>
<dt class="col-sm-4 fw-semibold">Current Value</dt>
<dd class="col-sm-8">
<span class="badge bg-success fs-6">{{ "%.4f"|format(factor.factor_value) }}</span>
</dd>
<dt class="col-sm-4 fw-semibold">Status</dt>
<dd class="col-sm-8">
{% if factor.is_active %}
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i>Active
</span>
{% else %}
<span class="badge bg-danger">
<i class="bi bi-x-circle me-1"></i>Inactive
</span>
{% endif %}
</dd>
<dt class="col-sm-4 fw-semibold">Created</dt>
<dd class="col-sm-8">{{ factor.created_at.strftime('%Y-%m-%d %H:%M') if factor.created_at else 'Unknown' }}</dd>
<dt class="col-sm-4 fw-semibold">Last Updated</dt>
<dd class="col-sm-8">{{ factor.updated_at.strftime('%Y-%m-%d %H:%M') if factor.updated_at else 'Never' }}</dd>
{% if factor.updater %}
<dt class="col-sm-4 fw-semibold">Updated By</dt>
<dd class="col-sm-8">{{ factor.updater.username }}</dd>
{% endif %}
</dl>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-light border-0">
<h5 class="card-title mb-0">
<i class="bi bi-lightning me-2"></i>Quick Actions
</h5>
</div>
<div class="card-body">
<a href="{{ url_for('admin.cost_factor_history', factor_id=factor.id) }}"
class="btn btn-outline-info btn-sm w-100 mb-2">
<i class="bi bi-clock-history me-1"></i> View Change History
</a>
<a href="{{ url_for('admin.cost_factors') }}"
class="btn btn-outline-secondary btn-sm w-100">
<i class="bi bi-list me-1"></i> Back to All Factors
</a>
</div>
</div>
</div>
</div>
</div>
<style>
.form-control[readonly] {
background-color: #f8f9fa !important;
border-color: #dee2e6;
color: #6c757d;
}
.form-control:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.badge {
font-size: 0.875em;
}
dl.row dt {
color: #6c757d;
font-size: 0.9rem;
}
dl.row dd {
margin-bottom: 0.75rem;
font-size: 0.9rem;
}
.card {
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-1px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.btn {
border-radius: 0.375rem;
}
.form-control-lg {
font-size: 1.1rem;
padding: 0.75rem 1rem;
}
</style>
{% endblock %}
+146
View File
@@ -0,0 +1,146 @@
{% extends "admin/base.html" %}
{% block admin_title %}Quotation Details{% endblock %}
{% block admin_header %}Quotation Details{% endblock %}
{% block admin_actions %}
<a href="{{ url_for('admin.quotations') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Quotations
</a>
{% endblock %}
{% block admin_content %}
{% from "_quotation_details.html" import render_quotation_details %}
{{ render_quotation_details(quotation) }}
{% endblock %}
{% block styles %}
{{ super() }}
<style>
.quotation-details {
display: flex;
flex-direction: column;
gap: 30px;
}
.detail-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.detail-card h3 {
margin: 0 0 20px;
color: #2c3e50;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.detail-item {
display: flex;
flex-direction: column;
gap: 5px;
}
.detail-item label {
color: #6c757d;
font-size: 0.9rem;
}
.detail-item span {
font-size: 1.1rem;
color: #2c3e50;
}
.cost-breakdown {
display: flex;
flex-direction: column;
gap: 15px;
}
.cost-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #dee2e6;
}
.cost-item:last-child {
border-bottom: none;
}
.cost-item label {
color: #6c757d;
font-size: 1rem;
}
.cost-item span {
font-size: 1.1rem;
color: #2c3e50;
font-weight: 500;
}
.cost-item.total {
margin-top: 10px;
padding-top: 20px;
border-top: 2px solid #dee2e6;
}
.cost-item.total label {
font-size: 1.2rem;
font-weight: 600;
color: #2c3e50;
}
.cost-item.total span {
font-size: 1.4rem;
font-weight: 700;
color: #28a745;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.badge {
padding: 5px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.badge-success {
background-color: #28a745;
color: white;
}
@media (max-width: 768px) {
.detail-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
+174
View File
@@ -0,0 +1,174 @@
{% extends "admin/base.html" %}
{% block title %}Quotations - Admin{% endblock %}
{% block content %}
<div class="admin-container">
<div class="admin-card">
<div class="admin-card-header">
<h2 class="mb-0">Quotations Management</h2>
</div>
<div class="admin-card-body">
<div class="admin-filters">
<form method="get" class="admin-filter-form" id="quotationFilterForm">
<div class="admin-form-group">
<select name="country" class="admin-form-control">
<option value="">All Countries</option>
{% for country in countries %}
<option value="{{ country }}" {% if filters.country == country %}selected{% endif %}>
{{ country }}
</option>
{% endfor %}
</select>
</div>
<div class="admin-form-group">
<select name="mold_type" class="admin-form-control">
<option value="">All Mold Types</option>
{% for type in mold_types %}
<option value="{{ type }}" {% if filters.mold_type == type %}selected{% endif %}>
{{ type }}
</option>
{% endfor %}
</select>
</div>
<div class="admin-form-group">
<input type="date" name="date_from" id="date_from" class="admin-form-control"
value="{{ filters.date_from.strftime('%Y-%m-%d') if filters.date_from else '' }}"
placeholder="From Date">
</div>
<div class="admin-form-group">
<input type="date" name="date_to" id="date_to" class="admin-form-control"
value="{{ filters.date_to.strftime('%Y-%m-%d') if filters.date_to else '' }}"
placeholder="To Date">
</div>
<div class="admin-form-group">
<button type="submit" class="admin-btn admin-btn-primary">Filter</button>
<a href="{{ url_for('admin.quotations') }}" class="admin-btn admin-btn-secondary">Clear</a>
</div>
</form>
</div>
{% if quotations.items %}
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>User</th>
<th>Model</th>
<th>Country</th>
<th>Mold Type</th>
<th>Base Price</th>
<th>Total Cost</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for quotation in quotations.items %}
<tr>
<td>{{ quotation.id }}</td>
<td>{{ quotation.user.username }}</td>
<td>{{ quotation.model_name }}</td>
<td>{{ quotation.mold_shop_country }}</td>
<td>{{ quotation.mold_main_type }}</td>
<td>${{ "%.2f"|format(quotation.base_price) }}</td>
<td>${{ "%.2f"|format(quotation.total_cost) }}</td>
<td>{{ quotation.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<a href="{{ url_for('admin.quotation_detail', id=quotation.id) }}"
class="admin-btn admin-btn-info admin-btn-sm">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if quotations.pages > 1 %}
<div class="admin-pagination">
{% if quotations.has_prev %}
<a href="{{ url_for('admin.quotations', page=quotations.prev_num, **filters) }}"
class="admin-pagination-link">Previous</a>
{% endif %}
<span class="admin-pagination-info">
Page {{ quotations.page }} of {{ quotations.pages }}
</span>
{% if quotations.has_next %}
<a href="{{ url_for('admin.quotations', page=quotations.next_num, **filters) }}"
class="admin-pagination-link">Next</a>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="text-center py-4">
<p class="text-muted mb-0">No quotations found matching your criteria.</p>
{% if filters.country or filters.mold_type or filters.date_from or filters.date_to %}
<a href="{{ url_for('admin.quotations') }}" class="admin-btn admin-btn-primary mt-3">Clear Filters</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to get today's date in YYYY-MM-DD format
function getTodayDate() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Get date input elements
const dateFromInput = document.getElementById('date_from');
const dateToInput = document.getElementById('date_to');
const form = document.getElementById('quotationFilterForm');
// Set default values if empty
if (!dateFromInput.value) {
dateFromInput.value = getTodayDate();
}
if (!dateToInput.value) {
dateToInput.value = getTodayDate();
}
// Prevent form submission if dates are invalid
form.addEventListener('submit', function(event) {
if (!dateFromInput.value || !dateToInput.value) {
event.preventDefault();
if (!dateFromInput.value) dateFromInput.value = getTodayDate();
if (!dateToInput.value) dateToInput.value = getTodayDate();
}
});
// Add event listeners to handle invalid dates
dateFromInput.addEventListener('change', function() {
if (!this.value) this.value = getTodayDate();
});
dateToInput.addEventListener('change', function() {
if (!this.value) this.value = getTodayDate();
});
});
</script>
{% endblock %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
{% endblock %}
+1
View File
@@ -0,0 +1 @@
+253
View File
@@ -0,0 +1,253 @@
{% extends "admin/base.html" %}
{% block title %}Users - Admin Panel{% endblock %}
{% block content %}
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<form method="get" class="row g-3 align-items-end">
<div class="col-md-8">
<label for="search" class="form-label">Search Users</label>
<input type="text" id="search" name="search" placeholder="Search by username or email..."
value="{{ request.args.get('search', '') }}" class="form-control">
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-search me-2"></i>Search
</button>
</div>
</form>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-transparent border-0">
<h5 class="card-title mb-0">
<i class="fas fa-users me-2 text-primary"></i>
User Management
</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="border-0">ID</th>
<th class="border-0">Username</th>
<th class="border-0">Email</th>
<th class="border-0">Status</th>
<th class="border-0">Role</th>
<th class="border-0">Last Login</th>
<th class="border-0">Created</th>
<th class="border-0 text-center">Actions</th>
</tr>
</thead>
<tbody>
{% for user in users.items %}
<tr>
<td>
<span class="badge bg-secondary">#{{ user.id }}</span>
</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 32px; height: 32px;">
<i class="fas fa-user text-primary"></i>
</div>
<span class="fw-medium">{{ user.username }}</span>
</div>
</td>
<td>
<span class="text-muted">{{ user.email }}</span>
</td>
<td>
<span class="badge bg-{{ 'success' if user.is_active else 'danger' }}">
<i class="fas fa-{{ 'check-circle' if user.is_active else 'times-circle' }} me-1"></i>
{{ 'Active' if user.is_active else 'Inactive' }}
</span>
</td>
<td>
<span class="badge bg-{{ 'primary' if user.is_admin else 'secondary' }}">
<i class="fas fa-{{ 'crown' if user.is_admin else 'user' }} me-1"></i>
{{ 'Admin' if user.is_admin else 'User' }}
</span>
</td>
<td>
<small class="text-muted">
{{ user.last_login.strftime('%m/%d/%Y %H:%M') if user.last_login else 'Never' }}
</small>
</td>
<td>
<small class="text-muted">
{{ user.created_at.strftime('%m/%d/%Y') }}
</small>
</td>
<td class="text-center">
<div class="btn-group" role="group">
<a href="{{ url_for('admin.user_detail', user_id=user.id) }}"
class="btn btn-outline-info btn-sm"
title="View Details">
<i class="fas fa-eye"></i>
</a>
<button type="button"
class="btn btn-{{ 'warning' if user.is_active else 'success' }} btn-sm"
onclick="toggleUserStatus({{ user.id }}, '{{ 'warning' if user.is_active else 'success' }}')"
title="{{ 'Deactivate' if user.is_active else 'Activate' }} User">
<i class="fas fa-{{ 'ban' if user.is_active else 'check' }}"></i>
</button>
{% if user.id != current_user.id %}
<button type="button"
class="btn btn-outline-danger btn-sm"
onclick="deleteUser({{ user.id }})"
title="Delete User">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Pagination -->
{% if users.pages > 1 %}
<div class="d-flex justify-content-between align-items-center mt-4">
<div class="text-muted">
Showing {{ users.items|length }} of {{ users.total }} users
</div>
<nav aria-label="User pagination">
<ul class="pagination pagination-sm mb-0">
{% if users.has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('admin.users', page=users.prev_num, search=request.args.get('search', '')) }}">
<i class="fas fa-chevron-left"></i>
</a>
</li>
{% endif %}
{% for page_num in users.iter_pages() %}
{% if page_num %}
{% if page_num != users.page %}
<li class="page-item">
<a class="page-link" href="{{ url_for('admin.users', page=page_num, search=request.args.get('search', '')) }}">
{{ page_num }}
</a>
</li>
{% else %}
<li class="page-item active">
<span class="page-link">{{ page_num }}</span>
</li>
{% endif %}
{% else %}
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% endfor %}
{% if users.has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('admin.users', page=users.next_num, search=request.args.get('search', '')) }}">
<i class="fas fa-chevron-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
function toggleUserStatus(userId, status) {
const button = event.target.closest('button');
const isActive = status === 'warning';
const action = isActive ? 'deactivate' : 'activate';
if (!confirm(`Are you sure you want to ${action} this user?`)) {
return;
}
fetch(`/admin/user/${userId}/toggle`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
location.reload();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while updating user status');
});
}
function deleteUser(userId) {
if (!confirm('Are you sure you want to delete this user? This action cannot be undone.')) {
return;
}
fetch('/admin/delete_user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ user_id: userId })
})
.then(response => response.json())
.then(data => {
if (data.message) {
location.reload();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the user');
});
}
</script>
{% endblock %}
{% block styles %}
{{ super() }}
<style>
.avatar-sm {
font-size: 0.875rem;
}
.table th {
font-weight: 600;
color: #495057;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.table td {
vertical-align: middle;
}
.btn-group .btn {
margin-right: 2px;
}
.btn-group .btn:last-child {
margin-right: 0;
}
</style>
{% endblock %}
+168
View File
@@ -0,0 +1,168 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<h2 class="mb-4">Admin Management</h2>
<!-- User Management Section -->
<div class="card mb-4">
<div class="card-header">
<h3 class="mb-0">User Management</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Company</th>
<th>Registration Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.company }}</td>
<td>{{ user.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
{% if user.is_admin %}
<span class="badge bg-primary">Admin</span>
{% else %}
<span class="badge bg-success">User</span>
{% endif %}
</td>
<td>
{% if not user.is_admin %}
<button class="btn btn-danger btn-sm"
onclick="deleteUser({{ user.id }})"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
Delete
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Quotation Management Section -->
<div class="card">
<div class="card-header">
<h3 class="mb-0">Quotation Management</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>User</th>
<th>Model</th>
<th>Tooling ID</th>
<th>Total Cost</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for quotation in quotations %}
<tr>
<td>{{ quotation.id }}</td>
<td>{{ quotation.user.username }}</td>
<td>{{ quotation.model_name }}</td>
<td>{{ quotation.tooling_id }}</td>
<td>${{ "%.2f"|format(quotation.total_cost) }}</td>
<td>{{ quotation.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<button class="btn btn-danger btn-sm"
onclick="deleteQuotation({{ quotation.id }})"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
Delete
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirm Delete</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this item?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDelete">Delete</button>
</div>
</div>
</div>
</div>
<script>
let itemToDelete = null;
let deleteType = null;
function deleteUser(userId) {
itemToDelete = userId;
deleteType = 'user';
}
function deleteQuotation(quoteId) {
itemToDelete = quoteId;
deleteType = 'quotation';
}
document.getElementById('confirmDelete').addEventListener('click', function() {
if (!itemToDelete || !deleteType) return;
const url = deleteType === 'user' ? '/admin/delete_user' : '/admin/delete_quotation';
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ id: itemToDelete })
})
.then(response => response.json())
.then(data => {
if (data.message) {
location.reload();
} else {
alert('Error deleting item');
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting item');
})
.finally(() => {
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteModal'));
modal.hide();
});
});
</script>
{% endblock %}
+128
View File
@@ -0,0 +1,128 @@
{% extends "base.html" %}
{% block title %}Admin Portal - MoldCost Pro{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-lock me-2"></i>Quotation Records</h2>
<div>
<a href="{{ url_for('calculator') }}" class="btn btn-secondary">
<i class="fas fa-calculator me-2"></i>Calculator
</a>
<a href="{{ url_for('logout') }}" class="btn btn-danger">
<i class="fas fa-sign-out-alt me-2"></i>Logout
</a>
</div>
</div>
<div class="card shadow">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>Date</th>
<th>Company</th>
<th>User</th>
<th class="text-end">Model</th>
<th class="text-end">Tooling ID</th>
<th class="text-end">Total</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{% for quotation in quotations %}
<tr>
<td>{{ quotation.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>{{ quotation.user.company }}</td>
<td>{{ quotation.user.username }}</td>
<td class="text-end">{{ quotation.model_name }}</td>
<td class="text-end">{{ quotation.tooling_id }}</td>
<td class="text-end fw-bold">${{ "{:,.2f}".format(quotation.total_cost) }}</td>
<td>
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal"
data-bs-target="#detailModal{{ quotation.id }}">
<i class="fas fa-search"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% for quotation in quotations %}
<div class="modal fade" id="detailModal{{ quotation.id }}" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Quotation Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-muted mb-3">Basic Information</h6>
<dl class="row">
<dt class="col-sm-4">Date</dt>
<dd class="col-sm-8">{{ quotation.created_at.strftime('%Y-%m-%d %H:%M') }}</dd>
<dt class="col-sm-4">Company</dt>
<dd class="col-sm-8">{{ quotation.user.company }}</dd>
<dt class="col-sm-4">User</dt>
<dd class="col-sm-8">{{ quotation.user.username }}</dd>
<dt class="col-sm-4">Model</dt>
<dd class="col-sm-8">{{ quotation.model_name }}</dd>
<dt class="col-sm-4">Tooling ID</dt>
<dd class="col-sm-8">{{ quotation.tooling_id }}</dd>
</dl>
</div>
<div class="col-md-6">
<h6 class="text-muted mb-3">Mold Specifications</h6>
<dl class="row">
<dt class="col-sm-4">Mold Type</dt>
<dd class="col-sm-8">{{ quotation.mold_main_type }} - {{ quotation.mold_sub_type }}</dd>
<dt class="col-sm-4">Cavities</dt>
<dd class="col-sm-8">{{ quotation.cavity_count }} cavity</dd>
<dt class="col-sm-4">Sliders</dt>
<dd class="col-sm-8">{{ quotation.sliders_count }} slider</dd>
<dt class="col-sm-4">Texture</dt>
<dd class="col-sm-8">{{ quotation.digital_texture_type }}</dd>
<dt class="col-sm-4">High Sidewall</dt>
<dd class="col-sm-8">{{ 'Yes' if quotation.complexity_high_sidewall else 'No' }}</dd>
</dl>
</div>
</div>
<div class="row">
<div class="col-12">
<h6 class="text-muted mb-3">Cost Breakdown</h6>
<dl class="row">
<dt class="col-sm-4">Base Price</dt>
<dd class="col-sm-8">${{ "{:,.2f}".format(quotation.net_base_price) }}</dd>
<dt class="col-sm-4">Tax Rate</dt>
<dd class="col-sm-8">{{ "{:.1%}".format(quotation.tax_rate) }}</dd>
<dt class="col-sm-4">Total Cost</dt>
<dd class="col-sm-8 fw-bold">${{ "{:,.2f}".format(quotation.total_cost) }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}Login - Mold Cost Calculator{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Login</h4>
</div>
<div class="card-body">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control" + (" is-invalid" if form.email.errors else "")) }}
{% for error in form.email.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }}
{% for error in form.password.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3 form-check">
{{ form.remember_me(class="form-check-input") }}
{{ form.remember_me.label(class="form-check-label") }}
</div>
<div class="d-grid gap-2">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
<div class="card-footer text-center">
<p class="mb-0">Don't have an account? <a href="{{ url_for('auth.register') }}">Register here</a></p>
<p class="mb-0 mt-2"><a href="{{ url_for('auth.request_reset') }}">Forgot your password?</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+77
View File
@@ -0,0 +1,77 @@
{% extends "base.html" %}
{% block title %}Register - Mold Cost Calculator{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Register</h4>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control" + (" is-invalid" if form.username.errors else "")) }}
{% for error in form.username.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control" + (" is-invalid" if form.email.errors else "")) }}
{% for error in form.email.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.company.label(class="form-label") }}
{{ form.company(class="form-control" + (" is-invalid" if form.company.errors else "")) }}
{% for error in form.company.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }}
{% for error in form.password.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password_confirm.label(class="form-label") }}
{{ form.password_confirm(class="form-control" + (" is-invalid" if form.password_confirm.errors else "")) }}
{% for error in form.password_confirm.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3 form-check">
{{ form.terms(class="form-check-input" + (" is-invalid" if form.terms.errors else "")) }}
{{ form.terms.label(class="form-check-label") }}
{% for error in form.terms.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="d-grid gap-2">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
<div class="card-footer text-center">
<p class="mb-0">Already have an account? <a href="{{ url_for('auth.login') }}">Login here</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+44
View File
@@ -0,0 +1,44 @@
{% extends "base.html" %}
{% block title %}Request Password Reset - Mold Cost Calculator{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Request Password Reset</h4>
</div>
<div class="card-body">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('auth.request_reset') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control" + (" is-invalid" if form.email.errors else "")) }}
{% for error in form.email.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="d-grid gap-2">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
<div class="card-footer text-center">
<p class="mb-0">Remember your password? <a href="{{ url_for('auth.login') }}">Login here</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+52
View File
@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block title %}Reset Password - Mold Cost Calculator{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Reset Password</h4>
</div>
<div class="card-body">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }}
{% for error in form.password.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password_confirm.label(class="form-label") }}
{{ form.password_confirm(class="form-control" + (" is-invalid" if form.password_confirm.errors else "")) }}
{% for error in form.password_confirm.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
<div class="d-grid gap-2">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
<div class="card-footer text-center">
<p class="mb-0">Remember your password? <a href="{{ url_for('auth.login') }}">Login here</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+156
View File
@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{% block title %}{% endblock %} - Mold Cost Calculator</title>
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🧮</text></svg>">
<!-- Bootstrap CSS -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-icons.css') }}">
<!-- Custom Styles -->
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<!-- Custom Styles -->
<style>
:root {
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--accent-color: #0d6efd;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--light-color: #f8f9fa;
--dark-color: #212529;
--text-color: #212529;
--text-muted: #6c757d;
--border-color: #dee2e6;
--shadow-color: rgba(0, 0, 0, 0.1);
}
body {
font-family: 'Inter', sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: #f8f9fa;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
background-color: var(--primary-color);
padding: 1rem 0;
box-shadow: 0 2px 4px var(--shadow-color);
position: sticky;
top: 0;
z-index: 1000;
}
.navbar-brand {
color: white;
text-decoration: none;
font-size: 1.5rem;
font-weight: 600;
}
.nav-link {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.nav-link:hover {
color: white;
background-color: rgba(255, 255, 255, 0.2);
}
.nav-link.active {
color: white;
background-color: rgba(255, 255, 255, 0.2);
}
.main-content {
flex: 1;
padding: 2rem 0;
}
.footer {
background-color: var(--primary-color);
color: white;
padding: 1rem 0;
text-align: center;
margin-top: auto;
}
</style>
{% block head %}{% endblock %}
{% block styles %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="{{ url_for('main.home') }}">
<i class="bi bi-calculator me-2"></i>Mold Cost Calculator
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.calculator' %}active{% endif %}"
href="{{ url_for('main.calculator') }}">
<i class="bi bi-calculator me-1"></i>Calculator
</a>
</li>
</ul>
{% if current_user.is_authenticated %}
<div class="d-flex align-items-center">
<span class="text-white me-3">
<i class="bi bi-person me-2"></i>{{ current_user.username }}
</span>
<a href="{{ url_for('auth.logout') }}" class="btn btn-outline-light">
<i class="bi bi-box-arrow-right me-2"></i>Logout
</a>
</div>
{% endif %}
</div>
</div>
</nav>
<main class="main-content">
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</main>
<footer class="footer">
<div class="container">
<p class="mb-0">PCT & Tooling. All rights reserved.</p>
</div>
</footer>
<!-- Bootstrap Bundle with Popper -->
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<!-- jQuery -->
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>
+215
View File
@@ -0,0 +1,215 @@
{% extends "base.html" %}
{% block head %}
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Mold Cost Calculator</h2>
{% if current_user.is_authenticated and current_user.is_admin %}
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-outline-primary">
<i class="fas fa-cog me-2"></i>Admin Portal
</a>
{% endif %}
</div>
<form id="calcForm" class="row g-3" method="POST" action="/calculate">
{{ form.csrf_token }}
<!-- Factory Information -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-title mb-3">Factory Information</h6>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
{{ form.mold_shop_name.label(class="form-label") }}
{{ form.mold_shop_name(class="form-control") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{ form.mold_shop_country.label(class="form-label") }}
{{ form.mold_shop_country(class="form-control") }}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
{{ form.t1_shoe_factory_name.label(class="form-label") }}
{{ form.t1_shoe_factory_name(class="form-control") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{ form.t1_shoe_factory_country.label(class="form-label") }}
{{ form.t1_shoe_factory_country(class="form-control") }}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
{{ form.t2_component_factory_name.label(class="form-label") }}
{{ form.t2_component_factory_name(class="form-control") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{ form.t2_component_factory_country.label(class="form-label") }}
{{ form.t2_component_factory_country(class="form-control") }}
</div>
</div>
</div>
</div>
</div>
<!-- Mold Information -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-title mb-3">Mold Information</h6>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
{{ form.model_name.label(class="form-label") }}
{{ form.model_name(class="form-control") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{ form.tooling_id.label(class="form-label") }}
{{ form.tooling_id(class="form-control") }}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-group">
{{ form.season.label(class="form-label") }}
{{ form.season(class="form-control", id="season") }}
</div>
</div>
<div class="col-md-4">
<div class="form-group">
{{ form.stage.label(class="form-label") }}
{{ form.stage(class="form-control", id="stage") }}
</div>
</div>
<div class="col-md-4">
<div class="form-group">
{{ form.type.label(class="form-label") }}
{{ form.type(class="form-control", id="type") }}
</div>
</div>
</div>
</div>
</div>
<!-- Mold Specifications -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-title mb-3">Mold Specifications</h6>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-group">
{{ form.mold_main_type.label(class="form-label") }}
{{ form.mold_main_type(class="form-control", id="mold_main_type") }}
</div>
</div>
<div class="col-md-4">
<div class="form-group">
{{ form.mold_sub_type.label(class="form-label") }}
{{ form.mold_sub_type(class="form-control", id="mold_sub_type") }}
</div>
</div>
<div class="col-md-4">
<div class="form-group">
{{ form.mold_specific_type.label(class="form-label") }}
{{ form.mold_specific_type(class="form-control", id="mold_specific_type") }}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
{{ form.sliders_count.label(class="form-label") }}
{{ form.sliders_count(class="form-control") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{ form.cavity_count.label(class="form-label") }}
{{ form.cavity_count(class="form-control") }}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
{{ form.digital_texture_type.label(class="form-label") }}
{{ form.digital_texture_type(class="form-control") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{ form.complexity_high_sidewall.label(class="form-label") }}
{{ form.complexity_high_sidewall(class="form-control") }}
</div>
</div>
</div>
</div>
</div>
<!-- Dates & Approvals -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-title mb-3">Dates & Approvals</h6>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
{{ form.issue_date.label(class="form-label") }}
{{ form.issue_date(class="form-control", type="date") }}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-group">
{{ form.approved_by_mold_shop.label(class="form-label") }}
{{ form.approved_by_mold_shop(class="form-control") }}
</div>
</div>
<div class="col-md-4">
<div class="form-group">
{{ form.approved_by_t1_tooling.label(class="form-label") }}
{{ form.approved_by_t1_tooling(class="form-control") }}
</div>
</div>
<div class="col-md-4">
<div class="form-group">
{{ form.approved_by_lo_tooling.label(class="form-label") }}
{{ form.approved_by_lo_tooling(class="form-control") }}
</div>
</div>
</div>
</div>
</div>
<!-- Submit Button -->
<div class="text-center">
<button type="submit" id="calculateBtn" class="btn btn-primary btn-lg">Calculate</button>
</div>
</form>
<!-- Results Section -->
<div id="result" class="mt-4"></div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script id="mold-calculator-script" src="{{ url_for('static', filename='js/mold_calculator.js', v='1.4') }}"></script>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-danger text-white">
<h4 class="mb-0">Error {{ code }}</h4>
</div>
<div class="card-body">
<h5 class="card-title">{{ message }}</h5>
<p class="card-text">
{% if code == 404 %}
The page you're looking for doesn't exist.
{% elif code == 403 %}
You don't have permission to access this resource.
{% elif code == 400 %}
The request was invalid.
{% else %}
An unexpected error occurred. Please try again later.
{% endif %}
</p>
<a href="{{ url_for('main.home') }}" class="btn btn-primary">Return to Home</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Bad Request - MoldCost Pro{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="text-center">
<h1 class="display-1">400</h1>
<h2 class="mb-4">Bad Request</h2>
<p class="lead">The request was invalid or cannot be served.</p>
<a href="{{ url_for('main.home') }}" class="btn btn-primary">Return Home</a>
</div>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Forbidden - MoldCost Pro{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="text-center">
<h1 class="display-1">403</h1>
<h2 class="mb-4">Access Forbidden</h2>
<p class="lead">You don't have permission to access this resource.</p>
<a href="{{ url_for('main.home') }}" class="btn btn-primary">Return Home</a>
</div>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Not Found - MoldCost Pro{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="text-center">
<h1 class="display-1">404</h1>
<h2 class="mb-4">Page Not Found</h2>
<p class="lead">The page you're looking for doesn't exist.</p>
<a href="{{ url_for('main.home') }}" class="btn btn-primary">Return Home</a>
</div>
</div>
{% endblock %}
+22
View File
@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-warning text-white">
<h4 class="mb-0">Conflict</h4>
</div>
<div class="card-body">
<h5 class="card-title">Data Constraint Error</h5>
<p class="card-text">{{ message }}</p>
<p class="card-text">This error typically occurs when trying to create a duplicate record or violate a data constraint.</p>
<p class="card-text">Please check your input and try again.</p>
<a href="{{ url_for('main.index') }}" class="btn btn-primary">Return to Home</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Server Error - MoldCost Pro{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="text-center">
<h1 class="display-1">500</h1>
<h2 class="mb-4">Server Error</h2>
<p class="lead">Something went wrong on our end. Please try again later.</p>
<a href="{{ url_for('main.home') }}" class="btn btn-primary">Return Home</a>
</div>
</div>
{% endblock %}
+22
View File
@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-warning text-white">
<h4 class="mb-0">Service Temporarily Unavailable</h4>
</div>
<div class="card-body">
<h5 class="card-title">Database Connection Error</h5>
<p class="card-text">{{ message }}</p>
<p class="card-text">Our technical team has been notified and is working to resolve this issue.</p>
<p class="card-text">Please try again in a few minutes.</p>
<a href="{{ url_for('main.index') }}" class="btn btn-primary">Return to Home</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+102
View File
@@ -0,0 +1,102 @@
{% extends "base.html" %}
{% block content %}
<div class="container-main py-5">
<div class="hero-section bg-gradient-primary text-white rounded-3 p-5 shadow-lg">
<div class="text-center">
<h1 class="display-4 fw-bold mb-4">
<i class="bi bi-gear-connected"></i>
MoldCost Pro
</h1>
<p class="lead mb-4">Precision Mold Cost Calculations</p>
<div class="d-grid gap-3 d-md-flex justify-content-md-center">
{% if current_user.is_authenticated %}
<a href="{{ url_for('calculator') }}" class="btn btn-light btn-lg px-4">
<i class="bi bi-calculator me-2"></i>
Go to Calculator
</a>
{% else %}
<a href="{{ url_for('login') }}" class="btn btn-light btn-lg px-4">
<i class="bi bi-box-arrow-in-right me-2"></i>
Get Started
</a>
<a href="{{ url_for('register') }}" class="btn btn-outline-light btn-lg px-4">
<i class="bi bi-person-plus me-2"></i>
Register
</a>
{% endif %}
</div>
</div>
</div>
<!-- Features Section -->
<div class="row g-4 mt-5">
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<i class="bi bi-speedometer2 display-4 text-primary"></i>
<h3 class="mt-3">Fast Calculation</h3>
<p class="text-muted">Instant mold cost estimates with industry-standard algorithms</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<i class="bi bi-clock-history display-4 text-primary"></i>
<h3 class="mt-3">History Tracking</h3>
<p class="text-muted">Review your previous quotes and calculations</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<i class="bi bi-shield-lock display-4 text-primary"></i>
<h3 class="mt-3">Secure Data</h3>
<p class="text-muted">Enterprise-grade security for your sensitive data</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block styles %}
<style>
.hero-section {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
position: relative;
overflow: hidden;
}
.hero-section:before {
content: "";
position: absolute;
top: -50%;
left: -50%;
right: -50%;
bottom: -50%;
background: linear-gradient(45deg,
rgba(255,255,255,0.1) 25%,
transparent 25%,
transparent 50%,
rgba(255,255,255,0.1) 50%,
rgba(255,255,255,0.1) 75%,
transparent 75%,
transparent
);
background-size: 40px 40px;
animation: animateStripes 4s linear infinite;
opacity: 0.15;
}
@keyframes animateStripes {
0% { transform: translateY(0); }
100% { transform: translateY(-40px); }
}
</style>
{% endblock %}
+82
View File
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Commercial Invoice - {{ quote.id }} | MoldCost Pro</title>
<style>
:root {
--primary-color: #1a365d;
--secondary-color: #2c5282;
--accent-color: #c53030;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
color: #1a202c;
line-height: 1.6;
margin: 0;
padding: 20px;
}
.letterhead {
border-bottom: 2px solid var(--primary-color);
margin-bottom: 2rem;
padding-bottom: 1.5rem;
}
.company-logo {
height: 60px;
margin-bottom: 1rem;
}
.legal-disclaimer {
font-size: 0.75rem;
color: #718096;
margin-top: 3rem;
}
@media print {
.page-break { page-break-before: always; }
}
</style>
</head>
<body>
<div class="letterhead">
<img src="https://cdn.example.com/logo.svg"
alt="MoldCost Pro"
class="company-logo">
<div class="flex justify-between text-sm text-gray-600">
<div>
<p>MoldCost Pro Limited</p>
<p>123 Manufacturing Blvd, Shenzhen, CN</p>
</div>
<div class="text-right">
<p>T: +86 (755) 1234-5678</p>
<p>E: sales@moldcostpro.com</p>
<p>W: www.moldcostpro.com</p>
</div>
</div>
</div>
<div class="header-section">
<h1 class="text-2xl font-bold">Commercial Invoice</h1>
<div class="flex justify-between text-sm text-gray-600 mt-2">
<div>
<p><strong>Quote ID:</strong> MC-{{ quote.id|string|replace(' ', '0') }}</p>
<p><strong>Date:</strong> {{ quote.created_at.strftime('%d-%b-%Y') }}</p>
</div>
<div>
<p><strong>Currency:</strong> USD</p>
<p><strong>Valid Until:</strong> {{ (quote.created_at + datetime.timedelta(days=30)).strftime('%d-%b-%Y') }}</p>
</div>
</div>
</div>
{% from "_quotation_details.html" import render_quotation_details %}
{{ render_quotation_details(quote, show_actions=false) }}
<div class="legal-disclaimer">
<p>This quotation is subject to our standard terms and conditions. Prices valid for 30 days from quotation date. All measurements tolerances ±0.5% unless otherwise specified. Export documentation extra.</p>
</div>
</body>
</html>
+68
View File
@@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}Terms of Service - Mold Cost Online Tool{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-body p-5">
<h3 class="mb-4 text-center">Terms of Service</h3>
<div class="terms-content">
<h4 class="mb-3">1. Acceptance of Terms</h4>
<p>By accessing and using the Mold Cost Online Tool, you agree to be bound by these Terms of Service.</p>
<h4 class="mb-3 mt-4">2. Use of Service</h4>
<p>The Mold Cost Online Tool is provided for professional use in the footwear industry. Users must:</p>
<ul>
<li>Provide accurate and complete information</li>
<li>Maintain the confidentiality of their account</li>
<li>Use the service in compliance with all applicable laws</li>
</ul>
<h4 class="mb-3 mt-4">3. Data Privacy</h4>
<p>We are committed to protecting your data:</p>
<ul>
<li>All calculations and quotations are stored securely</li>
<li>Personal information is used only for service provision</li>
<li>Data is not shared with third parties without consent</li>
</ul>
<h4 class="mb-3 mt-4">4. Intellectual Property</h4>
<p>The Mold Cost Online Tool and its contents are protected by intellectual property rights. Users may not:</p>
<ul>
<li>Copy or reproduce the tool's algorithms</li>
<li>Reverse engineer the service</li>
<li>Use the service for unauthorized commercial purposes</li>
</ul>
<h4 class="mb-3 mt-4">5. Disclaimer</h4>
<p>The tool provides estimates based on industry standards. While we strive for accuracy:</p>
<ul>
<li>Results are estimates only</li>
<li>Actual costs may vary</li>
<li>Users should verify calculations independently</li>
</ul>
<h4 class="mb-3 mt-4">6. Account Termination</h4>
<p>We reserve the right to terminate accounts that:</p>
<ul>
<li>Violate these terms</li>
<li>Engage in fraudulent activity</li>
<li>Misuse the service</li>
</ul>
<div class="mt-5 text-center">
<a href="{{ url_for('register') }}" class="btn btn-primary">
<i class="bi bi-arrow-left me-2"></i>Back to Registration
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}