674 lines
24 KiB
HTML
674 lines
24 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block content %}
|
||
<div class="spreadsheet-container">
|
||
<!-- Navigation Header -->
|
||
<div class="sheet-navigation">
|
||
<a href="{% url 'monthly_sheet' prev_month.year prev_month.month %}">← Previous</a>
|
||
<h2>{{ year }} - {{ month_name }} (Sheet {{ month }}/6)</h2>
|
||
<a href="{% url 'monthly_sheet' next_month.year next_month.month %}">Next →</a>
|
||
<a href="{% url 'summary_sheet' year month %}">Go to Summary</a>
|
||
</div>
|
||
|
||
<!-- Top Tables Container -->
|
||
<div class="top-tables">
|
||
<!-- Left Table (18 rows × clients) -->
|
||
<div class="table-container top-left-table">
|
||
<h3>Table 1: Top Left</h3>
|
||
<table class="spreadsheet-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Row Label</th>
|
||
{% for header in top_left_headers %}
|
||
<th>{{ header }}</th>
|
||
{% endfor %}
|
||
<th>Sum</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in top_left_rows %}
|
||
{% with rownum=forloop.counter %}
|
||
<tr data-row="{{ forloop.counter0 }}">
|
||
<td class="row-label">
|
||
{% if rownum == 1 %}
|
||
Stand der Gaszähler (Nm³)
|
||
{% elif rownum == 2 %}
|
||
Stand der Gaszähler (Vormonat) (Nm³)
|
||
{% elif rownum == 3 %}
|
||
Gasrückführung (Nm³)
|
||
{% elif rownum == 4 %}
|
||
Rückführung flüssig (Lit. L-He)
|
||
{% elif rownum == 5 %}
|
||
Sonderrückführungen (Lit. L-He)
|
||
{% elif rownum == 6 %}
|
||
Bestand in Kannen-1 (Lit. L-He)
|
||
{% elif rownum == 7 %}
|
||
Summe Bestand (Lit. L-He)
|
||
{% elif rownum == 8 %}
|
||
Best. in Kannen Vormonat (Lit. L-He)
|
||
{% elif rownum == 9 %}
|
||
Bezug (Liter L-He)
|
||
{% elif rownum == 10 %}
|
||
Rückführ. Soll (Lit. L-He)
|
||
{% elif rownum == 11 %}
|
||
Verluste (Soll-Rückf.) (Lit. L-He)
|
||
{% elif rownum == 12 %}
|
||
Füllungen warm (Lit. L-He)
|
||
{% elif rownum == 13 %}
|
||
Kaltgas Rückgabe (Lit. L-He) – Faktor
|
||
{% elif rownum == 14 %}
|
||
Faktor 0.06
|
||
{% elif rownum == 15 %}
|
||
Verbraucherverluste (Liter L-He)
|
||
{% elif rownum == 16 %}
|
||
%
|
||
{% elif rownum == 17 %}
|
||
Gesamtrückführung (Nm³)
|
||
{% elif rownum == 18 %}
|
||
Aufgeteilte Verluste (Liter L-He)
|
||
{% endif %}
|
||
</td>
|
||
|
||
{% for cell in row.cells %}
|
||
{% if is_start_sheet or rownum == 1 or rownum == 5 or rownum == 6 %}
|
||
{# Editable in start sheet OR always editable rows (B3, B7, B8) #}
|
||
<td class="editable-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="top_left"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="true">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% else %}
|
||
{# Readonly for non-start sheets #}
|
||
<td class="readonly-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="top_left"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="false">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
|
||
<td class="sum-cell">
|
||
{{ row.sum|default_if_none:"" }}
|
||
</td>
|
||
</tr>
|
||
{% endwith %}
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Right Table (24 rows × 6 clients) -->
|
||
<!-- Update the top-right table section in monthly_sheet.html -->
|
||
<div class="table-container top-right-table">
|
||
<h3>Table 2: Top Right</h3>
|
||
<table class="spreadsheet-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Row Label</th>
|
||
{% for header in top_right_headers %}
|
||
<th>{{ header }}</th>
|
||
{% endfor %}
|
||
<th>Sum</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in top_right_rows %}
|
||
{% with rownum=forloop.counter %}
|
||
<tr data-row="{{ forloop.counter0 }}">
|
||
<td class="row-label">
|
||
{% if rownum == 1 %}
|
||
Stand der Gaszähler (Vormonat) (Nm³)
|
||
{% elif rownum == 2 %}
|
||
Gasrückführung (Nm³)
|
||
{% elif rownum == 3 %}
|
||
Rückführung flüssig (Lit. L-He)
|
||
{% elif rownum == 4 %}
|
||
Sonderrückführungen (Lit. L-He)
|
||
{% elif rownum == 5 %}
|
||
Sammelrückführungen (Lit. L-He)
|
||
{% elif rownum == 6 %}
|
||
Bestand in Kannen-1 (Lit. L-He)
|
||
{% elif rownum == 7 %}
|
||
Summe Bestand (Lit. L-He)
|
||
{% elif rownum == 8 %}
|
||
Best. in Kannen Vormonat (Lit. L-He)
|
||
{% elif rownum == 9 %}
|
||
Bezug (Liter L-He)
|
||
{% elif rownum == 10 %}
|
||
Rückführ. Soll (Lit. L-He)
|
||
{% elif rownum == 11 %}
|
||
Verluste (Soll-Rückf.) (Lit. L-He)
|
||
{% elif rownum == 12 %}
|
||
Füllungen warm (Lit. L-He)
|
||
{% elif rownum == 13 %}
|
||
Kaltgas Rückgabe (Lit. L-He) – Faktor
|
||
{% elif rownum == 14 %}
|
||
Faktor 0.06
|
||
{% elif rownum == 15 %}
|
||
Verbraucherverluste (Liter L-He)
|
||
{% elif rownum == 16 %}
|
||
%
|
||
{% endif %}
|
||
</td>
|
||
|
||
{% for cell in row.cells %}
|
||
{% with client_name=cell.client.name|default:"" %}
|
||
{# Determine if this cell should be editable #}
|
||
{% if is_start_sheet %}
|
||
<td class="editable-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="top_right"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="true">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% elif rownum == 4 or rownum == 6 or rownum == 1 and client_name in "M3 Thiele,M3 Buntkowsky,M3 Gutfleisch" %}
|
||
<td class="editable-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="top_right"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="true">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% else %}
|
||
<td class="readonly-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="top_right"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="false">
|
||
{% if rownum == 2 %}
|
||
Aufteilung Nach Verbrauch
|
||
{% else %}
|
||
{{ cell.value|default:"" }}
|
||
{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
{% endwith %}
|
||
{% endfor %}
|
||
|
||
<td class="sum-cell">
|
||
{{ row.sum|default_if_none:"" }}
|
||
</td>
|
||
</tr>
|
||
{% endwith %}
|
||
{% endfor %}
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Bottom Tables -->
|
||
<div class="bottom-tables">
|
||
<div class="table-container bottom-table-1">
|
||
<h3>Bottom Table 1</h3>
|
||
<table class="spreadsheet-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Row Label</th>
|
||
{% for client in clients %}
|
||
<th>{{ client.name }}</th>
|
||
{% endfor %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in cells_by_table.bottom_1 %}
|
||
<tr data-row="{{ forloop.counter0 }}">
|
||
<td class="row-label">Row {{ forloop.counter }}</td>
|
||
{% for cell in row %}
|
||
<td class="editable-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="bottom_1"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="true">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="table-container bottom-table-2">
|
||
<h3>Bottom Table 2</h3>
|
||
<table class="spreadsheet-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Row Label</th>
|
||
{% for client in clients %}
|
||
<th>{{ client.name }}</th>
|
||
{% endfor %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in cells_by_table.bottom_2 %}
|
||
<tr data-row="{{ forloop.counter0 }}">
|
||
<td class="row-label">Row {{ forloop.counter }}</td>
|
||
{% for cell in row %}
|
||
<td class="editable-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="bottom_2"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="true">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="table-container bottom-table-3">
|
||
<h3>Bottom Table 3</h3>
|
||
<table class="spreadsheet-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Row Label</th>
|
||
{% for client in clients %}
|
||
<th>{{ client.name }}</th>
|
||
{% endfor %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in cells_by_table.bottom_3 %}
|
||
<tr data-row="{{ forloop.counter0 }}">
|
||
<td class="row-label">Row {{ forloop.counter }}</td>
|
||
{% for cell in row %}
|
||
<td class="editable-cell"
|
||
data-cell-id="{{ cell.id|default:'' }}"
|
||
data-table="bottom_3"
|
||
data-row="{{ forloop.parentloop.counter0 }}"
|
||
data-col="{{ forloop.counter0 }}"
|
||
data-client-id="{{ cell.client.id|default:'' }}"
|
||
contenteditable="true">
|
||
{{ cell.value|default:"" }}
|
||
</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Save Button -->
|
||
<div class="action-bar">
|
||
<button id="save-all-btn" class="btn btn-primary">Save All Cells</button>
|
||
<div id="save-status"></div>
|
||
<div class="legend">
|
||
<small>
|
||
<span style="background-color: #d1ecf1; padding: 2px 5px;">Saved cells</span>
|
||
<span style="background-color: #d4edda; padding: 2px 5px;">Calculated cells</span>
|
||
</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hidden form for cell data -->
|
||
<form id="cell-data-form" style="display: none;">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="sheet_id" value="{{ sheet.id }}">
|
||
</form>
|
||
|
||
<style>
|
||
.spreadsheet-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.sheet-navigation {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding: 10px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.sheet-navigation h2 {
|
||
margin: 0;
|
||
}
|
||
|
||
.top-tables {
|
||
display: flex;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.top-tables .table-container {
|
||
flex: 1;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
padding: 10px;
|
||
background-color: white;
|
||
}
|
||
.merged-cell-left {
|
||
border-right-width: 0;
|
||
}
|
||
|
||
.merged-cell-right {
|
||
border-left-width: 0;
|
||
}
|
||
|
||
.cell-group-LM {
|
||
background-color: #f0f8ff; /* Light blue for L&M group */
|
||
}
|
||
|
||
.cell-group-NO {
|
||
background-color: #f0fff0; /* Light green for N&O group */
|
||
}
|
||
|
||
.cell-group-PQR {
|
||
background-color: #fff0f0; /* Light red for P,Q,R group */
|
||
}
|
||
.bottom-tables {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.bottom-tables .table-container {
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
padding: 10px;
|
||
background-color: white;
|
||
}
|
||
|
||
.spreadsheet-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.spreadsheet-table th,
|
||
.spreadsheet-table td {
|
||
border: 1px solid #ddd;
|
||
padding: 8px;
|
||
text-align: center;
|
||
}
|
||
|
||
.spreadsheet-table th {
|
||
background-color: #f2f2f2;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.row-label {
|
||
background-color: #f9f9f9;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.editable-cell {
|
||
min-width: 100px;
|
||
min-height: 30px;
|
||
background-color: white;
|
||
cursor: text;
|
||
}
|
||
|
||
.editable-cell:focus {
|
||
outline: 2px solid #007bff;
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.editable-cell:hover {
|
||
background-color: #f1f1f1;
|
||
}
|
||
|
||
.readonly-cell {
|
||
background-color: #f8f9fa;
|
||
color: #495057;
|
||
cursor: default;
|
||
min-height: 30px;
|
||
}
|
||
|
||
.cell-calculated {
|
||
font-style: italic;
|
||
font-weight: bold;
|
||
background-color: #e8f4f8;
|
||
}
|
||
|
||
.sum-cell {
|
||
font-weight: bold;
|
||
background-color: #f0f0f0;
|
||
padding: 5px;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
padding: 10px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
#save-status {
|
||
margin-left: 10px;
|
||
color: #28a745;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.saving-text {
|
||
position: absolute;
|
||
font-size: 11px;
|
||
color: #666;
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.saving {
|
||
background-color: #fff3cd !important;
|
||
font-style: italic;
|
||
}
|
||
|
||
.saved-success {
|
||
background-color: #d4edda !important;
|
||
transition: background-color 0.5s ease;
|
||
}
|
||
|
||
.calculated-cell {
|
||
background-color: #e8f4f8 !important;
|
||
font-style: italic;
|
||
color: #0c5460;
|
||
}
|
||
|
||
.editable-cell[contenteditable="false"] {
|
||
background-color: #f8f9fa !important;
|
||
cursor: not-allowed;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
$(document).ready(function() {
|
||
// Enable editing on focus
|
||
$('.editable-cell').on('focus', function() {
|
||
$(this).data('original-value', $(this).text().trim());
|
||
});
|
||
|
||
// Save on blur
|
||
$('.editable-cell').on('blur', function() {
|
||
saveCell($(this));
|
||
});
|
||
|
||
// Save on Enter key
|
||
$('.editable-cell').on('keydown', function(e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
$(this).blur();
|
||
}
|
||
});
|
||
|
||
// Prevent editing of readonly cells
|
||
$('.readonly-cell').on('focus click', function(e) {
|
||
e.preventDefault();
|
||
$(this).blur();
|
||
});
|
||
|
||
function saveCell($cell) {
|
||
const cellId = $cell.data('cell-id');
|
||
const newValue = $cell.text().trim();
|
||
const originalValue = $cell.data('original-value') || '';
|
||
|
||
// Skip if no change
|
||
if (newValue === originalValue) {
|
||
return;
|
||
}
|
||
|
||
// Skip if no cell ID
|
||
if (!cellId) {
|
||
console.error('No cell ID found');
|
||
return;
|
||
}
|
||
|
||
// Show saving indicator
|
||
$cell.addClass('saving');
|
||
|
||
// Prepare AJAX request
|
||
const csrfToken = $('[name=csrfmiddlewaretoken]').val();
|
||
|
||
$.ajax({
|
||
url: '{% url "save_cells" %}',
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
data: {
|
||
'sheet_id': '{{ sheet.id }}',
|
||
'cell_id': cellId,
|
||
'value': newValue
|
||
},
|
||
success: function(response) {
|
||
$cell.removeClass('saving');
|
||
|
||
if (response.status === 'success') {
|
||
// Update all cells returned by the server
|
||
if (response.updated_cells) {
|
||
response.updated_cells.forEach(function(cellData) {
|
||
const targetCell = $('[data-cell-id="' + cellData.id + '"]');
|
||
if (targetCell.length) {
|
||
targetCell.text(cellData.value || '');
|
||
targetCell.data('original-value', cellData.value || '');
|
||
|
||
// Style calculated cells differently
|
||
// Style calculated cells differently
|
||
if (cellData.is_calculated) {
|
||
targetCell.addClass('calculated-cell');
|
||
// Leave contenteditable as set by the template
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Show success message
|
||
showStatus('Saved successfully!', 'success');
|
||
|
||
// Highlight the saved cell briefly
|
||
$cell.addClass('saved-success');
|
||
setTimeout(function() {
|
||
$cell.removeClass('saved-success');
|
||
}, 1000);
|
||
|
||
} else {
|
||
// Restore original value on error
|
||
$cell.text(originalValue);
|
||
showStatus('Error: ' + response.message, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
$cell.removeClass('saving');
|
||
$cell.text(originalValue);
|
||
showStatus('Error: ' + error, 'error');
|
||
console.error('Save error:', error);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Save All button
|
||
$('#save-all-btn').on('click', function() {
|
||
const csrfToken = $('[name=csrfmiddlewaretoken]').val();
|
||
const formData = new FormData();
|
||
formData.append('sheet_id', '{{ sheet.id }}');
|
||
formData.append('csrfmiddlewaretoken', csrfToken);
|
||
|
||
// Collect all cell values
|
||
$('.editable-cell').each(function() {
|
||
const cellId = $(this).data('cell-id');
|
||
if (cellId) {
|
||
formData.append('cell_' + cellId, $(this).text().trim());
|
||
}
|
||
});
|
||
|
||
$.ajax({
|
||
url: '{% url "save_cells" %}',
|
||
method: 'POST',
|
||
data: formData,
|
||
processData: false,
|
||
contentType: false,
|
||
headers: {
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
success: function(response) {
|
||
if (response.status === 'success') {
|
||
if (response.updated_cells) {
|
||
response.updated_cells.forEach(function(cellData) {
|
||
const targetCell = $('[data-cell-id="' + cellData.id + '"]');
|
||
if (targetCell.length) {
|
||
targetCell.text(cellData.value || '');
|
||
targetCell.data('original-value', cellData.value || '');
|
||
}
|
||
});
|
||
}
|
||
showStatus('All cells saved!', 'success');
|
||
} else {
|
||
showStatus('Error: ' + response.message, 'error');
|
||
}
|
||
},
|
||
error: function() {
|
||
showStatus('Error saving all cells', 'error');
|
||
}
|
||
});
|
||
});
|
||
|
||
function showStatus(message, type) {
|
||
const statusEl = $('#save-status');
|
||
statusEl.text(message);
|
||
statusEl.css('color', type === 'success' ? '#28a745' : '#dc3545');
|
||
|
||
setTimeout(function() {
|
||
statusEl.text('');
|
||
}, 3000);
|
||
}
|
||
});
|
||
</script>
|
||
{% endblock %} |