Reworked admin style
Build and Publish / Build and Publish Docker Image (push) Successful in 1m42s

This commit is contained in:
Aleksandr Bogomiakov
2026-08-09 03:19:47 +01:00
parent cec96cab1e
commit 79c287eaa0
18 changed files with 985 additions and 749 deletions
+29 -140
View File
@@ -5,10 +5,18 @@
{% block content %}
<div class="page-head">
<h1>{{ t.schedule_edit_title }}</h1>
<h1>{{ t.schedule_title }}</h1>
</div>
<div class="form-card">
<section class="admin-section">
<header class="admin-section-head">
<span class="admin-section-icon" aria-hidden="true">📋</span>
<div>
<h2>{{ t.schedule_edit_title }}</h2>
<p>{{ client.name }}</p>
</div>
</header>
<div class="admin-section-body">
<form method="post" action="/admin/schedule/{{ visit.id }}/save">
<!-- Client -->
<div class="field">
@@ -45,7 +53,7 @@
<!-- Time -->
<div class="field">
<label class="label">{{ t.schedule_default_time }}</label>
<div class="columns is-mobile" style="margin-bottom:0;">
<div class="columns is-mobile time-columns">
<div class="column">
<div class="control">
<input class="input" type="time" name="time_start" value="{{ visit.time_start }}" required>
@@ -93,21 +101,21 @@
</div>
{% if let Some(fb) = visit.client_feedback.as_deref() %}
<div style="margin-bottom:1rem;">
<div class="mb-4">
<label class="label">{{ t.schedule_client_feedback }}</label>
<div style="background:#f0f0ff;border-radius:8px;padding:0.6rem 0.85rem;font-size:0.9rem;color:#4a4570;">{{ fb }}</div>
<div class="admin-note admin-note-info">{{ fb }}</div>
</div>
{% endif %}
<hr style="margin:1rem 0;">
<hr>
<!-- Media -->
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem;">
<label class="label" style="margin:0;">{{ t.nav_media }}</label>
<div class="section-inline-head">
<label class="label">{{ t.nav_media }}</label>
<button type="button" class="button is-info is-small is-outlined" onclick="document.getElementById('uploadModal').classList.add('is-open')">+ {{ t.media_upload }}</button>
</div>
{% if media.is_empty() %}
<p class="has-text-grey is-size-7" style="margin-bottom:1rem;">{{ t.media_empty }}</p>
<p class="empty-state mb-4">{{ t.media_empty }}</p>
{% else %}
<div class="visit-media-grid">
{% for m in &media %}
@@ -139,7 +147,7 @@
</div>
{% endif %}
<hr style="margin:1rem 0;">
<hr>
<button type="submit" class="button is-primary is-fullwidth">{{ t.schedule_save }}</button>
</form>
@@ -147,18 +155,19 @@
<form id="visit-media-delete-{{ m.id.unwrap() }}" method="post" action="/admin/media/{{ m.id.unwrap() }}/delete" onsubmit="return confirm('{{ t.media_delete_confirm }}');"></form>
{% endfor %}
<hr style="margin:1rem 0;">
<hr>
<form method="post" action="/admin/schedule/{{ visit.id }}/delete" onsubmit="return confirm('{{ t.schedule_delete_confirm }}');">
<button type="submit" class="button is-danger is-outlined is-fullwidth is-small">{{ t.schedule_delete }}</button>
</form>
</div>
</div>
</section>
<!-- Upload Modal -->
<div class="upload-modal-bg" id="uploadModal">
<div class="upload-modal">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
<h3 style="font-size:1.1rem;font-weight:700;margin:0;">{{ t.media_upload_title }}</h3>
<button type="button" id="uploadModalClose" style="background:none;border:none;font-size:1.2rem;cursor:pointer;color:#888;"></button>
<div class="modal-head">
<h3>{{ t.media_upload_title }}</h3>
<button type="button" id="uploadModalClose" class="modal-close"></button>
</div>
<form id="uploadForm" action="/admin/media/{{ visit.id }}/upload/submit" enctype="multipart/form-data">
<div class="field">
@@ -166,7 +175,7 @@
<div class="control">
<input class="input" type="file" id="uploadFiles" name="files" multiple accept="image/*,video/*" required>
</div>
<p id="fileCount" style="font-size:0.8rem;color:#888;margin-top:0.3rem;"></p>
<p id="fileCount" class="field-help"></p>
<div id="uploadQueue" class="upload-queue"></div>
</div>
<div class="field">
@@ -177,13 +186,13 @@
</div>
<!-- Progress -->
<div id="uploadProgress" style="display:none;margin-bottom:1rem;">
<div style="display:flex;justify-content:space-between;font-size:0.82rem;color:#555;margin-bottom:0.3rem;">
<div id="uploadProgress" class="upload-progress">
<div class="upload-progress-head">
<span id="uploadStatusText">{{ t.media_upload }}...</span>
<span id="uploadPercent">0%</span>
</div>
<div style="background:#e8e8e8;border-radius:99px;height:8px;overflow:hidden;">
<div id="uploadBar" style="height:100%;width:0%;background:linear-gradient(90deg,#6c63ff,#b06cff);border-radius:99px;transition:width 0.2s;"></div>
<div class="upload-progress-track">
<div id="uploadBar" class="upload-progress-bar"></div>
</div>
</div>
@@ -192,126 +201,6 @@
</div>
</div>
<style>
.status-picker {
display: flex;
gap: 0.5rem;
}
.status-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.3rem;
padding: 0.6rem 0.4rem;
border-radius: 10px;
border: 2px solid transparent;
background: #f5f5f5;
font-size: 0.82rem;
font-weight: 600;
cursor: pointer;
color: #777;
transition: all 0.15s;
line-height: 1.2;
}
.status-btn-icon { font-size: 1.3rem; line-height: 1; }
.status-btn:hover { filter: brightness(0.95); }
.status-btn-scheduled.is-active { background: #dbeafe; border-color: #3b82f6; color: #1e40af; }
.status-btn-completed.is-active { background: #d1fae5; border-color: #22c55e; color: #15803d; }
.status-btn-cancelled.is-active { background: #fee2e2; border-color: #ef4444; color: #b91c1c; }
.status-btn-scheduled:not(.is-active):hover { background: #eff6ff; color: #3b82f6; }
.status-btn-completed:not(.is-active):hover { background: #f0fdf4; color: #22c55e; }
.status-btn-cancelled:not(.is-active):hover { background: #fff5f5; color: #ef4444; }
.visit-media-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 0.5rem;
margin-bottom: 1rem;
}
.visit-media-item {
background: #fafafa;
border-radius: 8px;
border: 1px solid #eee;
overflow: hidden;
}
.visit-media-item img {
width: 100%;
height: 80px;
object-fit: cover;
display: block;
}
.visit-media-item .photo-thumb {
width: 100%;
height: 80px;
}
.visit-media-item .video-thumb-sm {
position: relative;
width: 100%;
height: 80px;
background: #111;
}
.visit-media-item .video-play {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 1.6rem;
line-height: 1;
text-shadow: 0 1px 4px #000;
pointer-events: none;
}
.visit-media-item .media-cap {
font-size: 0.7rem;
color: #888;
padding: 0.2rem 0.4rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.visit-media-delete {
padding: 0.25rem 0.4rem 0.4rem;
}
.visit-media-delete .button {
width: 100%;
font-size: 0.68rem;
min-height: 1.65rem;
}
.upload-modal-bg {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.35);
z-index: 100;
align-items: center;
justify-content: center;
}
.upload-modal-bg.is-open {
display: flex;
}
.upload-modal {
background: #fff;
border-radius: 12px;
padding: 1.5rem;
width: 90%;
max-width: 420px;
box-shadow: 0 4px 24px rgba(0,0,0,0.15);
}
.upload-queue { display:flex; flex-direction:column; gap:.4rem; margin-top:.65rem; }
.upload-queue:empty { display:none; }
.upload-queue-item { display:grid; grid-template-columns:34px minmax(0,1fr); gap:.55rem; align-items:center; padding:.45rem .55rem; background:#f7f6ff; border-radius:8px; }
.upload-queue-icon { width:34px; height:34px; display:flex; align-items:center; justify-content:center; border-radius:7px; background:#ebe8ff; font-size:1.05rem; }
.upload-queue-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:.78rem; color:#494467; }
.upload-queue-state { font-size:.68rem; color:#8a84a5; }
.upload-queue-track { height:3px; overflow:hidden; border-radius:99px; background:#dedbea; margin-top:.25rem; }
.upload-queue-bar { height:100%; width:0; background:#7567e8; transition:width .2s; }
#uploadBar.is-processing, .upload-queue-bar.is-processing { width:35% !important; animation:upload-processing 1.15s ease-in-out infinite; }
@keyframes upload-processing { from { transform:translateX(-110%); } to { transform:translateX(300%); } }
</style>
<script>
// Status picker
document.querySelectorAll('.status-btn').forEach(function(btn) {