This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% let active_page = "schedule" %}
|
||||
|
||||
{% block title %}{{ t.schedule_new_title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>{{ t.schedule_new_title }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<form method="post" action="/admin/schedule/create" id="visitForm">
|
||||
<!-- Client -->
|
||||
<div class="field">
|
||||
<label class="label">{{ t.schedule_client }}</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select name="client_id" required>
|
||||
<option value="">—</option>
|
||||
{% for c in &clients %}
|
||||
<option value="{{ c.id }}">{{ c.name }}{% if let Some(p) = c.phone.as_deref() %} ({{ p }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin -->
|
||||
<div class="field">
|
||||
<label class="label">{{ t.schedule_admin }}</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select name="user_id">
|
||||
{% for u in &users %}
|
||||
<option value="{{ u.id }}" {% if u.id.unwrap() == current_user_id %}selected{% endif %}>
|
||||
{{ u.display_name.as_deref().unwrap_or(&u.login) }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Default time -->
|
||||
<div class="field">
|
||||
<label class="label">{{ t.schedule_default_time }}</label>
|
||||
<div class="columns is-mobile" style="margin-bottom:0;">
|
||||
<div class="column">
|
||||
<div class="control">
|
||||
<input class="input" type="time" id="defaultStart" value="18:00">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="control">
|
||||
<input class="input" type="time" id="defaultEnd" value="19:00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add individual date -->
|
||||
<div class="field">
|
||||
<label class="label">{{ t.schedule_pick_dates }}</label>
|
||||
<div class="columns is-mobile" style="margin-bottom:0;">
|
||||
<div class="column">
|
||||
<div class="control">
|
||||
<input class="input" type="date" id="pickDate">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<button type="button" class="button is-info" id="addDateBtn">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date range fill -->
|
||||
<div class="field">
|
||||
<label class="label is-small has-text-grey">{{ t.schedule_range_from }} — {{ t.schedule_range_to }}</label>
|
||||
<div class="columns is-mobile" style="margin-bottom:0;">
|
||||
<div class="column">
|
||||
<input class="input" type="date" id="rangeFrom">
|
||||
</div>
|
||||
<div class="column">
|
||||
<input class="input" type="date" id="rangeTo">
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<button type="button" class="button is-info is-outlined" id="fillRangeBtn">{{ t.schedule_fill_range }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected days list -->
|
||||
<div class="field">
|
||||
<label class="label">{{ t.schedule_selected_days }}</label>
|
||||
<div id="daysList">
|
||||
<p class="has-text-grey is-size-7" id="noDaysMsg">{{ t.schedule_no_days }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="field">
|
||||
<label class="label">{{ t.schedule_notes }}</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" name="notes" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden days data -->
|
||||
<input type="hidden" name="days_json" id="daysJson" value="[]">
|
||||
|
||||
<button type="submit" class="button is-primary is-fullwidth" id="submitBtn" disabled>{{ t.schedule_create }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.day-row {
|
||||
display: flex; align-items: center; gap: 0.4rem; padding: 0.4rem 0;
|
||||
border-bottom: 1px solid #f0f0f0; flex-wrap: wrap;
|
||||
}
|
||||
.day-row .day-date { font-weight: 600; min-width: 6rem; font-size: 0.9rem; }
|
||||
.day-row input[type="time"] { width: 7rem; padding: 0.2rem 0.4rem; border: 1px solid #ddd; border-radius: 4px; font-size: 0.85rem; }
|
||||
.day-row .remove-btn { color: #e55; cursor: pointer; font-size: 0.8rem; margin-left: auto; background: none; border: none; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const days = new Map(); // date string -> {start, end}
|
||||
const removeLabel = '{{ t.schedule_remove_day }}';
|
||||
const weekdays = '{{ lang.code() }}' === 'ru'
|
||||
? ['Вс','Пн','Вт','Ср','Чт','Пт','Сб']
|
||||
: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
|
||||
|
||||
function getDefaults() {
|
||||
return {
|
||||
start: document.getElementById('defaultStart').value || '18:00',
|
||||
end: document.getElementById('defaultEnd').value || '19:00'
|
||||
};
|
||||
}
|
||||
|
||||
function addDay(dateStr) {
|
||||
if (!dateStr || days.has(dateStr)) return;
|
||||
const def = getDefaults();
|
||||
days.set(dateStr, { start: def.start, end: def.end });
|
||||
renderDays();
|
||||
}
|
||||
|
||||
function removeDay(dateStr) {
|
||||
days.delete(dateStr);
|
||||
renderDays();
|
||||
}
|
||||
|
||||
function renderDays() {
|
||||
const list = document.getElementById('daysList');
|
||||
const msg = document.getElementById('noDaysMsg');
|
||||
const btn = document.getElementById('submitBtn');
|
||||
|
||||
// Remove old day rows
|
||||
list.querySelectorAll('.day-row').forEach(el => el.remove());
|
||||
|
||||
if (days.size === 0) {
|
||||
msg.style.display = '';
|
||||
btn.disabled = true;
|
||||
document.getElementById('daysJson').value = '[]';
|
||||
return;
|
||||
}
|
||||
msg.style.display = 'none';
|
||||
btn.disabled = false;
|
||||
|
||||
// Sort by date
|
||||
const sorted = [...days.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
||||
|
||||
sorted.forEach(([dateStr, times]) => {
|
||||
const d = new Date(dateStr + 'T00:00:00');
|
||||
const wd = weekdays[d.getDay()];
|
||||
const label = dateStr.split('-').reverse().join('.') + ' ' + wd;
|
||||
|
||||
const row = document.createElement('div');
|
||||
row.className = 'day-row';
|
||||
row.innerHTML = `
|
||||
<span class="day-date">${label}</span>
|
||||
<input type="time" value="${times.start}" data-date="${dateStr}" data-field="start">
|
||||
<span>—</span>
|
||||
<input type="time" value="${times.end}" data-date="${dateStr}" data-field="end">
|
||||
<button type="button" class="remove-btn" data-date="${dateStr}">${removeLabel}</button>
|
||||
`;
|
||||
list.appendChild(row);
|
||||
});
|
||||
|
||||
// Update hidden JSON
|
||||
updateJson();
|
||||
|
||||
// Bind events
|
||||
list.querySelectorAll('input[type="time"]').forEach(inp => {
|
||||
inp.addEventListener('change', function() {
|
||||
const dt = this.dataset.date;
|
||||
const field = this.dataset.field;
|
||||
if (days.has(dt)) {
|
||||
days.get(dt)[field] = this.value;
|
||||
updateJson();
|
||||
}
|
||||
});
|
||||
});
|
||||
list.querySelectorAll('.remove-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
removeDay(this.dataset.date);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateJson() {
|
||||
const arr = [...days.entries()].map(([date, t]) => ({
|
||||
date: date,
|
||||
time_start: t.start,
|
||||
time_end: t.end
|
||||
}));
|
||||
document.getElementById('daysJson').value = JSON.stringify(arr);
|
||||
}
|
||||
|
||||
document.getElementById('addDateBtn').addEventListener('click', function() {
|
||||
const v = document.getElementById('pickDate').value;
|
||||
addDay(v);
|
||||
document.getElementById('pickDate').value = '';
|
||||
});
|
||||
|
||||
document.getElementById('pickDate').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') { e.preventDefault(); document.getElementById('addDateBtn').click(); }
|
||||
});
|
||||
|
||||
document.getElementById('fillRangeBtn').addEventListener('click', function() {
|
||||
const from = document.getElementById('rangeFrom').value;
|
||||
const to = document.getElementById('rangeTo').value;
|
||||
if (!from || !to || from > to) return;
|
||||
let cur = new Date(from + 'T00:00:00');
|
||||
const end = new Date(to + 'T00:00:00');
|
||||
while (cur <= end) {
|
||||
const ds = cur.toISOString().slice(0, 10);
|
||||
addDay(ds);
|
||||
cur.setDate(cur.getDate() + 1);
|
||||
}
|
||||
document.getElementById('rangeFrom').value = '';
|
||||
document.getElementById('rangeTo').value = '';
|
||||
});
|
||||
|
||||
// Set default pick date to today
|
||||
document.getElementById('pickDate').valueAsDate = new Date();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user