remove cycle creation

This commit is contained in:
Alberto-Audrix committed 2026-08-28 16:06:04 +07:00
1 parent c608580f16
commit 5b5d28ce52
2 files changed
+17 -412

No files matched your search

+2 -227
View File
@@ -1,4 +1,4 @@
from flask import Flask, abort, jsonify, redirect, render_template, request, url_for
from flask import Flask, abort, jsonify, render_template, request, url_for
import sqlite3
import json
from datetime import datetime, timedelta, timezone
@@ -7,17 +7,11 @@ from pathlib import Path
import uuid
from counter_time import (
INDONESIA_TIMEZONES,
apply_env_counter_settings,
default_counter_settings,
get_cutoff_metadata,
get_default_settings_file,
get_business_date,
get_env_locked_counter_settings,
get_period_window,
normalize_cutoff_time,
normalize_timezone,
save_counter_settings,
)
from werkzeug.exceptions import HTTPException
@@ -48,7 +42,6 @@ def get_configured_db_path(env_name, default_file_name):
load_env_file(BASE_DIR / '.env')
COUNTER_SETTINGS_FILE = get_default_settings_file(BASE_DIR)
COUNTER_SETTING_DEFAULTS = default_counter_settings()
KARUNG_TUANG_DB = get_configured_db_path('KARUNG_TUANG_DB', 'karung_tuang.db')
@@ -83,11 +76,6 @@ DEFAULT_DASHBOARD_SETTINGS = {
'local_timezone': COUNTER_SETTING_DEFAULTS['local_timezone'],
}
DEFAULT_CUTOFF_TIME_OPTIONS = [
*(f'{hour:02d}:00:00' for hour in range(24)),
'24:00:00',
]
def get_db_data(db_path, table_name):
"""Fetch all data from database"""
db_path = Path(db_path)
@@ -114,6 +102,7 @@ def get_db_data(db_path, table_name):
return data
def load_dashboard_settings():
"""Load active cycle settings. Will be replaced by external cycle API."""
ensure_settings_db()
with sqlite3.connect(SETTINGS_DB) as conn:
@@ -147,108 +136,6 @@ def load_dashboard_settings():
'local_timezone': row[7],
})
def save_dashboard_settings(settings):
ensure_settings_db()
with sqlite3.connect(SETTINGS_DB) as conn:
conn.execute('UPDATE cycle_settings SET is_active = 0')
conn.execute(
"""
INSERT INTO cycle_settings (
cycle_name,
cycle_start,
cycle_end,
saldo_awal,
tuang_cutoff_time,
masuk_cutoff_time,
local_timezone,
is_active,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?)
""",
(
settings['cycle_name'],
settings['cycle_start'],
settings['cycle_end'],
settings['saldo_awal'],
settings['tuang_cutoff_time'],
settings['masuk_cutoff_time'],
settings['local_timezone'],
datetime.now().isoformat(timespec='seconds')
)
)
export_counter_settings(settings)
def get_cycle_options():
ensure_settings_db()
with sqlite3.connect(SETTINGS_DB) as conn:
rows = conn.execute(
"""
SELECT latest_cycle.id,
latest_cycle.cycle_name,
latest_cycle.cycle_start,
latest_cycle.cycle_end,
latest_cycle.saldo_awal,
latest_cycle.tuang_cutoff_time,
latest_cycle.masuk_cutoff_time,
latest_cycle.local_timezone
FROM cycle_settings AS latest_cycle
INNER JOIN (
SELECT cycle_name, MAX(id) AS latest_id
FROM cycle_settings
GROUP BY cycle_name
) AS grouped_cycle
ON latest_cycle.id = grouped_cycle.latest_id
ORDER BY latest_cycle.cycle_name COLLATE NOCASE
"""
).fetchall()
return [
{
'id': row[0],
'cycle_name': row[1],
'cycle_start': row[2],
'cycle_end': row[3],
'saldo_awal': row[4],
'tuang_cutoff_time': row[5],
'masuk_cutoff_time': row[6],
'local_timezone': row[7],
}
for row in rows
]
def select_cycle_by_name(cycle_name):
ensure_settings_db()
with sqlite3.connect(SETTINGS_DB) as conn:
row = conn.execute(
"""
SELECT id
FROM cycle_settings
WHERE cycle_name = ?
ORDER BY id DESC
LIMIT 1
""",
(cycle_name,)
).fetchone()
if not row:
abort(404, description='Cycle name was not found.')
conn.execute('UPDATE cycle_settings SET is_active = 0')
conn.execute(
"""
UPDATE cycle_settings
SET is_active = 1,
updated_at = ?
WHERE id = ?
""",
(datetime.now().isoformat(timespec='seconds'), row[0])
)
def ensure_settings_db():
with sqlite3.connect(SETTINGS_DB) as conn:
existing_schema = conn.execute(
@@ -434,13 +321,6 @@ def format_history_date(date_text):
return parsed_date.strftime('%Y-%m-%d')
def format_date_picker_value(date_text):
parsed_date = parse_cycle_date(date_text, 'Cycle date')
if not parsed_date:
return ''
return parsed_date.strftime('%Y-%m-%d')
def filter_data_by_cycle(data, cycle_start, cycle_end):
if not cycle_start or not cycle_end:
return []
@@ -467,68 +347,6 @@ def is_masuk_out_item(item):
or camera_name.endswith(' out')
)
def get_settings_from_form():
cycle_name = request.form.get('cycle_name', '').strip()
cycle_start_raw = request.form.get('cycle_start', '').strip()
cycle_end_raw = request.form.get('cycle_end', '').strip()
saldo_awal_raw = request.form.get('saldo_awal', '0').strip()
tuang_cutoff_raw = request.form.get(
'tuang_cutoff_time',
DEFAULT_DASHBOARD_SETTINGS['tuang_cutoff_time']
).strip()
masuk_cutoff_raw = request.form.get(
'masuk_cutoff_time',
DEFAULT_DASHBOARD_SETTINGS['masuk_cutoff_time']
).strip()
local_timezone_raw = request.form.get(
'local_timezone',
DEFAULT_DASHBOARD_SETTINGS['local_timezone']
).strip()
if not cycle_name:
abort(400, description='Cycle name is required.')
if not cycle_start_raw or not cycle_end_raw:
abort(400, description='Cycle start and cycle end are required to filter historical data.')
cycle_start = parse_cycle_date(cycle_start_raw, 'Cycle start')
cycle_end = parse_cycle_date(cycle_end_raw, 'Cycle end')
if cycle_start and cycle_end and cycle_start > cycle_end:
abort(400, description='Cycle start must be before or equal to cycle end.')
try:
saldo_awal = int(saldo_awal_raw or 0)
except ValueError:
abort(400, description='Saldo awal must be a whole number.')
if saldo_awal < 0:
abort(400, description='Saldo awal cannot be negative.')
try:
tuang_cutoff_time = normalize_cutoff_time(tuang_cutoff_raw)
except ValueError as error:
abort(400, description=f'Karung Tuang cutoff time is invalid: {error}')
try:
masuk_cutoff_time = normalize_cutoff_time(masuk_cutoff_raw)
except ValueError as error:
abort(400, description=f'Karung Masuk cutoff time is invalid: {error}')
try:
local_timezone = normalize_timezone(local_timezone_raw)
except ValueError as error:
abort(400, description=f'Local timezone is invalid: {error}')
return apply_env_counter_settings({
'cycle_name': cycle_name,
'cycle_start': cycle_start.strftime(DISPLAY_DATE_FORMAT) if cycle_start else '',
'cycle_end': cycle_end.strftime(DISPLAY_DATE_FORMAT) if cycle_end else '',
'saldo_awal': saldo_awal,
'tuang_cutoff_time': tuang_cutoff_time,
'masuk_cutoff_time': masuk_cutoff_time,
'local_timezone': local_timezone,
})
def get_counter_settings_from_dashboard(dashboard_settings):
return apply_env_counter_settings({
'local_timezone': dashboard_settings.get(
@@ -545,21 +363,6 @@ def get_counter_settings_from_dashboard(dashboard_settings):
),
})
def export_counter_settings(settings):
try:
save_counter_settings(get_counter_settings_from_dashboard(settings), COUNTER_SETTINGS_FILE)
except OSError as error:
abort(500, description=f'Cannot save cutoff settings to {COUNTER_SETTINGS_FILE}: {error}')
def get_cutoff_time_options(*selected_cutoff_times):
option_values = list(DEFAULT_CUTOFF_TIME_OPTIONS)
for cutoff_time in selected_cutoff_times:
normalized_cutoff_time = normalize_cutoff_time(cutoff_time)
if normalized_cutoff_time not in option_values:
option_values.append(normalized_cutoff_time)
return sorted(option_values)
def get_json_data(json_path):
"""Fetch current data from JSON file"""
try:
@@ -896,32 +699,13 @@ def get_k1_combined():
response.headers['X-Request-ID'] = payload['meta']['requestId']
return response
@app.route('/settings', methods=['POST'])
def update_dashboard_settings():
settings = get_settings_from_form()
save_dashboard_settings(settings)
return redirect(url_for('index'))
@app.route('/settings/select', methods=['POST'])
def select_dashboard_cycle():
cycle_name = request.form.get('cycle_name', '').strip()
if not cycle_name:
abort(400, description='Cycle name is required to select a cycle.')
select_cycle_by_name(cycle_name)
export_counter_settings(load_dashboard_settings())
return redirect(url_for('index'))
@app.route('/')
def index():
dashboard_settings = load_dashboard_settings()
cycle_options = get_cycle_options()
counter_settings = get_counter_settings_from_dashboard(dashboard_settings)
cutoff_metadata = get_cutoff_metadata(counter_settings)
cycle_start = parse_cycle_date(dashboard_settings['cycle_start'], 'Cycle start')
cycle_end = parse_cycle_date(dashboard_settings['cycle_end'], 'Cycle end')
cycle_start_picker_value = format_date_picker_value(dashboard_settings['cycle_start'])
cycle_end_picker_value = format_date_picker_value(dashboard_settings['cycle_end'])
today_section = build_today_section(cycle_start, cycle_end, counter_settings, cutoff_metadata)
# Get historical data from databases
@@ -955,16 +739,7 @@ def index():
return render_template('index.html',
dashboard_settings=dashboard_settings,
timezone_options=INDONESIA_TIMEZONES,
cutoff_time_options=get_cutoff_time_options(
dashboard_settings['tuang_cutoff_time'],
dashboard_settings['masuk_cutoff_time']
),
cutoff_metadata=cutoff_metadata,
env_locked_counter_settings=get_env_locked_counter_settings(),
cycle_options=cycle_options,
cycle_start_picker_value=cycle_start_picker_value,
cycle_end_picker_value=cycle_end_picker_value,
tuang_db_data=tuang_db_data,
masuk_db_data=masuk_db_data,
tuang_json_data=tuang_json_data,
+15 -185
View File
@@ -418,8 +418,7 @@
.theme-card label,
.selected-cycle-label,
.metric-label,
.form-group label {
.metric-label {
color: var(--muted);
font-size: 12px;
font-weight: 800;
@@ -427,9 +426,7 @@
text-transform: uppercase;
}
.theme-select,
.selected-cycle-select,
.settings-input {
.theme-select {
width: 100%;
color: var(--text);
border: 1px solid var(--border);
@@ -438,17 +435,13 @@
transition: border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}
.theme-select,
.selected-cycle-select,
.settings-input {
.theme-select {
min-height: 46px;
padding: 0 14px;
border-radius: 14px;
}
.theme-select:focus,
.selected-cycle-select:focus,
.settings-input:focus {
.theme-select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 4px var(--accent-soft);
}
@@ -462,13 +455,17 @@
.selected-cycle {
display: grid;
grid-template-columns: minmax(280px, 1.15fr) minmax(0, 2fr);
gap: 18px;
margin-top: 22px;
padding: 22px;
border-radius: var(--radius-lg);
}
.selected-cycle-header {
display: grid;
gap: 8px;
}
.selected-cycle h2,
.section h2 {
margin-bottom: 8px;
@@ -478,50 +475,12 @@
}
.panel-subtitle,
.settings-hint,
.filter-note {
color: var(--muted);
font-size: 14px;
line-height: 1.6;
}
.selected-cycle-form {
display: flex;
gap: 10px;
margin-top: 14px;
}
.selected-cycle-button,
.settings-button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 42px;
padding: 0 16px;
color: var(--button-text);
border: 0;
border-radius: 14px;
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
font-weight: 800;
transition: transform 160ms ease, opacity 160ms ease, box-shadow 160ms ease;
}
html[data-theme="futuristic"] .selected-cycle-button,
html[data-theme="futuristic"] .settings-button {
color: #031016;
border: 1px solid rgba(143, 251, 255, 0.65);
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.08em;
box-shadow: 0 0 18px rgba(0, 245, 255, 0.28);
}
.selected-cycle-button:hover,
.settings-button:hover {
transform: translateY(-1px);
box-shadow: 0 12px 26px var(--accent-soft);
}
.selected-cycle-details {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -872,23 +831,6 @@
background: rgba(56, 189, 248, 0.12);
}
.settings-form {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
gap: 14px;
align-items: end;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.settings-button {
min-height: 46px;
}
html[data-theme="classic"] body {
padding: 20px;
}
@@ -937,8 +879,7 @@
html[data-theme="classic"] .theme-card label,
html[data-theme="classic"] .selected-cycle-label,
html[data-theme="classic"] .metric-label,
html[data-theme="classic"] .form-group label {
html[data-theme="classic"] .metric-label {
color: #2c3e50;
font-size: 12px;
font-weight: 600;
@@ -950,9 +891,7 @@
margin-top: 6px;
}
html[data-theme="classic"] .theme-select,
html[data-theme="classic"] .selected-cycle-select,
html[data-theme="classic"] .settings-input {
html[data-theme="classic"] .theme-select {
color: #333333;
border: 1px solid #cccccc;
border-radius: 5px;
@@ -1007,17 +946,6 @@
font-weight: 700;
}
html[data-theme="classic"] .selected-cycle-button {
min-height: auto;
padding: 8px 12px;
border-radius: 5px;
background: #27ae60;
}
html[data-theme="classic"] .selected-cycle-button:hover {
background: #219653;
}
html[data-theme="classic"] .metric-grid,
html[data-theme="classic"] .cards {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
@@ -1203,23 +1131,6 @@
background: #a8e6cf;
}
html[data-theme="classic"] .settings-form {
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 15px;
}
html[data-theme="classic"] .settings-button {
min-height: auto;
padding: 11px 16px;
border-radius: 5px;
background: #2c3e50;
font-weight: 600;
}
html[data-theme="classic"] .settings-button:hover {
background: #1f2d3a;
}
@media (max-width: 1200px) {
.metric-grid,
.cards {
@@ -1229,10 +1140,6 @@
.table-grid {
grid-template-columns: 1fr;
}
.settings-form {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 860px) {
@@ -1264,23 +1171,16 @@
}
.metric-grid,
.cards,
.settings-form {
.cards {
grid-template-columns: 1fr;
}
.section-header,
.historical-summary,
.table-header,
.selected-cycle-form {
.table-header {
flex-direction: column;
align-items: stretch;
}
.selected-cycle-button,
.settings-button {
width: 100%;
}
}
</style>
</head>
@@ -1310,18 +1210,10 @@
</header>
<section class="selected-cycle" aria-labelledby="active-cycle-title">
<div>
<div class="selected-cycle-header">
<span class="section-kicker">Active cycle</span>
<h2 id="active-cycle-title">Cycle Command Center</h2>
<p class="panel-subtitle">Switch the active cycle and keep every total aligned to the selected date range.</p>
<form class="selected-cycle-form" action="{{ url_for('select_dashboard_cycle') }}" method="post">
<select class="selected-cycle-select" name="cycle_name" required>
{% for cycle in cycle_options %}
<option value="{{ cycle.cycle_name }}" {% if cycle.cycle_name == dashboard_settings.cycle_name %}selected{% endif %}>{{ cycle.cycle_name }}</option>
{% endfor %}
</select>
<button class="selected-cycle-button" type="submit">Pilih</button>
</form>
<p class="panel-subtitle">Active cycle data is loaded from the configured source and shown read-only.</p>
</div>
<div class="selected-cycle-details">
<div class="cycle-stat">
@@ -1507,68 +1399,6 @@
</div>
</div>
</details>
<section class="section" aria-labelledby="settings-title">
<div class="section-header">
<div>
<span class="section-kicker">Cycle settings</span>
<h2 id="settings-title">Pengaturan Siklus</h2>
<p class="panel-subtitle">Data historis hanya menampilkan data dalam rentang siklus.</p>
</div>
</div>
<form class="settings-form" action="{{ url_for('update_dashboard_settings') }}" method="post">
<div class="form-group">
<label for="cycle_name">Cycle Name</label>
<input id="cycle_name" class="settings-input" type="text" name="cycle_name" value="{{ dashboard_settings.cycle_name }}" required>
</div>
<div class="form-group">
<label for="cycle_start">Cycle Start</label>
<input id="cycle_start" class="settings-input" type="date" name="cycle_start" value="{{ cycle_start_picker_value }}" required>
</div>
<div class="form-group">
<label for="cycle_end">Cycle End</label>
<input id="cycle_end" class="settings-input" type="date" name="cycle_end" value="{{ cycle_end_picker_value }}" required>
</div>
<div class="form-group">
<label for="saldo_awal">Saldo Awal</label>
<input id="saldo_awal" class="settings-input" type="number" name="saldo_awal" value="{{ dashboard_settings.saldo_awal }}" min="0" step="1" required>
</div>
<div class="form-group">
<label for="tuang_cutoff_time">Karung Tuang Cutoff</label>
<select id="tuang_cutoff_time" class="settings-input" name="tuang_cutoff_time" required {% if 'tuang_cutoff_time' in env_locked_counter_settings %}disabled{% endif %}>
{% for cutoff_time in cutoff_time_options %}
<option value="{{ cutoff_time }}" {% if cutoff_time == dashboard_settings.tuang_cutoff_time %}selected{% endif %}>{{ cutoff_time }}</option>
{% endfor %}
</select>
{% if 'tuang_cutoff_time' in env_locked_counter_settings %}
<input type="hidden" name="tuang_cutoff_time" value="{{ dashboard_settings.tuang_cutoff_time }}">
{% endif %}
</div>
<div class="form-group">
<label for="masuk_cutoff_time">Karung Masuk Cutoff</label>
<select id="masuk_cutoff_time" class="settings-input" name="masuk_cutoff_time" required {% if 'masuk_cutoff_time' in env_locked_counter_settings %}disabled{% endif %}>
{% for cutoff_time in cutoff_time_options %}
<option value="{{ cutoff_time }}" {% if cutoff_time == dashboard_settings.masuk_cutoff_time %}selected{% endif %}>{{ cutoff_time }}</option>
{% endfor %}
</select>
{% if 'masuk_cutoff_time' in env_locked_counter_settings %}
<input type="hidden" name="masuk_cutoff_time" value="{{ dashboard_settings.masuk_cutoff_time }}">
{% endif %}
</div>
<div class="form-group">
<label for="local_timezone">Timezone Indonesia</label>
<select id="local_timezone" class="settings-input" name="local_timezone" required {% if 'local_timezone' in env_locked_counter_settings %}disabled{% endif %}>
{% for timezone_name, timezone_label in timezone_options.items() %}
<option value="{{ timezone_name }}" {% if timezone_name == dashboard_settings.local_timezone %}selected{% endif %}>{{ timezone_label }}</option>
{% endfor %}
</select>
{% if 'local_timezone' in env_locked_counter_settings %}
<input type="hidden" name="local_timezone" value="{{ dashboard_settings.local_timezone }}">
{% endif %}
</div>
<button class="settings-button" type="submit">Simpan Pengaturan</button>
</form>
</section>
</div>
<script>
const THEME_STORAGE_KEY = 'karung-dashboard-theme';