forked from izu/student-web-if-development-kit
fix(tapops): ganti input date native dengan dd/mm/yyyy text input terpisah agar tahun max 4 digit
This commit is contained in:
@@ -363,36 +363,47 @@
|
|||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-range-inputs .input-wrapper {
|
/* Custom dd/mm/yyyy date input */
|
||||||
position: relative;
|
.custom-date-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 1px;
|
||||||
|
padding: 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-range-inputs input[type="date"] {
|
.date-sep {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #94a3b8;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-part {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 8px 0;
|
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--primary-blue);
|
color: var(--primary-blue);
|
||||||
cursor: pointer;
|
text-align: center;
|
||||||
min-width: 120px;
|
padding: 2px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-range-inputs input[type="date"]:focus {
|
.date-dd, .date-mm { width: 2.2ch; }
|
||||||
|
.date-yyyy { width: 4.2ch; }
|
||||||
|
|
||||||
|
.date-part::placeholder {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-part:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
background: rgba(0, 49, 80, 0.07);
|
||||||
|
|
||||||
.date-range-inputs input[type="date"]::-webkit-calendar-picker-indicator {
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.6;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-range-inputs input[type="date"]::-webkit-calendar-picker-indicator:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.range-divider {
|
.range-divider {
|
||||||
@@ -701,18 +712,8 @@ a.btn-action-outline:hover {
|
|||||||
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-range-inputs input[type="date"] {
|
.date-part {
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
font-weight: 700;
|
|
||||||
color: var(--primary-blue);
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.range-divider {
|
.range-divider {
|
||||||
|
|||||||
@@ -946,12 +946,20 @@
|
|||||||
<div class="control-group date-range-group">
|
<div class="control-group date-range-group">
|
||||||
<span class="control-label" data-tp-i18n="label_period">Periode Unggahan</span>
|
<span class="control-label" data-tp-i18n="label_period">Periode Unggahan</span>
|
||||||
<div class="date-range-inputs">
|
<div class="date-range-inputs">
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper custom-date-input">
|
||||||
<input type="date" id="date-start" placeholder="Mulai">
|
<input type="text" inputmode="numeric" maxlength="2" placeholder="DD" class="date-part date-dd" id="date-start-dd" autocomplete="off">
|
||||||
|
<span class="date-sep">/</span>
|
||||||
|
<input type="text" inputmode="numeric" maxlength="2" placeholder="MM" class="date-part date-mm" id="date-start-mm" autocomplete="off">
|
||||||
|
<span class="date-sep">/</span>
|
||||||
|
<input type="text" inputmode="numeric" maxlength="4" placeholder="YYYY" class="date-part date-yyyy" id="date-start-yyyy" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
<span class="range-divider" data-tp-i18n="range_divider">s/d</span>
|
<span class="range-divider" data-tp-i18n="range_divider">s/d</span>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper custom-date-input">
|
||||||
<input type="date" id="date-end" placeholder="Selesai">
|
<input type="text" inputmode="numeric" maxlength="2" placeholder="DD" class="date-part date-dd" id="date-end-dd" autocomplete="off">
|
||||||
|
<span class="date-sep">/</span>
|
||||||
|
<input type="text" inputmode="numeric" maxlength="2" placeholder="MM" class="date-part date-mm" id="date-end-mm" autocomplete="off">
|
||||||
|
<span class="date-sep">/</span>
|
||||||
|
<input type="text" inputmode="numeric" maxlength="4" placeholder="YYYY" class="date-part date-yyyy" id="date-end-yyyy" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+61
-26
@@ -423,14 +423,23 @@ class DocumentRepository {
|
|||||||
this.filterWrapper = document.getElementById('tag-filter-wrapper');
|
this.filterWrapper = document.getElementById('tag-filter-wrapper');
|
||||||
this.mobileFilterWrapper = document.getElementById('mobile-tag-filter-wrapper');
|
this.mobileFilterWrapper = document.getElementById('mobile-tag-filter-wrapper');
|
||||||
this.searchInput = document.getElementById('doc-search');
|
this.searchInput = document.getElementById('doc-search');
|
||||||
this.dateStartInput = document.getElementById('date-start');
|
|
||||||
this.dateEndInput = document.getElementById('date-end');
|
|
||||||
this.sortFilterWrapper = document.getElementById('sort-filter-wrapper');
|
this.sortFilterWrapper = document.getElementById('sort-filter-wrapper');
|
||||||
|
|
||||||
// Hero stat values (filled from real API data after fetch).
|
// Hero stat values (filled from real API data after fetch).
|
||||||
this.statActiveEl = document.getElementById('stat-active-value');
|
this.statActiveEl = document.getElementById('stat-active-value');
|
||||||
this.statUpdatesEl = document.getElementById('stat-updates-value');
|
this.statUpdatesEl = document.getElementById('stat-updates-value');
|
||||||
|
|
||||||
|
this.dateStartParts = {
|
||||||
|
dd: document.getElementById('date-start-dd'),
|
||||||
|
mm: document.getElementById('date-start-mm'),
|
||||||
|
yyyy: document.getElementById('date-start-yyyy'),
|
||||||
|
};
|
||||||
|
this.dateEndParts = {
|
||||||
|
dd: document.getElementById('date-end-dd'),
|
||||||
|
mm: document.getElementById('date-end-mm'),
|
||||||
|
yyyy: document.getElementById('date-end-yyyy'),
|
||||||
|
};
|
||||||
|
|
||||||
this.cards = [];
|
this.cards = [];
|
||||||
this.paginationContainer = null;
|
this.paginationContainer = null;
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -443,6 +452,52 @@ class DocumentRepository {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns YYYY-MM-DD string only when dd, mm, and yyyy are all filled and valid.
|
||||||
|
_getDateString(parts) {
|
||||||
|
const dd = parts.dd?.value.trim() || '';
|
||||||
|
const mm = parts.mm?.value.trim() || '';
|
||||||
|
const yyyy = parts.yyyy?.value.trim() || '';
|
||||||
|
|
||||||
|
if (!dd || !mm || yyyy.length < 4) return '';
|
||||||
|
|
||||||
|
const d = parseInt(dd, 10);
|
||||||
|
const m = parseInt(mm, 10);
|
||||||
|
const y = parseInt(yyyy, 10);
|
||||||
|
|
||||||
|
if (isNaN(d) || isNaN(m) || isNaN(y)) return '';
|
||||||
|
if (d < 1 || d > 31 || m < 1 || m > 12 || y < 1 || y > 9999) return '';
|
||||||
|
|
||||||
|
return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire up a set of dd/mm/yyyy text inputs:
|
||||||
|
// • numeric-only keydown guard
|
||||||
|
// • auto-advance dd→mm after 2 chars, mm→yyyy after 2 chars
|
||||||
|
// • backspace from empty field navigates back
|
||||||
|
// • calls onUpdate() on every change
|
||||||
|
_bindDateParts(parts, onUpdate) {
|
||||||
|
const { dd, mm, yyyy } = parts;
|
||||||
|
const CTRL_KEYS = new Set(['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter']);
|
||||||
|
|
||||||
|
const setup = (input, prev, next) => {
|
||||||
|
if (!input) return;
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (!/^\d$/.test(e.key) && !CTRL_KEYS.has(e.key)) e.preventDefault();
|
||||||
|
if (e.key === 'Backspace' && !input.value && prev) prev.focus();
|
||||||
|
});
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
input.value = input.value.replace(/\D/g, '');
|
||||||
|
onUpdate();
|
||||||
|
const max = parseInt(input.getAttribute('maxlength'), 10);
|
||||||
|
if (next && input.value.length >= max) next.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setup(dd, null, mm);
|
||||||
|
setup(mm, dd, yyyy);
|
||||||
|
setup(yyyy, mm, null);
|
||||||
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
if (!this.gridContainer) return;
|
if (!this.gridContainer) return;
|
||||||
this._bindControls();
|
this._bindControls();
|
||||||
@@ -581,40 +636,20 @@ class DocumentRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bindControls() {
|
_bindControls() {
|
||||||
// Limit date inputs to a 4-digit year (max 9999).
|
|
||||||
[this.dateStartInput, this.dateEndInput].forEach(input => {
|
|
||||||
if (!input) return;
|
|
||||||
input.setAttribute('max', '9999-12-31');
|
|
||||||
input.setAttribute('min', '1000-01-01');
|
|
||||||
// Clamp year on both input (fires while typing) and change (fires on blur).
|
|
||||||
const clampYear = () => {
|
|
||||||
if (!input.value) return;
|
|
||||||
const parts = input.value.split('-');
|
|
||||||
if (parts.length !== 3) return;
|
|
||||||
const year = parseInt(parts[0], 10);
|
|
||||||
if (!Number.isNaN(year) && year > 9999) {
|
|
||||||
parts[0] = '9999';
|
|
||||||
input.value = parts.join('-');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
input.addEventListener('input', clampYear);
|
|
||||||
input.addEventListener('change', clampYear);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.searchInput?.addEventListener('input', (e) => {
|
this.searchInput?.addEventListener('input', (e) => {
|
||||||
this.state.search = e.target.value.toLowerCase().trim();
|
this.state.search = e.target.value.toLowerCase().trim();
|
||||||
this.state.page = 1;
|
this.state.page = 1;
|
||||||
this._applyFilters();
|
this._applyFilters();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dateStartInput?.addEventListener('change', (e) => {
|
this._bindDateParts(this.dateStartParts, () => {
|
||||||
this.state.startDate = e.target.value;
|
this.state.startDate = this._getDateString(this.dateStartParts);
|
||||||
this.state.page = 1;
|
this.state.page = 1;
|
||||||
this._applyFilters();
|
this._applyFilters();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dateEndInput?.addEventListener('change', (e) => {
|
this._bindDateParts(this.dateEndParts, () => {
|
||||||
this.state.endDate = e.target.value;
|
this.state.endDate = this._getDateString(this.dateEndParts);
|
||||||
this.state.page = 1;
|
this.state.page = 1;
|
||||||
this._applyFilters();
|
this._applyFilters();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user