forked from ariska/karung-web-admin
remove cycle creation
This commit is contained in:
1 parent
c608580f16
commit
5b5d28ce52
2 files changed
+17
-412
No files matched your search
@@ -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
@@ -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';
|
||||
|
||||
Reference in new issue
Block a user