A bunch of updates

This commit is contained in:
2026-06-20 06:05:50 -05:00
parent 8cd3aa5f84
commit 06ac8d6eca
24 changed files with 1447 additions and 516 deletions

Binary file not shown.

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

View File

@@ -20,6 +20,7 @@ from django.urls import include, path
urlpatterns = [
path("public/", include("public.urls")),
path("", include("public.urls")),
path("financial/", include("financial.urls")),
path("planning/", include("planning.urls")),
path("accounts/", include("django.contrib.auth.urls")),

18
company_site/financial/admin.py Normal file → Executable file
View File

@@ -1,8 +1,24 @@
from django.contrib import admin
from .models import Contract
from .models import Contract, Employee, ChargeNumber, TimeCard, TimeCardCell
# Register your models here.
class ContractAdmin(admin.ModelAdmin):
pass
class EmployeeAdmin(admin.ModelAdmin):
pass
class ChargeNumberAdmin(admin.ModelAdmin):
pass
class TimeCardAdmin(admin.ModelAdmin):
pass
class TimeCardCellAdmin(admin.ModelAdmin):
pass
admin.site.register(Contract, ContractAdmin)
admin.site.register(Employee, EmployeeAdmin)
admin.site.register(ChargeNumber, ChargeNumberAdmin)
admin.site.register(TimeCard, TimeCardAdmin)
admin.site.register(TimeCardCell, TimeCardCellAdmin)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0 on 2026-03-26 14:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('financial', '0013_alter_employee_primaryaddress_and_more'),
]
operations = [
migrations.AddField(
model_name='chargenumber',
name='name',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

3
company_site/financial/models.py Normal file → Executable file
View File

@@ -192,6 +192,7 @@ class ChargeNumber(IdMixin, TimeMixin):
MAX_NUM_TASK_TYPES = "MAX", "MAX_NUM_TASK_TYPES"
charge_number_type = EnumField(ChargeNumberTypeEnum)
name = models.CharField(max_length=100, blank=True, null=True)
contract = models.ForeignKey(Contract, on_delete=models.CASCADE)
amount = models.FloatField(default=0.0)
@@ -202,7 +203,7 @@ class ChargeNumber(IdMixin, TimeMixin):
end_date = models.DateField(null=True, blank=True, default = None)
def __str__(self):
return self.slug or f"CN-{self.id}"
return self.name or self.slug or f"CN-{self.id}"
@property
def get_percent_complete(self):

View File

@@ -30,6 +30,7 @@
<button type="submit" class="btn" style="margin-top: 1rem;">Update</button>
</form>
</div>
{% endif %}
<hr style="margin: 40px 0; border: 0; border-top: 1px solid rgba(255,255,255,0.1);">
@@ -40,7 +41,8 @@
<h2 class="section-title" style="text-align: left; font-size: 2rem;">Earned Value Management</h2>
<!-- EVM KPI Cards -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-bottom: 2rem;">
<div
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-bottom: 2rem;">
<!-- BAC -->
<div class="card evm-kpi-card">
<span class="evm-kpi-label">BAC (Budget)</span>
@@ -68,7 +70,8 @@
</div>
<!-- Variance & Index Cards -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-bottom: 2rem;">
<div
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-bottom: 2rem;">
<!-- SV -->
<div class="card evm-kpi-card">
<span class="evm-kpi-label">Schedule Variance</span>
@@ -88,32 +91,38 @@
<!-- SPI -->
<div class="card evm-kpi-card">
<span class="evm-kpi-label">SPI</span>
<span class="evm-kpi-value {% if evm.spi >= 1 %}evm-positive{% elif evm.spi > 0 %}evm-negative{% endif %}">
<span
class="evm-kpi-value {% if evm.spi >= 1 %}evm-positive{% elif evm.spi > 0 %}evm-negative{% endif %}">
{{ evm.spi }}
</span>
<span class="evm-kpi-sub">{% if evm.spi >= 1 %}On/ahead{% elif evm.spi > 0 %}Behind{% else %}No data{% endif %}</span>
<span class="evm-kpi-sub">{% if evm.spi >= 1 %}On/ahead{% elif evm.spi > 0 %}Behind{% else %}No data{%
endif %}</span>
</div>
<!-- CPI -->
<div class="card evm-kpi-card">
<span class="evm-kpi-label">CPI</span>
<span class="evm-kpi-value {% if evm.cpi >= 1 %}evm-positive{% elif evm.cpi > 0 %}evm-negative{% endif %}">
<span
class="evm-kpi-value {% if evm.cpi >= 1 %}evm-positive{% elif evm.cpi > 0 %}evm-negative{% endif %}">
{{ evm.cpi }}
</span>
<span class="evm-kpi-sub">{% if evm.cpi >= 1 %}Efficient{% elif evm.cpi > 0 %}Over-spending{% else %}No data{% endif %}</span>
<span class="evm-kpi-sub">{% if evm.cpi >= 1 %}Efficient{% elif evm.cpi > 0 %}Over-spending{% else %}No
data{% endif %}</span>
</div>
</div>
<!-- Charts Row -->
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 2rem;">
<!-- S-Curve Chart -->
<div class="card" style="padding: 20px;">
<h3 style="margin-bottom: 1rem; font-size: 1.1rem; color: var(--text-muted);">S-Curve (PV vs EV vs AC)</h3>
<canvas id="evmSCurveChart"></canvas>
<!-- Charts: full-width rows -->
<div class="evm-charts-stack">
<div class="card evm-chart-card">
<h3 class="evm-chart-title">S-Curve (PV vs EV vs AC)</h3>
<div class="evm-chart-canvas-wrap">
<canvas id="evmSCurveChart"></canvas>
</div>
</div>
<!-- SPI / CPI Bar Chart -->
<div class="card" style="padding: 20px;">
<h3 style="margin-bottom: 1rem; font-size: 1.1rem; color: var(--text-muted);">Performance Indices</h3>
<canvas id="evmIndexChart"></canvas>
<div class="card evm-chart-card">
<h3 class="evm-chart-title">Performance Indices</h3>
<div class="evm-chart-canvas-wrap evm-chart-canvas-wrap--compact">
<canvas id="evmIndexChart"></canvas>
</div>
</div>
</div>
{% endif %}
@@ -123,9 +132,12 @@
<h2 class="section-title" style="text-align: left; font-size: 2rem;">Charge Numbers</h2>
{% if charge_numbers %}
{% if mermaid_gantt %}
<div class="card" style="margin-bottom: 2rem; background: var(--surface-color);">
<div class="mermaid">
{{ mermaid_gantt|safe }}
<div class="card charge-gantt-card" style="margin-bottom: 2rem; background: var(--surface-color);">
<h3 class="evm-chart-title" style="margin-bottom: 1rem;">Timeline</h3>
<div class="charge-gantt-scroll">
<div class="mermaid">
{{ mermaid_gantt|safe }}
</div>
</div>
</div>
{% endif %}
@@ -135,7 +147,7 @@
<table class="table">
<thead>
<tr>
<th>Slug/ID</th>
<th>Slug/ID/Name</th>
<th>Type</th>
<th>Amount</th>
<th>% Complete</th>
@@ -147,18 +159,20 @@
<tbody>
{% for cn in charge_numbers %}
<tr>
<td>{{ cn.slug }}</td>
<td>{{ cn }}</td>
<td>{{ cn.get_charge_number_type_display }}</td>
<td>${{ cn.amount|floatformat:2 }}</td>
<td>
<span class="badge" style="background: rgba(0, 243, 255, 0.1); border: 1px solid var(--primary-color); color: var(--primary-color);">
<span class="badge"
style="background: rgba(0, 243, 255, 0.1); border: 1px solid var(--primary-color); color: var(--primary-color);">
{{ cn.percent_complete|floatformat:0 }}%
</span>
</td>
<td>{{ cn.start_date|default:"-" }}</td>
<td>{{ cn.end_date|default:"-" }}</td>
<td class="charge-date-cell">{{ cn.start_date|default:"-" }}</td>
<td class="charge-date-cell">{{ cn.end_date|default:"-" }}</td>
<td>
<a href="{% url 'update_charge_number' cn.slug %}" class="text-cyber-cyan" style="font-size: 0.9rem;">Edit</a>
<a href="{% url 'update_charge_number' cn.slug %}" class="text-cyber-cyan"
style="font-size: 0.9rem;">Edit</a>
</td>
</tr>
{% endfor %}
@@ -177,7 +191,8 @@
</div>
{% else %}
<div class="card" style="max-width: 600px;">
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">There are no charge numbers for this contract.</p>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">There are no charge numbers for this contract.
</p>
<form method="post" action='{% url "new_charge_number" contract.slug %}'>
{% csrf_token %}
{{ charge_number_form.as_p }}
@@ -198,14 +213,16 @@
padding: 20px 16px;
text-align: center;
background: var(--surface-color);
border: 1px solid rgba(255,255,255,0.08);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.evm-kpi-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 243, 255, 0.08);
}
.evm-kpi-label {
font-size: 0.75rem;
text-transform: uppercase;
@@ -214,19 +231,71 @@
margin-bottom: 6px;
font-weight: 600;
}
.evm-kpi-value {
font-size: 1.5rem;
font-weight: 800;
color: #fff;
line-height: 1.2;
}
.evm-kpi-sub {
font-size: 0.75rem;
color: var(--text-muted, #888);
margin-top: 4px;
}
.evm-positive { color: #39ff14 !important; }
.evm-negative { color: #ff4444 !important; }
.evm-positive {
color: #39ff14 !important;
}
.evm-negative {
color: #ff4444 !important;
}
.evm-charts-stack {
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 2rem;
width: 100%;
}
.evm-chart-card {
padding: 20px;
width: 100%;
}
.evm-chart-title {
margin-bottom: 1rem;
font-size: 1.1rem;
color: var(--text-muted);
}
.evm-chart-canvas-wrap {
position: relative;
width: 100%;
height: 320px;
}
.evm-chart-canvas-wrap--compact {
height: 260px;
max-width: 480px;
}
.charge-gantt-scroll {
overflow-x: auto;
width: 100%;
padding-bottom: 0.5rem;
}
.charge-gantt-scroll .mermaid {
min-width: 720px;
}
.charge-date-cell {
white-space: nowrap;
}
</style>
<script type="module">
@@ -237,165 +306,167 @@
{% if evm_chart_json %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const raw = '{{ evm_chart_json|escapejs }}';
if (!raw) return;
const data = JSON.parse(raw);
document.addEventListener("DOMContentLoaded", function () {
const raw = '{{ evm_chart_json|escapejs }}';
if (!raw) return;
const data = JSON.parse(raw);
Chart.defaults.color = '#e0e0e0';
Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.1)';
Chart.defaults.color = '#e0e0e0';
Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.1)';
/* ─────── S-Curve Chart ─────── */
const sCurveCanvas = document.getElementById('evmSCurveChart');
if (sCurveCanvas && data.time_series && data.time_series.length > 0) {
const labels = data.time_series.map(d => d.month);
const pvData = data.time_series.map(d => d.pv);
const evData = data.time_series.map(d => d.ev);
const acData = data.time_series.map(d => d.ac);
/* ─────── S-Curve Chart ─────── */
const sCurveCanvas = document.getElementById('evmSCurveChart');
if (sCurveCanvas && data.time_series && data.time_series.length > 0) {
const labels = data.time_series.map(d => d.month);
const pvData = data.time_series.map(d => d.pv);
const evData = data.time_series.map(d => d.ev);
const acData = data.time_series.map(d => d.ac);
new Chart(sCurveCanvas.getContext('2d'), {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Planned Value (PV)',
data: pvData,
borderColor: '#00f3ff',
backgroundColor: 'rgba(0, 243, 255, 0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#00f3ff',
},
{
label: 'Earned Value (EV)',
data: evData,
borderColor: '#39ff14',
backgroundColor: 'rgba(57, 255, 20, 0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#39ff14',
},
{
label: 'Actual Cost (AC)',
data: acData,
borderColor: '#bc13fe',
backgroundColor: 'rgba(188, 19, 254, 0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#bc13fe',
}
]
},
options: {
responsive: true,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { labels: { color: '#e0e0e0', usePointStyle: true, padding: 16 } },
tooltip: {
callbacks: {
label: ctx => `${ctx.dataset.label}: $${ctx.parsed.y.toLocaleString()}`
new Chart(sCurveCanvas.getContext('2d'), {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Planned Value (PV)',
data: pvData,
borderColor: '#00f3ff',
backgroundColor: 'rgba(0, 243, 255, 0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#00f3ff',
},
{
label: 'Earned Value (EV)',
data: evData,
borderColor: '#39ff14',
backgroundColor: 'rgba(57, 255, 20, 0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#39ff14',
},
{
label: 'Actual Cost (AC)',
data: acData,
borderColor: '#bc13fe',
backgroundColor: 'rgba(188, 19, 254, 0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#bc13fe',
}
}
]
},
scales: {
x: {
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#a0a0a0' }
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { labels: { color: '#e0e0e0', usePointStyle: true, padding: 16 } },
tooltip: {
callbacks: {
label: ctx => `${ctx.dataset.label}: $${ctx.parsed.y.toLocaleString()}`
}
}
},
y: {
beginAtZero: true,
title: { display: true, text: 'Dollars ($)', color: '#a0a0a0' },
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: {
color: '#a0a0a0',
callback: v => '$' + v.toLocaleString()
scales: {
x: {
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#a0a0a0' }
},
y: {
beginAtZero: true,
title: { display: true, text: 'Dollars ($)', color: '#a0a0a0' },
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: {
color: '#a0a0a0',
callback: v => '$' + v.toLocaleString()
}
}
}
}
}
});
}
});
}
/* ─────── SPI / CPI Bar Chart ─────── */
const indexCanvas = document.getElementById('evmIndexChart');
if (indexCanvas) {
const spi = data.spi || 0;
const cpi = data.cpi || 0;
/* ─────── SPI / CPI Bar Chart ─────── */
const indexCanvas = document.getElementById('evmIndexChart');
if (indexCanvas) {
const spi = data.spi || 0;
const cpi = data.cpi || 0;
new Chart(indexCanvas.getContext('2d'), {
type: 'bar',
data: {
labels: ['SPI', 'CPI'],
datasets: [{
label: 'Performance Index',
data: [spi, cpi],
backgroundColor: [
spi >= 1 ? 'rgba(57, 255, 20, 0.6)' : 'rgba(255, 68, 68, 0.6)',
cpi >= 1 ? 'rgba(57, 255, 20, 0.6)' : 'rgba(255, 68, 68, 0.6)',
],
borderColor: [
spi >= 1 ? '#39ff14' : '#ff4444',
cpi >= 1 ? '#39ff14' : '#ff4444',
],
borderWidth: 2,
borderRadius: 6,
barPercentage: 0.5,
}]
},
plugins: [{
id: 'baselineLine',
afterDraw(chart) {
const yScale = chart.scales.y;
const ctx = chart.ctx;
const yPixel = yScale.getPixelForValue(1);
ctx.save();
ctx.strokeStyle = '#00f3ff';
ctx.lineWidth = 2;
ctx.setLineDash([6, 4]);
ctx.beginPath();
ctx.moveTo(chart.chartArea.left, yPixel);
ctx.lineTo(chart.chartArea.right, yPixel);
ctx.stroke();
// Label
ctx.fillStyle = '#00f3ff';
ctx.font = '11px Inter, sans-serif';
ctx.fillText('Target = 1.0', chart.chartArea.right - 72, yPixel - 6);
ctx.restore();
}
}],
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: ctx => `${ctx.label}: ${ctx.parsed.y.toFixed(2)}`
}
}
new Chart(indexCanvas.getContext('2d'), {
type: 'bar',
data: {
labels: ['SPI', 'CPI'],
datasets: [{
label: 'Performance Index',
data: [spi, cpi],
backgroundColor: [
spi >= 1 ? 'rgba(57, 255, 20, 0.6)' : 'rgba(255, 68, 68, 0.6)',
cpi >= 1 ? 'rgba(57, 255, 20, 0.6)' : 'rgba(255, 68, 68, 0.6)',
],
borderColor: [
spi >= 1 ? '#39ff14' : '#ff4444',
cpi >= 1 ? '#39ff14' : '#ff4444',
],
borderWidth: 2,
borderRadius: 6,
barPercentage: 0.5,
}]
},
scales: {
x: {
grid: { display: false },
ticks: { color: '#e0e0e0', font: { size: 14, weight: 'bold' } }
plugins: [{
id: 'baselineLine',
afterDraw(chart) {
const yScale = chart.scales.y;
const ctx = chart.ctx;
const yPixel = yScale.getPixelForValue(1);
ctx.save();
ctx.strokeStyle = '#00f3ff';
ctx.lineWidth = 2;
ctx.setLineDash([6, 4]);
ctx.beginPath();
ctx.moveTo(chart.chartArea.left, yPixel);
ctx.lineTo(chart.chartArea.right, yPixel);
ctx.stroke();
// Label
ctx.fillStyle = '#00f3ff';
ctx.font = '11px Inter, sans-serif';
ctx.fillText('Target = 1.0', chart.chartArea.right - 72, yPixel - 6);
ctx.restore();
}
}],
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: ctx => `${ctx.label}: ${ctx.parsed.y.toFixed(2)}`
}
}
},
y: {
beginAtZero: true,
suggestedMax: Math.max(spi, cpi, 1.5) + 0.3,
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#a0a0a0' }
scales: {
x: {
grid: { display: false },
ticks: { color: '#e0e0e0', font: { size: 14, weight: 'bold' } }
},
y: {
beginAtZero: true,
suggestedMax: Math.max(spi, cpi, 1.5) + 0.3,
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#a0a0a0' }
}
}
}
}
});
}
});
});
}
});
</script>
{% endif %}
{% endblock %}

View File

@@ -17,6 +17,110 @@
</div>
</div>
<div class="card" style="margin-bottom: 2rem; padding: 1.25rem;">
<form method="get" action="{% url 'time_logs' %}"
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; align-items: end;">
<div>
<label for="filter-employee"
style="display: block; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.35rem;">Employee <span style="opacity: 0.75;">(Ctrl/Cmd+click for multiple)</span></label>
<select name="employee" id="filter-employee" multiple size="5"
style="width: 100%; padding: 0.5rem; background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px;">
{% for emp in employees %}
<option value="{{ emp.id }}" {% if emp.id|stringformat:"s" in filters.employees %}selected{% endif %}>{{ emp }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="filter-month"
style="display: block; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.35rem;">Month</label>
<input type="month" name="month" id="filter-month" value="{{ filters.month }}"
style="width: 100%; padding: 0.5rem; background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px;">
</div>
<div>
<label for="filter-contract"
style="display: block; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.35rem;">Contract</label>
<select name="contract" id="filter-contract"
style="width: 100%; padding: 0.5rem; background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px;">
<option value="">All contracts</option>
{% for c in contracts %}
<option value="{{ c.id }}" {% if filters.contract == c.id|stringformat:"s" %}selected{% endif %}>{{ c.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="filter-charge-number"
style="display: block; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.35rem;">Charge Number</label>
<select name="charge_number" id="filter-charge-number"
style="width: 100%; padding: 0.5rem; background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px;">
<option value="">All charge numbers</option>
{% for cn in charge_numbers %}
<option value="{{ cn.id }}" {% if filters.charge_number == cn.id|stringformat:"s" %}selected{% endif %}>{{ cn }} ({{ cn.contract.name }})</option>
{% endfor %}
</select>
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button type="submit" class="btn" style="padding: 0.5rem 1.25rem; font-size: 0.9rem;">Apply</button>
<a href="{% url 'time_logs' %}" class="btn"
style="padding: 0.5rem 1.25rem; font-size: 0.9rem; background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.1);">Clear</a>
</div>
</form>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
<div class="card" style="padding: 1.25rem;">
<h2 style="font-size: 1rem; margin: 0 0 1rem 0; color: var(--text-muted);">Hours by Contract</h2>
{% if contract_totals %}
<table class="table" style="margin-bottom: 0;">
<thead>
<tr>
<th>Contract</th>
<th style="text-align: right;">Hours</th>
</tr>
</thead>
<tbody>
{% for row in contract_totals %}
<tr>
<td>{{ row.charge_number__contract__name }}</td>
<td style="text-align: right;">{{ row.total_hours|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color: var(--text-muted); margin: 0;">No contract totals for current filters.</p>
{% endif %}
</div>
<div class="card" style="padding: 1.25rem;">
<h2 style="font-size: 1rem; margin: 0 0 1rem 0; color: var(--text-muted);">Hours by Charge Number</h2>
{% if charge_number_totals %}
<table class="table" style="margin-bottom: 0;">
<thead>
<tr>
<th>Charge Number</th>
<th>Contract</th>
<th style="text-align: right;">Hours</th>
</tr>
</thead>
<tbody>
{% for row in charge_number_totals %}
<tr>
<td>{{ row.charge_number__name|default:"—" }}</td>
<td>{{ row.charge_number__contract__name }}</td>
<td style="text-align: right;">{{ row.total_hours|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color: var(--text-muted); margin: 0;">No charge number totals for current filters.</p>
{% endif %}
</div>
</div>
<p style="margin: -1rem 0 2rem 0; font-size: 0.95rem; color: var(--text-muted);">
<strong style="color: var(--text-color);">Grand total:</strong> {{ grand_total|floatformat:2 }} hrs
<span style="opacity: 0.75;">(all filtered rows, including entries without a charge number)</span>
</p>
<div class="table-responsive">
<table class="table">
<thead>

View File

@@ -114,7 +114,7 @@ def contract_detail(request, contract_slug):
has_dates = True
start = cn.start_date.strftime("%Y-%m-%d")
end = cn.end_date.strftime("%Y-%m-%d")
slug_label = cn.slug or f"ID-{cn.id}"
slug_label = cn.name or cn.slug or f"ID-{cn.id}"
mermaid_gantt_lines.append(f" {slug_label} : {start}, {end}")
mermaid_gantt = None
@@ -178,8 +178,72 @@ def timekeeping(request):
@user_passes_test(is_admin)
def time_logs(request):
logs = TimeCardCell.objects.all().order_by('-date', '-created')
return render(request, 'financial/time_logs.html', {'logs': logs})
logs = TimeCardCell.objects.select_related(
'timeCard__employee__user',
'charge_number__contract',
).order_by('-date', '-created')
employee_ids = []
for raw in request.GET.getlist('employee'):
if raw:
try:
employee_ids.append(int(raw))
except (ValueError, TypeError):
pass
month = request.GET.get('month')
contract_id = request.GET.get('contract')
charge_number_id = request.GET.get('charge_number')
if employee_ids:
logs = logs.filter(timeCard__employee_id__in=employee_ids)
if month:
try:
year, mon = month.split('-', 1)
logs = logs.filter(date__year=int(year), date__month=int(mon))
except (ValueError, TypeError):
pass
if contract_id:
logs = logs.filter(charge_number__contract_id=contract_id)
if charge_number_id:
logs = logs.filter(charge_number_id=charge_number_id)
charge_numbers = ChargeNumber.objects.select_related('contract').order_by('contract__name', 'name')
if contract_id:
charge_numbers = charge_numbers.filter(contract_id=contract_id)
contract_totals = (
logs.filter(charge_number__isnull=False)
.values('charge_number__contract_id', 'charge_number__contract__name')
.annotate(total_hours=Sum('hour'))
.order_by('charge_number__contract__name')
)
charge_number_totals = (
logs.filter(charge_number__isnull=False)
.values(
'charge_number_id',
'charge_number__name',
'charge_number__contract__name',
)
.annotate(total_hours=Sum('hour'))
.order_by('charge_number__contract__name', 'charge_number__name')
)
grand_total = logs.aggregate(total_hours=Sum('hour'))['total_hours'] or 0.0
return render(request, 'financial/time_logs.html', {
'logs': logs,
'employees': Employee.objects.select_related('user').order_by('user__last_name', 'user__first_name'),
'contracts': Contract.objects.order_by('name'),
'charge_numbers': charge_numbers,
'contract_totals': contract_totals,
'charge_number_totals': charge_number_totals,
'grand_total': grand_total,
'filters': {
'employees': [str(eid) for eid in employee_ids],
'month': month or '',
'contract': contract_id or '',
'charge_number': charge_number_id or '',
},
})
@user_passes_test(is_admin)
def edit_time_log(request, log_id):

View File

@@ -8,24 +8,58 @@
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<h2 class="section-title" style="margin-bottom: 0;">Backlog</h2>
<div>
<a href="{% url 'planning:board_view' %}" class="btn" style="padding: 0.5rem 1rem; border-radius: 8px; margin-right: 1rem; background: var(--surface-color); color: var(--text-color); border: 1px solid var(--primary-color);">View Board</a>
<a href="{% url 'planning:board_view' %}" class="btn"
style="padding: 0.5rem 1rem; border-radius: 8px; margin-right: 1rem; background: var(--surface-color); color: var(--text-color); border: 1px solid var(--primary-color);">View
Board</a>
{% if is_developer %}
<button class="btn" id="openAddModalBtn" style="padding: 0.5rem 1rem; border-radius: 8px;">Add Item</button>
{% endif %}
</div>
</div>
<form method="get" action="{% url 'planning:backlog_view' %}" style="margin-bottom: 2rem; display: flex; gap: 1rem; align-items: center;">
<label for="status" style="color: var(--text-muted);">Filter by Status:</label>
<select name="status" id="status" class="form-control" style="width: 200px; margin-bottom: 0;" onchange="this.form.submit()">
<form method="get" action="{% url 'planning:backlog_view' %}"
style="margin-bottom: 2rem; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;">
<div style="display: flex; gap: 0.5rem; align-items: center;">
<label for="status" style="color: var(--text-muted);">Status:</label>
<select name="status" id="status" class="form-control" style="width: 150px; margin-bottom: 0;"
onchange="this.form.submit()">
<option value="">All</option>
{% for status_value, status_label in statuses %}
<option value="{{ status_value }}" {% if request.GET.status == status_value %}selected{% endif %}>{{ status_label }}</option>
<option value="{{ status_value }}" {% if request.GET.status == status_value %}selected{% endif %}>{{
status_label }}</option>
{% endfor %}
</select>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<label for="contract" style="color: var(--text-muted);">Contract:</label>
<select name="contract" id="contract" class="form-control" style="width: 200px; margin-bottom: 0;"
onchange="this.form.submit()">
<option value="">All</option>
{% for contract in contracts %}
<option value="{{ contract.id }}" {% if request.GET.contract == contract.id|stringformat:"i" %}selected{% endif %}>{{ contract.name }}</option>
{% endfor %}
</select>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<label for="charge_number" style="color: var(--text-muted);">Charge Number:</label>
<select name="charge_number" id="charge_number" class="form-control" style="width: 200px; margin-bottom: 0;"
onchange="this.form.submit()">
<option value="">All</option>
{% for cn in charge_numbers %}
<option value="{{ cn.id }}" {% if request.GET.charge_number == cn.id|stringformat:"i" %}selected{% endif %}>{{ cn }}</option>
{% endfor %}
</select>
</div>
<a href="{% url 'planning:backlog_view' %}" class="btn"
style="padding: 0.5rem 1rem; border-radius: 8px; background: transparent; border: 1px solid var(--text-muted); color: var(--text-muted);">Clear
Filters</a>
</form>
<div class="table-responsive" style="background: var(--surface-color); border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05); padding: 1rem;">
<div class="table-responsive"
style="background: var(--surface-color); border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05); padding: 1rem;">
<table class="table">
<thead>
<tr>
@@ -38,10 +72,21 @@
</thead>
<tbody>
{% for item in items %}
<tr style="cursor: pointer; transition: background 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'" onclick="showItemDetails({{ item.id }})">
<tr style="cursor: pointer; transition: background 0.2s;"
onmouseover="this.style.background='rgba(255,255,255,0.05)'"
onmouseout="this.style.background='transparent'" onclick="showItemDetails({{ item.id }})">
<td style="font-weight: 500; color: white;">{{ item.title }}</td>
<td>{% if item.charge_number %}<span class="badge" style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">{{ item.charge_number.contract.name }} - {{ item.charge_number.slug }} ({{ item.charge_number.get_percent_complete|floatformat:0 }}%)</span>{% else %}-{% endif %}</td>
<td><span class="badge" style="border: 1px solid var(--primary-color);">{{ item.get_status_display }}</span></td>
<td>
{% if item.charge_number %}<span class="badge"
style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">{{
item.charge_number }} ({{ item.charge_number.get_percent_complete|floatformat:0
}}%)</span>
{% else %}
-
{% endif %}
</td>
<td><span class="badge" style="border: 1px solid var(--primary-color);">
{{ item.get_status_display }}</span></td>
<td style="color: var(--text-muted); font-size: 0.9rem;">{{ item.created_at|date:"M d, Y" }}</td>
<td style="color: var(--text-muted); font-size: 0.9rem;">{{ item.updated_at|date:"M d, Y" }}</td>
</tr>

View File

@@ -5,112 +5,129 @@
{% block content %}
<style>
/* Custom Kanban styles to match the cyber aesthetic */
.kanban-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
padding: 2rem 0;
align-items: start;
}
.kanban-column {
background: rgba(26, 26, 26, 0.6);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 1.5rem;
min-height: 500px;
transition: border-color 0.3s;
}
.kanban-column h3 {
margin-bottom: 1rem;
color: var(--primary-color);
text-transform: uppercase;
font-size: 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.kanban-items {
min-height: 400px; /* For drop zone */
}
.kanban-item {
background: var(--surface-color);
border: 1px solid rgba(0, 243, 255, 0.1);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
cursor: grab;
transition: transform 0.2s, box-shadow 0.2s;
}
.kanban-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 243, 255, 0.2);
border-color: var(--primary-color);
}
.kanban-item:active {
cursor: grabbing;
}
.kanban-item-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: white;
}
.kanban-item-desc {
font-size: 0.85rem;
color: var(--text-muted);
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.8);
backdrop-filter: blur(5px);
}
.modal-content {
background-color: var(--surface-color);
margin: 10% auto;
padding: 2rem;
border: 1px solid var(--primary-color);
border-radius: 12px;
width: 90%;
max-width: 600px;
color: var(--text-color);
box-shadow: 0 0 30px rgba(0, 243, 255, 0.2);
}
.close {
color: var(--text-muted);
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: var(--secondary-color);
}
.badge {
background: rgba(255, 255, 255, 0.1);
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
color: white;
}
@media (max-width: 968px) {
/* Custom Kanban styles to match the cyber aesthetic */
.kanban-board {
grid-template-columns: 1fr;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
padding: 2rem 0;
align-items: start;
}
.kanban-column {
background: rgba(26, 26, 26, 0.6);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 1.5rem;
min-height: 500px;
transition: border-color 0.3s;
}
.kanban-column h3 {
margin-bottom: 1rem;
color: var(--primary-color);
text-transform: uppercase;
font-size: 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.kanban-items {
min-height: 400px;
/* For drop zone */
}
.kanban-item {
background: var(--surface-color);
border: 1px solid rgba(0, 243, 255, 0.1);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
cursor: grab;
transition: transform 0.2s, box-shadow 0.2s;
}
.kanban-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 243, 255, 0.2);
border-color: var(--primary-color);
}
.kanban-item:active {
cursor: grabbing;
}
.kanban-item-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: white;
}
.kanban-item-desc {
font-size: 0.85rem;
color: var(--text-muted);
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
}
.modal-content {
background-color: var(--surface-color);
margin: 10% auto;
padding: 2rem;
border: 1px solid var(--primary-color);
border-radius: 12px;
width: 90%;
max-width: 600px;
color: var(--text-color);
box-shadow: 0 0 30px rgba(0, 243, 255, 0.2);
}
.close {
color: var(--text-muted);
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: var(--secondary-color);
}
.badge {
background: rgba(255, 255, 255, 0.1);
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
color: white;
}
@media (max-width: 968px) {
.kanban-board {
grid-template-columns: 1fr;
}
}
}
</style>
<div class="container section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<h2 class="section-title" style="margin-bottom: 0;">Planning Board</h2>
<div>
<a href="{% url 'planning:backlog_view' %}" class="btn" style="padding: 0.5rem 1rem; border-radius: 8px; margin-right: 1rem; background: var(--surface-color); color: var(--text-color); border: 1px solid var(--primary-color);">View Backlog</a>
<a href="{% url 'planning:backlog_view' %}" class="btn"
style="padding: 0.5rem 1rem; border-radius: 8px; margin-right: 1rem; background: var(--surface-color); color: var(--text-color); border: 1px solid var(--primary-color);">View
Backlog</a>
{% if is_developer %}
<button class="btn" id="openAddModalBtn">Add Item</button>
{% endif %}
@@ -123,10 +140,13 @@
<h3>Todo <span class="badge">{{ todo_items.count }}</span></h3>
<div class="kanban-items" data-status="TODO">
{% for item in todo_items %}
<div class="kanban-item" draggable="{% if is_developer %}true{% else %}false{% endif %}" data-id="{{ item.id }}">
<div class="kanban-item" draggable="{% if is_developer %}true{% else %}false{% endif %}"
data-id="{{ item.id }}">
<div class="kanban-item-title">{{ item.title }}</div>
{% if item.charge_number %}
<div style="margin-bottom: 0.5rem;"><span class="badge" style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">{{ item.charge_number.contract.name }} ({{ item.charge_number.slug }})</span></div>
<div style="margin-bottom: 0.5rem;"><span class="badge"
style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">
{{ item.charge_number.contract.name }} ({{ item.charge_number }})</span></div>
{% endif %}
{% if item.description %}
<div class="kanban-item-desc">{{ item.description|truncatechars:50 }}</div>
@@ -141,10 +161,13 @@
<h3>In Progress <span class="badge">{{ in_progress_items.count }}</span></h3>
<div class="kanban-items" data-status="IN_PROGRESS">
{% for item in in_progress_items %}
<div class="kanban-item" draggable="{% if is_developer %}true{% else %}false{% endif %}" data-id="{{ item.id }}">
<div class="kanban-item" draggable="{% if is_developer %}true{% else %}false{% endif %}"
data-id="{{ item.id }}">
<div class="kanban-item-title">{{ item.title }}</div>
{% if item.charge_number %}
<div style="margin-bottom: 0.5rem;"><span class="badge" style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">{{ item.charge_number.contract.name }} ({{ item.charge_number.slug }})</span></div>
<div style="margin-bottom: 0.5rem;"><span class="badge"
style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">
{{ item.charge_number.contract.name }} ({{ item.charge_number }})</span></div>
{% endif %}
{% if item.description %}
<div class="kanban-item-desc">{{ item.description|truncatechars:50 }}</div>
@@ -159,10 +182,13 @@
<h3>Done <span class="badge">{{ done_items.count }}</span></h3>
<div class="kanban-items" data-status="DONE">
{% for item in done_items %}
<div class="kanban-item" draggable="{% if is_developer %}true{% else %}false{% endif %}" data-id="{{ item.id }}">
<div class="kanban-item" draggable="{% if is_developer %}true{% else %}false{% endif %}"
data-id="{{ item.id }}">
<div class="kanban-item-title">{{ item.title }}</div>
{% if item.charge_number %}
<div style="margin-bottom: 0.5rem;"><span class="badge" style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">{{ item.charge_number.contract.name }} ({{ item.charge_number.slug }})</span></div>
<div style="margin-bottom: 0.5rem;"><span class="badge"
style="background: rgba(188, 19, 254, 0.2); color: var(--secondary-color); border: 1px solid var(--secondary-color);">
{{ item.charge_number.contract.name }} ({{ item.charge_number }})</span></div>
{% endif %}
{% if item.description %}
<div class="kanban-item-desc">{{ item.description|truncatechars:50 }}</div>
@@ -186,7 +212,8 @@
<input type="text" name="title" class="form-control" required>
</div>
<div class="form-group">
<label style="color: var(--text-muted); margin-bottom: 0.5rem; display: block;">Charge Number (QBD)</label>
<label style="color: var(--text-muted); margin-bottom: 0.5rem; display: block;">Charge Number
(QBD)</label>
<select name="charge_number" class="form-control">
<option value="">-- No Charge Number --</option>
{% for cn in qbd_charge_numbers %}
@@ -215,99 +242,99 @@
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Modal logic
const addModal = document.getElementById('addItemModal');
const openAddBtn = document.getElementById('openAddModalBtn');
const closeAddBtn = document.getElementById('closeAddModal');
document.addEventListener('DOMContentLoaded', () => {
// Modal logic
const addModal = document.getElementById('addItemModal');
const openAddBtn = document.getElementById('openAddModalBtn');
const closeAddBtn = document.getElementById('closeAddModal');
if (openAddBtn) {
openAddBtn.onclick = () => addModal.style.display = "block";
}
if (closeAddBtn) {
closeAddBtn.onclick = () => addModal.style.display = "none";
}
if (openAddBtn) {
openAddBtn.onclick = () => addModal.style.display = "block";
}
if (closeAddBtn) {
closeAddBtn.onclick = () => addModal.style.display = "none";
}
const detailModal = document.getElementById('itemDetailModal');
const closeDetailBtn = document.getElementById('closeDetailModal');
const detailContent = document.getElementById('itemDetailContent');
const detailModal = document.getElementById('itemDetailModal');
const closeDetailBtn = document.getElementById('closeDetailModal');
const detailContent = document.getElementById('itemDetailContent');
closeDetailBtn.onclick = () => detailModal.style.display = "none";
closeDetailBtn.onclick = () => detailModal.style.display = "none";
window.onclick = (event) => {
if (event.target == addModal) addModal.style.display = "none";
if (event.target == detailModal) detailModal.style.display = "none";
}
window.onclick = (event) => {
if (event.target == addModal) addModal.style.display = "none";
if (event.target == detailModal) detailModal.style.display = "none";
}
// Drag and Drop Logic
const items = document.querySelectorAll('.kanban-item');
const dropzones = document.querySelectorAll('.kanban-items');
// Drag and Drop Logic
const items = document.querySelectorAll('.kanban-item');
const dropzones = document.querySelectorAll('.kanban-items');
items.forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', e.target.closest('.kanban-item').dataset.id);
setTimeout(() => e.target.closest('.kanban-item').style.opacity = '0.5', 0);
items.forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', e.target.closest('.kanban-item').dataset.id);
setTimeout(() => e.target.closest('.kanban-item').style.opacity = '0.5', 0);
});
item.addEventListener('dragend', (e) => {
e.target.closest('.kanban-item').style.opacity = '1';
});
item.addEventListener('click', (e) => {
const itemId = item.dataset.id;
fetch(`/planning/item/${itemId}/`)
.then(res => res.text())
.then(html => {
detailContent.innerHTML = html;
detailModal.style.display = "block";
});
});
});
item.addEventListener('dragend', (e) => {
e.target.closest('.kanban-item').style.opacity = '1';
});
dropzones.forEach(zone => {
zone.addEventListener('dragover', (e) => {
e.preventDefault(); // Necessary to allow dropping
zone.parentElement.style.borderColor = 'var(--primary-color)';
});
item.addEventListener('click', (e) => {
const itemId = item.dataset.id;
fetch(`/planning/item/${itemId}/`)
.then(res => res.text())
.then(html => {
detailContent.innerHTML = html;
detailModal.style.display = "block";
});
zone.addEventListener('dragleave', (e) => {
zone.parentElement.style.borderColor = 'rgba(255, 255, 255, 0.05)';
});
zone.addEventListener('drop', (e) => {
e.preventDefault();
zone.parentElement.style.borderColor = 'rgba(255, 255, 255, 0.05)';
const id = e.dataTransfer.getData('text/plain');
if (!id) return;
const draggable = document.querySelector(`.kanban-item[data-id='${id}']`);
if (!draggable) return;
zone.appendChild(draggable);
const newStatus = zone.dataset.status;
// Send AJAX update
fetch(`/planning/item/${id}/update/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify({
status: newStatus,
order: Array.from(zone.children).indexOf(draggable)
})
}).then(res => res.json())
.then(data => {
if (data.status !== 'success') {
alert('Failed to update status');
} else {
// Update counts if necessary, though reloading or dynamic updates are better.
// Simple approach: user just sees it moved.
}
});
});
});
});
dropzones.forEach(zone => {
zone.addEventListener('dragover', (e) => {
e.preventDefault(); // Necessary to allow dropping
zone.parentElement.style.borderColor = 'var(--primary-color)';
});
zone.addEventListener('dragleave', (e) => {
zone.parentElement.style.borderColor = 'rgba(255, 255, 255, 0.05)';
});
zone.addEventListener('drop', (e) => {
e.preventDefault();
zone.parentElement.style.borderColor = 'rgba(255, 255, 255, 0.05)';
const id = e.dataTransfer.getData('text/plain');
if(!id) return;
const draggable = document.querySelector(`.kanban-item[data-id='${id}']`);
if (!draggable) return;
zone.appendChild(draggable);
const newStatus = zone.dataset.status;
// Send AJAX update
fetch(`/planning/item/${id}/update/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify({
status: newStatus,
order: Array.from(zone.children).indexOf(draggable)
})
}).then(res => res.json())
.then(data => {
if(data.status !== 'success') {
alert('Failed to update status');
} else {
// Update counts if necessary, though reloading or dynamic updates are better.
// Simple approach: user just sees it moved.
}
});
});
});
});
</script>
{% endblock %}

View File

@@ -4,7 +4,8 @@
<input type="hidden" name="next" id="editItemNext">
<div class="form-group" style="margin-bottom: 1.5rem;">
<label style="color: var(--text-muted); display: block; margin-bottom: 0.5rem;">Title</label>
<input type="text" name="title" class="form-control" value="{{ item.title }}" style="font-size: 1.2rem; font-weight: bold; color: var(--primary-color);" required>
<input type="text" name="title" class="form-control" value="{{ item.title }}"
style="font-size: 1.2rem; font-weight: bold; color: var(--primary-color);" required>
</div>
<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem;">
@@ -22,7 +23,7 @@
<select name="charge_number" class="form-control">
<option value="">-- No Charge Number --</option>
{% for cn in qbd_charge_numbers %}
<option value="{{ cn.id }}" {% if item.charge_number_id == cn.id %}selected{% endif %}>{{ cn.contract.name }} - {{ cn.slug }}</option>
<option value="{{ cn.id }}" {% if item.charge_number_id == cn.id %}selected{% endif %}>{{ cn }}</option>
{% endfor %}
</select>
</div>
@@ -41,7 +42,10 @@
<div style="display: flex; justify-content: space-between;">
<button type="submit" class="btn" style="padding: 0.8rem 2rem;">Save Changes</button>
<button type="button" class="btn" style="background: transparent; border: 1px solid #ff4444; color: #ff4444; padding: 0.8rem 2rem;" onclick="if(confirm('Are you sure you want to delete this ticket?')) document.getElementById('deleteItemForm').submit();">Delete Ticket</button>
<button type="button" class="btn"
style="background: transparent; border: 1px solid #ff4444; color: #ff4444; padding: 0.8rem 2rem;"
onclick="if(confirm('Are you sure you want to delete this ticket?')) document.getElementById('deleteItemForm').submit();">Delete
Ticket</button>
</div>
</form>
@@ -54,21 +58,52 @@
// Ensure form redirects to the current page upon save or delete
document.getElementById('editItemNext').value = window.location.pathname;
document.getElementById('deleteItemNext').value = window.location.pathname;
// Handle AJAX submission for editing
const editForm = document.querySelector('form[action*="edit"]');
if (editForm) {
editForm.onsubmit = (e) => {
e.preventDefault();
const formData = new FormData(editForm);
fetch(editForm.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': formData.get('csrf_token')
}
}).then(res => res.json())
.then(data => {
if (data.status === 'success') {
// Close modal and refresh the current page to show updates
// (Alternatively, we could update the item in the DOM dynamically)
location.reload();
} else {
alert('Error updating item: ' + (data.message || 'Unknown error'));
}
});
};
}
</script>
{% else %}
<h2 style="color: var(--primary-color); margin-bottom: 1rem;">{{ item.title }}</h2>
<div style="margin-bottom: 1.5rem; display: flex; gap: 1rem; align-items: center;">
<span style="background: var(--surface-color); padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.8rem; border: 1px solid var(--secondary-color);">{{ item.get_status_display }}</span>
<span
style="background: var(--surface-color); padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.8rem; border: 1px solid var(--secondary-color);">{{
item.get_status_display }}</span>
{% if item.charge_number %}
<span style="background: rgba(188, 19, 254, 0.1); padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.8rem; border: 1px solid var(--secondary-color); color: var(--secondary-color);">Charge Number: <strong>{{ item.charge_number.contract.name }} ({{ item.charge_number.slug }})</strong> ({{ item.charge_number.get_percent_complete|floatformat:0 }}% Complete)</span>
<span
style="background: rgba(188, 19, 254, 0.1); padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.8rem; border: 1px solid var(--secondary-color); color: var(--secondary-color);">Charge
Number: <strong>{{ item.charge_number.contract.name }} ({{ item.charge_number.slug }})</strong> ({{
item.charge_number.get_percent_complete|floatformat:0 }}% Complete)</span>
{% endif %}
</div>
<div style="color: var(--text-color); line-height: 1.6; white-space: pre-wrap;">
{% if item.description %}
{{ item.description }}
{% else %}
<span style="color: var(--text-muted); font-style: italic;">No description provided.</span>
{% endif %}
{% if item.description %}
{{ item.description }}
{% else %}
<span style="color: var(--text-muted); font-style: italic;">No description provided.</span>
{% endif %}
</div>
<hr style="border: 0; border-top: 1px solid rgba(255,255,255,0.1); margin: 2rem 0 1rem;">
<div style="font-size: 0.8rem; color: var(--text-muted);">

View File

@@ -7,25 +7,25 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}AI ML Operations, LLC{% endblock %}</title>
<meta name="description"
content="{% block meta_description %}AI ML Operations, LLC provides cutting-edge AI, Machine Learning, and Web Development solutions.{% endblock %}">
content="{% block meta_description %}AI ML Operations, LLC — forward-deployed AI engineering. We embed with your team to build and deploy custom agentic workflows and production AI systems.{% endblock %}">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:title" content="{% block og_title %}AI ML Operations, LLC{% endblock %}">
<meta property="og:description"
content="{% block og_description %}AI ML Operations, LLC provides cutting-edge AI, Machine Learning, and Web Development solutions.{% endblock %}">
content="{% block og_description %}Forward-deployed AI engineering — custom agentic workflows deployed in your environment.{% endblock %}">
<meta property="og:image"
content="{% block og_image %}{{ request.scheme }}://{{ request.get_host }}{% static 'public/img/favicon.jpg' %}{% endblock %}">
content="{% block og_image %}{{ request.scheme }}://{{ request.get_host }}{% static 'public/img/logo.png' %}{% endblock %}">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="{{ request.build_absolute_uri }}">
<meta property="twitter:title" content="{% block twitter_title %}AI ML Operations, LLC{% endblock %}">
<meta property="twitter:description"
content="{% block twitter_description %}AI ML Operations, LLC provides cutting-edge AI, Machine Learning, and Web Development solutions.{% endblock %}">
content="{% block twitter_description %}Forward-deployed AI engineering — custom agentic workflows deployed in your environment.{% endblock %}">
<meta property="twitter:image"
content="{% block twitter_image %}{{ request.scheme }}://{{ request.get_host }}{% static 'public/img/favicon.jpg' %}{% endblock %}">
content="{% block twitter_image %}{{ request.scheme }}://{{ request.get_host }}{% static 'public/img/logo.png' %}{% endblock %}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -34,7 +34,7 @@
<!-- Custom CSS -->
<link href="{% static 'public/css/style.css' %}" rel="stylesheet">
<link rel="icon" type="image/xicon" href="{% static 'public/img/favicon.jpg' %}">
<link rel="icon" type="image/png" href="{% static 'public/img/favicon.png' %}">
{% if not debug %}
<script async defer src="https://tianji.aimloperations.com/tracker.js"
@@ -47,8 +47,7 @@
<!-- Navigation Bar -->
<nav>
<a href="{% url 'public_index' %}" class="brand-logo">
AI ML<span class="text-cyber-cyan"> OPERATIONS</span>
<img src="{% static 'public/img/logo.png' %}" alt="AI ML Operations" class="brand-logo-img" width="243" height="28">
</a>
<button class="mobile-menu-btn" aria-label="Menu"></button>
<ul class="nav-links">
@@ -56,32 +55,37 @@
class="{% if request.resolver_match.url_name == 'public_index' %}active{% endif %}">Home</a></li>
<li class="dropdown">
<a href="#"
class="{% if request.resolver_match.url_name in 'ai_education,ai_sensor,bot,chat,computers,file_hosting,ml_model,web_design' %}active{% endif %}">Services</a>
class="{% if request.resolver_match.url_name in 'forward_deployed,ai_education,ai_sensor,bot,chat,computers,file_hosting,ml_model,web_design' %}active{% endif %}">Services</a>
<ul class="dropdown-content">
<li><a href="{% url 'ai_education' %}">Education</a></li>
<li><a href="{% url 'ai_sensor' %}">Sensors</a></li>
<li><a href="{% url 'bot' %}">Bots</a></li>
<li><a href="{% url 'forward_deployed' %}">Forward-Deployed AI</a></li>
<li><a href="{% url 'bot' %}">AI Agents</a></li>
<li><a href="{% url 'ml_model' %}">ML Models</a></li>
<li><a href="{% url 'chat' %}">Chat</a></li>
<li><a href="{% url 'ai_sensor' %}">Sensors</a></li>
<li><a href="{% url 'ai_education' %}">Education</a></li>
<li><a href="{% url 'computers' %}">Hardware</a></li>
<li><a href="{% url 'file_hosting' %}">Hosting</a></li>
<li><a href="{% url 'ml_model' %}">ML Models</a></li>
<li><a href="{% url 'web_design' %}">Web Design</a></li>
</ul>
</li>
<li><a href="{% url 'contact' %}"
class="{% if request.resolver_match.url_name == 'contact' %}active{% endif %}">Contact</a></li>
{% if user.is_authenticated %}
<li><a href="{% url 'planning:board_view' %}" class="{% if 'planning' in request.path %}active{% endif %}">Planning</a></li>
<li><a href="{% url 'financial_index' %}" class="{% if 'financial' in request.path %}active{% endif %}">Financials</a></li>
<li><a href="{% url 'planning:board_view' %}"
class="{% if 'planning' in request.path %}active{% endif %}">Planning</a></li>
<li><a href="{% url 'financial_index' %}"
class="{% if 'financial' in request.path %}active{% endif %}">Financials</a></li>
<li class="dropdown" id="user-profile-dropdown">
<a href="#" class="profile-icon-link" title="{{ user.get_full_name|default:user.username }}">
<svg class="profile-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4"/>
<path d="M4 21v-1a6 6 0 0 1 12 0v1"/>
<svg class="profile-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4" />
<path d="M4 21v-1a6 6 0 0 1 12 0v1" />
</svg>
</a>
<ul class="dropdown-content profile-dropdown-content">
<li class="profile-name-item">{{ user.get_full_name|default:user.username }}</li>
<li><a href="{% url 'change_password' %}">Change Password</a></li>
<li>
<form action="{% url 'logout' %}" method="post" style="margin: 0;">
{% csrf_token %}
@@ -91,7 +95,9 @@
</ul>
</li>
{% else %}
<li><a href="{% url 'login' %}" class="btn text-cyber-cyan" style="border: 1px solid var(--primary-color); padding: 0.4rem 1rem; border-radius: 4px; margin-left: 1rem;">Sign In</a></li>
<li><a href="{% url 'login' %}" class="btn"
style="border: 1px solid var(--primary-color); padding: 0.4rem 1rem; border-radius: 4px; margin-left: 1rem; color: #000;">Sign
In</a></li>
{% endif %}
</ul>
</nav>
@@ -104,8 +110,9 @@
<!-- Footer -->
<footer class="footer">
<div class="container">
<p class="footer-text">&copy; 2023 -
<script>document.write(new Date().getFullYear());</script> AI ML Operations, LLC. All rights reserved.
<p class="footer-text">AI ML Operations, LLC — Forward-deployed AI engineering.</p>
<p class="footer-text footer-text--muted">&copy; 2023 -
<script>document.write(new Date().getFullYear());</script> All rights reserved.
</p>
</div>
</footer>

View File

@@ -1,55 +1,97 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Agentic Bots - AI ML Operations, LLC{% endblock %}
{% block meta_description %}Intelligent agentic bots for automation and engagement. AI ML Operations, LLC develops
custom bots for Telegram and other platforms.{% endblock %}
{% block title %}AI Agents &amp; Automation - AI ML Operations, LLC{% endblock %}
{% block meta_description %}Custom proprietary AI agents for workflow automation and conversations. Web-hosted, scalable, with real-time performance analytics by AI ML Operations, LLC.{% endblock %}
{% block og_title %}AI Agents &amp; Automation - AI ML Operations, LLC{% endblock %}
{% block og_description %}Proprietary web-hosted AI agents with scalable hosting and real-time performance analytics.{% endblock %}
{% block content %}
<!-- Hero Section -->
<div class="hero-section" style="height: 40vh; min-height: 300px;">
<div class="hero-section" style="height: 45vh; min-height: 320px;">
<div class="hero-content">
<h1 class="hero-title">Agentic Bots</h1>
<p class="hero-subtitle">Intelligent Bots for Automation and Engagement</p>
<h1 class="hero-title">AI Agents &amp; Automation</h1>
<p class="hero-subtitle hero-subtitle--wide">
Proprietary agents built for your workflows and conversations—hosted, scalable, and observable.
</p>
<div class="hero-cta">
<a href="{% url 'contact' %}?subject=AI%20Agents" class="btn">Build Your Agent</a>
<a href="{% url 'forward_deployed' %}" class="btn btn-secondary">Forward-Deployed AI</a>
</div>
</div>
</div>
<!-- About Agentic Bots Section -->
<div class="section">
<div class="container">
<h2 class="section-title">About Agentic Bots</h2>
<p style="text-align: center; max-width: 800px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
At AI ML Operations, we create intelligent, agentic bots designed to automate tasks, engage users, and streamline
workflows. Our bots currently integrate seamlessly with Telegram, but we can customize them to suit your specific
platform or needs. Whether you need a bot for customer support, data collection, or process automation, we deliver
solutions that are reliable, scalable, and easy to use.
<h2 class="section-title">About Our AI Agents</h2>
<p style="text-align: center; max-width: 820px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
We design and deploy proprietary AI agents that automate tasks, support customers, and orchestrate multi-step workflows.
Agents are web-hosted and built to scale with your business. As part of our forward-deployed engagements—or as standalone
products—we integrate with your platforms (including Telegram and custom channels) and give you visibility into how
agents perform in the real world.
</p>
</div>
</div>
<!-- Features Section -->
<div class="section" style="background: var(--surface-color);">
<div class="container">
<h2 class="section-title">What We Offer</h2>
<div class="card-grid">
<div class="card" style="text-align: center;">
<h5 class="card-title">Custom Bot Development</h5>
<p class="card-text">Tailored bots designed to meet your unique business requirements.</p>
<h5 class="card-title">Custom Agent Development</h5>
<p class="card-text">Agents tailored to your data, policies, and workflows—not generic chatbots.</p>
</div>
<div class="card" style="text-align: center;">
<h5 class="card-title">Web-Hosted &amp; Scalable</h5>
<p class="card-text">Production hosting and maintenance so your agents stay available 24/7.</p>
</div>
<div class="card" style="text-align: center;">
<h5 class="card-title">Analytics &amp; Insights</h5>
<p class="card-text">Real-time performance metrics and conversation analytics so you can improve outcomes continuously.</p>
</div>
<div class="card" style="text-align: center;">
<h5 class="card-title">Platform Integration</h5>
<p class="card-text">Bots that integrate with Telegram or other platforms of your choice.</p>
</div>
<div class="card" style="text-align: center;">
<h5 class="card-title">Hosting & Maintenance</h5>
<p class="card-text">We host and maintain your bots, ensuring they run smoothly 24/7.</p>
<p class="card-text">Telegram, web embeds, APIs, and custom channels connected to your operations.</p>
</div>
</div>
</div>
</div>
<!-- Applications Section -->
<div class="section">
<div class="container">
<h2 class="section-title">How We Build</h2>
<p style="text-align: center; max-width: 820px; margin: 0 auto 2.5rem; color: var(--text-muted); font-size: 1.05rem;">
Robust agentic orchestration—not simple API wrappers. Representative stack:
</p>
<div class="card-grid card-grid--stack">
<div class="card">
<span class="card-title">LangChain &amp; LangGraph</span>
<p class="card-text">Stateful agent workflows and multi-step orchestration.</p>
</div>
<div class="card">
<span class="card-title">LangSmith</span>
<p class="card-text">Tracing, evaluation, and observability for LLM pipelines.</p>
</div>
<div class="card">
<span class="card-title">Pydantic AI</span>
<p class="card-text">Type-safe agents with structured inputs and outputs.</p>
</div>
<div class="card">
<span class="card-title">Pipecat AI</span>
<p class="card-text">Real-time voice and multimodal conversation pipelines.</p>
</div>
<div class="card">
<span class="card-title">Model Context Protocol (MCP)</span>
<p class="card-text">Standardized tool and data access for agent systems.</p>
</div>
<div class="card">
<span class="card-title">Local &amp; Self-Hosted LLM Inference</span>
<p class="card-text">On-prem and air-gapped deployment for sensitive workloads.</p>
</div>
</div>
</div>
</div>
<div class="section" style="background: var(--surface-color);">
<div class="container">
<h2 class="section-title">Applications</h2>
<div class="card-grid">
@@ -57,30 +99,29 @@ custom bots for Telegram and other platforms.{% endblock %}
<img src="{% static 'public/img/bot/card-1.jpg' %}" alt="Customer Support"
style="width: 100%; border-radius: 8px; margin-bottom: 1rem;">
<span class="card-title">Customer Support</span>
<p class="card-text">Automate responses and provide instant support to your customers.</p>
<p class="card-text">Automate responses and escalate intelligently when human judgment is required.</p>
</div>
<div class="card">
<img src="{% static 'public/img/bot/card-2.jpg' %}" alt="Data Collection"
style="width: 100%; border-radius: 8px; margin-bottom: 1rem;">
<span class="card-title">Data Collection</span>
<p class="card-text">Gather and organize data efficiently using intelligent bots.</p>
<p class="card-text">Gather and structure data from conversations and workflows automatically.</p>
</div>
<div class="card">
<img src="{% static 'public/img/bot/card-3.jpg' %}" alt="Process Automation"
style="width: 100%; border-radius: 8px; margin-bottom: 1rem;">
<span class="card-title">Process Automation</span>
<p class="card-text">Streamline workflows and reduce manual effort with automated bots.</p>
<p class="card-text">Multi-step agent workflows that reduce manual effort across departments.</p>
</div>
</div>
</div>
</div>
<!-- Call to Action Section -->
<div class="section" style="text-align: center;">
<div class="container">
<h2 class="section-title">Ready to Automate?</h2>
<p class="hero-subtitle" style="margin-bottom: 2rem;">Contact us today to create your custom agentic bot.</p>
<a href="{% url 'contact' %}" class="btn">Get Started</a>
<p class="hero-subtitle" style="margin-bottom: 2rem;">Tell us the workflow you want an agent to own—we will design, deploy, and monitor it.</p>
<a href="{% url 'contact' %}?subject=AI%20Agents" class="btn">Get Started</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,74 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Change Password - AI ML Operations, LLC{% endblock %}
{% block content %}
<div class="section">
<div class="container" style="max-width: 600px; margin: 0 auto; padding: 4rem 1rem;">
<h2 class="section-title">Change Password</h2>
<div class="card"
style="padding: 2rem; background: var(--card-bg); border-radius: 8px; border: 1px solid var(--border-color);">
{% if messages %}
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}"
style="margin-bottom: 1rem; padding: 1rem; border-radius: 4px; {% if message.tags == 'error' %}background-color: rgba(255, 0, 0, 0.1); color: #ff4444; border: 1px solid #ff4444;{% else %}background-color: rgba(0, 255, 255, 0.1); color: var(--cyber-cyan); border: 1px solid var(--cyber-cyan);{% endif %}">
{{ message }}
</div>
{% endfor %}
{% endif %}
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group" style="margin-bottom: 1.5rem;">
<label for="{{ field.id_for_label }}"
style="display: block; margin-bottom: 0.5rem; color: var(--text-color);">{{ field.label
}}</label>
{{ field }}
{% if field.help_text %}
<small class="form-text"
style="display: block; margin-top: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">{{
field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<p class="error-message" style="margin-top: 0.25rem; color: #ff4444; font-size: 0.85rem;">{{ error
}}</p>
{% endfor %}
</div>
{% endfor %}
<div style="margin-top: 2rem;">
<button type="submit" class="btn"
style="width: 100%; padding: 0.75rem; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; background: linear-gradient(90deg, #00d4ff 0%, #0099ff 100%);">Update
Password</button>
</div>
</form>
<div style="margin-top: 1.5rem; text-align: center;">
<a href="{% url 'public_index' %}"
style="color: var(--text-muted); text-decoration: none; font-size: 0.9rem;">&larr; Back to Home</a>
</div>
</div>
</div>
</div>
<style>
.form-group input {
width: 100%;
padding: 0.75rem;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 4px;
color: white;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: var(--cyber-cyan);
box-shadow: 0 0 5px rgba(0, 255, 255, 0.3);
}
</style>
{% endblock %}

View File

@@ -2,8 +2,7 @@
{% load static %}
{% block title %}Secure AI Chat - AI ML Operations, LLC{% endblock %}
{% block meta_description %}Secure, private AI chat solution by AI ML Operations, LLC. Host locally, analyze files
securely, and keep your data safe.{% endblock %}
{% block meta_description %}Secure, hosted AI chat by AI ML Operations, LLC—an example of scalable, privacy-first AI products with production deployment.{% endblock %}
{% block content %}
@@ -12,8 +11,8 @@ securely, and keep your data safe.{% endblock %}
<div class="product-hero-content">
<h1 class="product-hero-title">Your Private, Secure AI Workspace</h1>
<p class="product-hero-subtitle">
Experience the power of a locally hosted Large Language Model.
Keep your data safe, analyze files securely, and never worry about third-party access.
A production-ready, hosted LLM workspace—built the way we deliver forward-deployed systems: scalable, observable, and
designed to keep your data under your control.
</p>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<a href="https://chat.aimloperations.com" class="btn">Launch Chat</a>

View File

@@ -2,15 +2,14 @@
{% load static %}
{% block title %}Contact Us - AI ML Operations, LLC{% endblock %}
{% block meta_description %}Get in touch with AI ML Operations, LLC. Contact us for AI solutions, web design, hosting,
and more. We'd love to hear from you.{% endblock %}
{% block meta_description %}Tell AI ML Operations about your operational bottleneck. Forward-deployed AI engineering, custom agents, and production ML deployments.{% endblock %}
{% block content %}
<!-- Hero Section -->
<div class="hero-section" style="height: 40vh; min-height: 300px;">
<div class="hero-content">
<h1 class="hero-title">Get in Touch</h1>
<p class="hero-subtitle">We'd love to hear from you.</p>
<p class="hero-subtitle hero-subtitle--wide">Describe the workflow or bottleneck you want to automate—we will help you scope a forward-deployed solution.</p>
</div>
</div>
@@ -46,11 +45,11 @@ and more. We'd love to hear from you.{% endblock %}
</div>
<div class="form-group">
<input type="text" class="form-control" name="subject" placeholder="Subject">
<input type="text" class="form-control" name="subject" id="contact-subject" placeholder="Subject" value="{{ request.GET.subject|default:'' }}">
</div>
<div class="form-group">
<textarea name="message" class="form-control" rows="5" placeholder="Your message"></textarea>
<textarea name="message" class="form-control" rows="5" placeholder="What workflow is costing you the most time? What systems does it touch?"></textarea>
</div>
<div class="form-group">

View File

@@ -0,0 +1,152 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Forward-Deployed AI Engineering - AI ML Operations, LLC{% endblock %}
{% block meta_description %}AI ML Operations embeds with your team to architect, build, and deploy custom AI workflows and agentic automation in your environment—not slide decks.{% endblock %}
{% block og_title %}Forward-Deployed AI Engineering - AI ML Operations, LLC{% endblock %}
{% block og_description %}Embed with your team. Map bottlenecks. Build and deploy production AI workflows in your stack.{% endblock %}
{% block twitter_title %}Forward-Deployed AI Engineering - AI ML Operations, LLC{% endblock %}
{% block twitter_description %}Embed with your team. Map bottlenecks. Build and deploy production AI workflows in your stack.{% endblock %}
{% block content %}
<div class="hero-section" style="height: 50vh; min-height: 360px;">
<div class="hero-content">
<h1 class="hero-title">Forward-Deployed AI</h1>
<p class="hero-subtitle hero-subtitle--wide">
We embed with your team to architect, build, and deploy custom AI workflows that solve real operational bottlenecks.
</p>
<div class="hero-cta">
<a href="{% url 'bot' %}" class="btn btn-secondary">AI Agents &amp; Automation</a>
</div>
</div>
</div>
<div class="section">
<div class="container">
<h2 class="section-title">What Is Forward-Deployed AI?</h2>
<p style="text-align: center; max-width: 820px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
Instead of handing you a strategy deck and walking away, we work inside your organization—onsite or embedded remotely.
We map messy, real-world workflows, integrate with your existing systems, and ship production automation that your team
can run day to day. You get faster time-to-value without hiring a full-time specialized AI engineering team.
</p>
</div>
</div>
<div class="section" style="background: var(--surface-color);">
<div class="container">
<h2 class="section-title">How We Work</h2>
<div class="card-grid card-grid--steps">
<div class="card">
<span class="card-step-num">01</span>
<span class="card-title">Embed</span>
<p class="card-text">Join your team to understand operations, stakeholders, and constraints—not just the tech stack.</p>
</div>
<div class="card">
<span class="card-step-num">02</span>
<span class="card-title">Map</span>
<p class="card-text">Audit workflows, data sources, and integration points. Identify where AI removes friction.</p>
</div>
<div class="card">
<span class="card-step-num">03</span>
<span class="card-title">Build</span>
<p class="card-text">Design agentic workflows, APIs, and orchestration tailored to your environment and security needs.</p>
</div>
<div class="card">
<span class="card-step-num">04</span>
<span class="card-title">Deploy</span>
<p class="card-text">Ship to production with monitoring, documentation, and handoff so the system keeps running after we leave.</p>
</div>
<div class="card">
<span class="card-step-num">05</span>
<span class="card-title">Monitor</span>
<p class="card-text">Track performance, refine workflows, and iterate for continuous improvement.</p>
</div>
<div class="card">
<span class="card-step-num">06</span>
<span class="card-title">Handoff</span>
<p class="card-text">Transfer ownership and documentation to your team for ongoing maintenance.</p>
</div>
</div>
</div>
</div>
<div class="section">
<div class="container">
<h2 class="section-title">What We Deliver</h2>
<div class="card-grid">
<div class="card">
<h5 class="card-title">Workflow Automation</h5>
<p class="card-text">Multi-step agentic pipelines that connect LLMs, tools, and your internal systems—not one-off chat wrappers.</p>
</div>
<div class="card">
<h5 class="card-title">Deep Integrations</h5>
<p class="card-text">CRM, ticketing, databases, document stores, and custom APIs wired into solutions that fit how you already work.</p>
</div>
<div class="card">
<h5 class="card-title">Enterprise-Ready Architecture</h5>
<p class="card-text">Self-hosted and air-gapped options, sensitive-data handling, and architectures designed with compliance-minded teams in mind.</p>
</div>
</div>
</div>
</div>
<div class="section" style="background: var(--surface-color);">
<div class="container">
<h2 class="section-title">Technical Stack</h2>
<p style="text-align: center; max-width: 820px; margin: 0 auto 2.5rem; color: var(--text-muted); font-size: 1.05rem;">
We build robust, observable agent systems—not demos. Representative tools and frameworks we use:
</p>
<div class="card-grid card-grid--stack">
<div class="card">
<span class="card-title">LangChain &amp; LangGraph</span>
<p class="card-text">Stateful agent workflows and multi-step orchestration.</p>
</div>
<div class="card">
<span class="card-title">LangSmith</span>
<p class="card-text">Tracing, evaluation, and observability for LLM pipelines.</p>
</div>
<div class="card">
<span class="card-title">Pydantic AI</span>
<p class="card-text">Type-safe agents with structured inputs and outputs.</p>
</div>
<div class="card">
<span class="card-title">Pipecat AI</span>
<p class="card-text">Real-time voice and multimodal conversation pipelines.</p>
</div>
<div class="card">
<span class="card-title">Model Context Protocol (MCP)</span>
<p class="card-text">Standardized tool and data access for agent systems.</p>
</div>
<div class="card">
<span class="card-title">Local &amp; Self-Hosted LLM Inference</span>
<p class="card-text">On-prem and air-gapped deployment for sensitive workloads.</p>
</div>
<div class="card">
<span class="card-title">n8n</span>
<p class="card-text">Workflow automation connecting APIs, triggers, and internal systems.</p>
</div>
</div>
</div>
</div>
<div class="section">
<div class="container">
<h2 class="section-title">Who This Is For</h2>
<p style="text-align: center; max-width: 820px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
Leaders who have access to LLMs but struggle to wire them into daily operations. Teams suffering from “AI fatigue”—lots of
pilots, little production impact. Organizations that need an engineer in the trenches, not another advisory engagement.
</p>
</div>
</div>
<div class="section" style="text-align: center;">
<div class="container">
<h2 class="section-title">Ready to Deploy?</h2>
<p class="hero-subtitle" style="margin-bottom: 2rem;">Tell us the workflow that is costing you the most time. We will help you automate it.</p>
<div class="hero-cta" style="margin-top: 0;">
<a href="{% url 'contact' %}?subject=Forward-Deployed%20AI" class="btn">Start a Conversation</a>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,143 @@
<!doctype html>
{% load static %}
<html lang="en"">
<head>
<title>AI/ML Operations, LLC</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="icon" type="image/xicon" href="{% static 'public/img/logo.png' %}">
<body>
<header>
<div class="page-header text-light" style="background-image: url({% static 'public/img/background_w_logo.png' %}); background-repeat: no-repeat; background-size: 100% 100%; background-attachment: fixed;" >
<div class="container">
<div class="row" style="text-align: center;">
<div style="height: 60vh; padding: 30vh">
<h1 > </h1>
<!-- <p>Taking AI and ML from concept to Production</p> -->
</div>
</div>
</div>
</div>
</header>
<section class="bg-light text-dark" >
<div class="container" style="text-align: center; height: 20vh; padding: 7vh">
<div class="row">
<h2><u>About Us</u></h2>
<p>At AI/ML Operations, we're are commited to bringing production ready AI and ML to workflows. We have over a decad of experience create both ML models as well as AI algorithms to solve some of the the most demanding problem spaces.</p>
</div>
</div>
</section>
<section class="bg-dark text-light" >
<div class="container text-center">
<h2><u>Services</u></h2>
<div class="row justify-content-start">
<div class="col-4">
<h3>ML Model Creation</h3>
<p>Unleash the power of data with our cutting-edge ML model creation services, tailored to your unique needs. From data preprocessing to model training and deployment, we streamline the process to unlock actionable insights.</p>
</div>
<div class="col-4">
<h3>Pipeline Optimization</h3>
<p>Boost efficiency and productivity with our pipeline optimization solutions. We fine-tune your workflows, automate repetitive tasks, and integrate advanced analytics to optimize your business processes for peak performance.</p>
</div>
<div class="col-4">
<h3>AI Algorithms for Sensors</h3>
<p>Harness the potential of sensor data with our advanced AI algorithms tailored for diverse applications. From real-time processing to post collection data analysis, our algorithms empower you to extract valuable insights, optimize processes, and drive innovation.</p>
</div>
<div class="col-4">
<h3>Web Design</h3>
<p>Elevate your online presence with captivating web design that blends creativity with functionality. Our expert designers craft visually stunning websites that engage your audience and drive conversions, ensuring a seamless user experience across all devices.</p>
</div>
<div class="col-4">
<h3>File Hosting</h3>
<p>Securely store and share your files with ease using our reliable file hosting platform. With robust encryption and flexible access controls, you can confidently manage your data, collaborate seamlessly, and streamline your workflow.</p>
</div>
<div class="col-4">
<h3>App Virtualization</h3>
<p>Transform your software delivery with our app virtualization solutions, enabling seamless access to applications from any device, anywhere. Experience enhanced flexibility, scalability, and security as we optimize your IT infrastructure for the digital age.</p>
</div>
</div>
</div>
</section>
<section class="bg-light text-dark" >
<div class="container" style="text-align: center; height: 20vh; padding: 7vh">
<div class="row">
<h2>Partners</h2>
<div class="col-12">
<img src="{% static 'public/img/ansys_logo.png' %}" alt="Ansys" height="100" />
</div>
</div>
</div>
</section>
<section class="bg-dark text-light">
<div class="container" style="padding-top: 1rem; padding-bottom: 1rem;">
<h2 >Contact Us</h2>
<form action="{% url 'contact' %}" method="post">
{% csrf_token %}
<div class="row">
{% if success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<span class="alert-text"> We'll be in contact shortly!</span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
{% if errors %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<span class="alert-text">{{ errors }}</span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
<!-- <div class="col-md-6">
<p> something</p>
</div>
<div class="col-md-6">
-->
<div class="form-group col-6" style="padding-left:1rem">
<input type="email" class="form-control" name="email" placeholder="Your Email">
</div>
<div class="form-group col-6" style="padding-right:1rem">
<input type="text" class="form-control" name="name" placeholder="Your Name">
</div>
<div class="form-group col-12" style="padding:1rem">
<textarea name="message" type="text" class="form-control" rows="5" placeholder="Your message"></textarea>
</div>
<div class="form-group col-12">
{% if capchaForm %}
{{ capchaForm }}
{% endif %}
</div>
</div>
<input type="submit" class="btn btn-primary" value="Send">
</form>
</div>
</section>
<footer>
<div class="container">
<div class="row" style="padding-top: 1rem;">
<p> &copy; <script>document.write( new Date().getFullYear() );</script> AI/ML Operations, LLC. ALL RIGHTS RESERVED</p>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -18,11 +18,15 @@ supervised, unsupervised, and reinforcement learning.{% endblock %}
<div class="section">
<div class="container">
<h2 class="section-title">About ML Model Creation</h2>
<p style="text-align: center; max-width: 800px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
At AI ML Operations, we specialize in creating custom machine learning models to solve complex problems across
industries. With extensive expertise in supervised, unsupervised, and reinforcement learning, we design models
that deliver accurate, scalable, and actionable insights. Whether you need predictive analytics, pattern
recognition, or decision-making systems, we provide end-to-end solutions tailored to your needs.
<p style="text-align: center; max-width: 820px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
We build custom machine learning models and deploy them inside your pipelines—not as disconnected experiments.
With expertise in supervised, unsupervised, and reinforcement learning, we deliver models that integrate with
forward-deployed engagements: predictive analytics, pattern recognition, and decision systems wired into how you operate.
</p>
<p style="text-align: center; max-width: 820px; margin: 1.5rem auto 0; color: var(--text-muted); font-size: 1.05rem;">
Need full workflow automation around your models? See our
<a href="{% url 'forward_deployed' %}" class="text-cyber-cyan">forward-deployed AI</a> and
<a href="{% url 'bot' %}" class="text-cyber-cyan">AI agents</a> services.
</p>
</div>
</div>

View File

@@ -2,8 +2,11 @@
{% load static %}
{% block title %}Home - AI ML Operations, LLC{% endblock %}
{% block meta_description %}AI ML Operations, LLC specializes in delivering production-ready AI and ML solutions, custom
web design, and reliable hosting services.{% endblock %}
{% block meta_description %}Forward-deployed AI engineering by AI ML Operations, LLC. We embed with your team to build and deploy custom agentic workflows, integrations, and production AI systems.{% endblock %}
{% block og_title %}Forward-Deployed AI Engineering - AI ML Operations, LLC{% endblock %}
{% block og_description %}We embed with your team to architect, build, and deploy custom AI workflows that solve real operational bottlenecks.{% endblock %}
{% block twitter_title %}Forward-Deployed AI Engineering - AI ML Operations, LLC{% endblock %}
{% block twitter_description %}We embed with your team to architect, build, and deploy custom AI workflows that solve real operational bottlenecks.{% endblock %}
{% block content %}
<!-- Hero Section -->
@@ -11,74 +14,152 @@ web design, and reliable hosting services.{% endblock %}
<canvas id="hero-canvas"></canvas>
<div class="hero-content">
<h1 class="hero-title">AI ML Operations</h1>
<p class="hero-subtitle">Taking concepts to production with intelligent solutions.</p>
<p class="hero-subtitle hero-subtitle--wide">
Forward-deployed AI engineering. We embed with your team to architect, build, and deploy custom AI workflows that solve complex operational bottlenecks.
</p>
<div class="hero-cta">
<a href="{% url 'forward_deployed' %}" class="btn btn-secondary">See How We Deploy</a>
</div>
</div>
</div>
<!-- About Us Section -->
<div class="section">
<div class="section" style="background: var(--surface-color);">
<div class="container">
<h2 class="section-title">About Us</h2>
<p style="text-align: center; max-width: 800px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
At AI/ML Operations, we are dedicated to delivering production-ready AI and ML solutions that seamlessly integrate
into your workflows. With over a decade of experience, we specialize in crafting cutting-edge machine learning
models and AI algorithms to tackle the most complex and demanding challenges across various industries.
<p style="text-align: center; max-width: 820px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
At AI ML Operations, we deliver production-ready AI by embedding with your team—not handing off strategy decks.
With over a decade of experience, we audit your real workflows, integrate with your infrastructure, and deploy
agentic automation and machine learning where it creates measurable impact. You reduce time-to-value without building
a full-time specialized AI engineering org from scratch.
</p>
</div>
</div>
<!-- How We Work -->
<!-- <div id="how-we-work" class="section" style="background: var(--surface-color);"> -->
<div id="how-we-work" class="section" >
<div class="container">
<h2 class="section-title">How We Work</h2>
<div class="card-grid card-grid--steps">
<div class="card">
<span class="card-step-num">01</span>
<span class="card-title">Embed</span>
<p class="card-text">Work alongside your team—onsite or remote—to understand operations and constraints.</p>
</div>
<div class="card">
<span class="card-step-num">02</span>
<span class="card-title">Map</span>
<p class="card-text">Document workflows, data flows, and integration points across your stack.</p>
</div>
<div class="card">
<span class="card-step-num">03</span>
<span class="card-title">Build</span>
<p class="card-text">Ship agents, orchestration, and integrations tailored to your environment.</p>
</div>
<div class="card">
<span class="card-step-num">04</span>
<span class="card-title">Deploy</span>
<p class="card-text">Launch to production with monitoring, analytics, and a clean handoff.</p>
</div>
<div class="card">
<span class="card-step-num">05</span>
<span class="card-title">Monitor</span>
<p class="card-text">Track performance, refine workflows, and iterate for continuous improvement.</p>
</div>
<div class="card">
<span class="card-step-num">06</span>
<span class="card-title">Handoff</span>
<p class="card-text">Transfer ownership and documentation to your team for ongoing maintenance.</p>
</div>
</div>
<p style="text-align: center; margin-top: 2.5rem;">
<a href="{% url 'forward_deployed' %}" class="text-cyber-cyan" style="font-weight: 600;">Learn more about forward-deployed AI →</a>
</p>
</div>
</div>
<!-- Workflow Transformations -->
<!-- <div id="case-studies" class="section">
<div class="container">
<h2 class="section-title">Workflow Transformations</h2>
<p style="text-align: center; max-width: 720px; margin: -1.5rem auto 2.5rem; color: var(--text-muted); font-size: 1.05rem;">
Real deployments, real outcomes—problem, deployment, and measurable results.
</p>
<div class="card-grid">
<div class="card case-study-placeholder">
<span class="case-study-label">Coming Soon</span>
<h5 class="card-title">Client Workflow #1</h5>
<p class="card-text"><strong>Problem:</strong> Manual process consuming hours each week.</p>
<p class="card-text"><strong>Deployment:</strong> Embedded engineering, mapped data, built automated agent workflow.</p>
<p class="card-text"><strong>Outcome:</strong> Testimonials and metrics arriving soon.</p>
</div>
<div class="card case-study-placeholder">
<span class="case-study-label">Coming Soon</span>
<h5 class="card-title">Client Workflow #2</h5>
<p class="card-text"><strong>Problem:</strong> Disconnected tools and no path from LLM pilots to production.</p>
<p class="card-text"><strong>Deployment:</strong> Integrated orchestration into existing operations.</p>
<p class="card-text"><strong>Outcome:</strong> Case study in progress—check back shortly.</p>
</div>
</div>
</div>
</div> -->
<!-- Services Section -->
<div class="section">
<div class="section" style="background: var(--surface-color);">
<div class="container">
<h2 class="section-title">Our Services</h2>
<p style="text-align: center; max-width: 640px; margin: -1.5rem auto 2rem; color: var(--text-muted);">
Core delivery: embedded AI engineering and production agents.
</p>
<div class="card-grid">
<a href="{% url 'ai_sensor' %}" class="card">
<span class="card-title">AI Sensor Algorithms</span>
<p class="card-text">Develop intelligent algorithms that enhance sensor data processing, enabling real-time
insights and decision-making for IoT and industrial applications.</p>
<a href="{% url 'forward_deployed' %}" class="card card-featured">
<span class="card-badge">Primary</span>
<span class="card-title">Forward-Deployed AI</span>
<p class="card-text">Embed with your team to map bottlenecks, build integrations, and deploy custom AI workflows in your environment.</p>
</a>
<a href="{% url 'ai_education' %}" class="card">
<span class="card-title">AI Education</span>
<p class="card-text">Stay ahead of the AI technology curve with our AI Education classes. Classes are tailored
to the audience familiarity with AI. Held in-person or virtual.</p>
<a href="{% url 'bot' %}" class="card card-featured">
<span class="card-badge">Primary</span>
<span class="card-title">AI Agents &amp; Automation</span>
<p class="card-text">Proprietary web-hosted agents for your workflows and conversations—scalable hosting plus performance and real-time analytics.</p>
</a>
<a href="{% url 'bot' %}" class="card">
<span class="card-title">Bot Creation</span>
<p class="card-text">Build agentic intelligent bots for workflow automation, customer support, and data
collection for enhancing productivity and user experience.</p>
</a>
<a href="{% url 'chat' %}" class="card">
<span class="card-title">Chat</span>
<p class="card-text">Chat is a closed-source Large Language Model (LLM) designed specifically for small and
medium businesses.</p>
</a>
<a href="{% url 'computers' %}" class="card">
<span class="card-title">Computer Builds</span>
<p class="card-text">Whether you're looking for a server for storage or compute or a workstation computer, we
will build your next system for you.</p>
</a>
<a href="{% url 'file_hosting' %}" class="card">
<span class="card-title">File Hosting</span>
<p class="card-text">Secure and scalable file hosting solutions to store, manage, and share your data with ease
and confidence.</p>
<a href="{% url 'web_design' %}" class="card">
<span class="card-badge">Primary</span>
<span class="card-title">Web Design and Hosting</span>
<p class="card-text">Modern websites with reliable hosting for your online presence.</p>
</a>
<a href="{% url 'ml_model' %}" class="card">
<span class="card-title">ML Model Creation</span>
<p class="card-text">Design and deploy custom machine learning models tailored to your business needs, ensuring
accuracy, scalability, and performance.</p>
<p class="card-text">Custom models designed for deployment inside your pipelines—not experiments that never ship.</p>
</a>
<a href="{% url 'web_design' %}" class="card">
<span class="card-title">Web Design and Hosting</span>
<p class="card-text">Create visually stunning and highly functional websites, coupled with reliable hosting
solutions to ensure your online presence is always at its best.</p>
<a href="{% url 'chat' %}" class="card">
<span class="card-title">Secure AI Chat</span>
<p class="card-text">Hosted LLM workspace with privacy-first architecture—an example of how we deliver scalable, observable AI products.</p>
</a>
<a href="{% url 'ai_sensor' %}" class="card">
<span class="card-title">AI Sensor Algorithms</span>
<p class="card-text">Intelligent algorithms for sensor data processing, real-time insights, and IoT applications.</p>
</a>
<a href="{% url 'ai_education' %}" class="card">
<span class="card-title">AI Education</span>
<p class="card-text">Tailored AI training for your team—in-person or virtual.</p>
</a>
<a href="{% url 'computers' %}" class="card">
<span class="card-title">Computer Builds</span>
<p class="card-text">Servers and workstations for compute, storage, and local inference workloads.</p>
</a>
<a href="{% url 'file_hosting' %}" class="card">
<span class="card-title">File Hosting</span>
<p class="card-text">Secure, scalable file hosting to store and share your data.</p>
</a>
</div>

View File

@@ -93,12 +93,15 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<h2 class="section-title">Web Hosting Plans</h2>
<!-- Pricing toggle -->
<div style="text-align: center; margin-bottom: 3rem;">
<label style="color: var(--text-color); font-size: 1.1rem; cursor: pointer;">
Monthly
<input type="checkbox" id="pricingToggle" style="margin: 0 10px;">
Yearly
</label>
<div class="pricing-toggle-wrap">
<div class="pricing-toggle" role="group" aria-label="Billing period">
<span class="pricing-toggle-label active" id="monthlyLabel">Monthly</span>
<label class="pricing-switch" for="pricingToggle">
<input type="checkbox" id="pricingToggle" role="switch" aria-labelledby="monthlyLabel yearlyLabel">
<span class="pricing-switch-slider"></span>
</label>
<span class="pricing-toggle-label" id="yearlyLabel">Yearly</span>
</div>
</div>
<!-- Pricing cards -->
@@ -139,12 +142,18 @@ visually stunning, functional websites tailored to your business.{% endblock %}
</div>
<script>
// Toggle functionality
document.getElementById('pricingToggle').addEventListener('change', function () {
const pricingToggle = document.getElementById('pricingToggle');
const monthlyLabel = document.getElementById('monthlyLabel');
const yearlyLabel = document.getElementById('yearlyLabel');
function updatePricing(isYearly) {
const prices = document.querySelectorAll('.price');
const periods = document.querySelectorAll('.billing-period');
if (this.checked) {
monthlyLabel.classList.toggle('active', !isYearly);
yearlyLabel.classList.toggle('active', isYearly);
if (isYearly) {
prices[0].textContent = '100';
periods[0].textContent = 'per year';
prices[1].textContent = '150';
@@ -155,6 +164,20 @@ visually stunning, functional websites tailored to your business.{% endblock %}
prices[1].textContent = '15';
periods[1].textContent = 'per month';
}
}
pricingToggle.addEventListener('change', function () {
updatePricing(this.checked);
});
monthlyLabel.addEventListener('click', function () {
pricingToggle.checked = false;
updatePricing(false);
});
yearlyLabel.addEventListener('click', function () {
pricingToggle.checked = true;
updatePricing(true);
});
</script>

2
company_site/public/urls.py Normal file → Executable file
View File

@@ -11,7 +11,9 @@ urlpatterns = [
path("ai_sensor", views.ai_sensor, name="ai_sensor"),
path("file_hosting", views.file_hosting, name="file_hosting"),
path("bot_creation", views.bot, name="bot"),
path("forward-deployed-ai", views.forward_deployed, name="forward_deployed"),
path("ml_model", views.ml_model, name="ml_model"),
path("contact", views.contact, name="contact"),
path("change_password", views.change_password, name="change_password"),
path("preview_email/<int:pk>/", views.preview_email, name="preview_email")
]

25
company_site/public/views.py Normal file → Executable file
View File

@@ -1,4 +1,4 @@
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse
from .models import Contact, EmailMessage
@@ -8,6 +8,9 @@ from django.conf import settings
from django.core.mail import send_mail
from .forms import FormWithCaptcha
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash
from django.contrib import messages
def send_contact_email(email, subject, message):
subject = "New Contact Request for AI ML Operations, LLC"
@@ -46,6 +49,9 @@ def file_hosting(request):
def bot(request):
return render(request, "public/bot.html", {})
def forward_deployed(request):
return render(request, "public/forward_deployed.html", {})
def ml_model(request):
return render(request, "public/ml_model.html", {})
@@ -102,3 +108,20 @@ def contact(request):
capcha = None if settings.DEBUG else FormWithCaptcha()
return render(request, "public/contact.html", {'capchaForm': capcha})
@login_required
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user) # Important!
messages.success(request, 'Your password was successfully updated!')
return redirect('public_index')
else:
messages.error(request, 'Please correct the error below.')
else:
form = PasswordChangeForm(request.user)
return render(request, 'public/change_password.html', {
'form': form
})