Dibawah ini merupakan hasil uji komptensi paket 4 dari soal ujikomptensi keahlian rekayasa prangkat lunak
Download Hasil File Html Klik disini
Sourccode :
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
:root {
--bg: #0e1117;
--panel: #161b22;
--text: #e6edf3;
--muted: #9da7b3;
--accent: #2ea043;
--accent-2: #1f6feb;
--danger: #f85149;
--border: #30363d;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Helvetica Neue", sans-serif;
background: radial-gradient(1200px 800px at 20% -10%, #182034, transparent 50%),
radial-gradient(1000px 700px at 110% 10%, #1a2238, transparent 50%),
var(--bg);
color: var(--text);
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
}
.app {
width: 100%;
max-width: 780px;
background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent 40%),
var(--panel);
border: 1px solid var(--border);
border-radius: 16px;
padding: 22px;
box-shadow: 0 10px 30px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.04);
backdrop-filter: blur(4px);
}
h1 {
font-size: 22px;
margin: 0 0 6px;
letter-spacing: 0.2px;
}
.sub {
color: var(--muted);
font-size: 13px;
margin-bottom: 18px;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}
@media (min-width: 720px) {
.grid { grid-template-columns: 1fr 1fr; }
}
label {
display: block;
font-size: 13px;
color: var(--muted);
margin-bottom: 6px;
}
.input, select {
width: 100%;
padding: 12px 12px;
border-radius: 10px;
border: 1px solid var(--border);
background: #0d1117;
color: var(--text);
outline: none;
transition: border-color .15s, box-shadow .15s;
}
.group {
display: grid;
gap: 8px;
background: rgba(255,255,255,0.02);
border: 1px dashed var(--border);
padding: 12px;
border-radius: 12px;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.inline {
display: flex;
align-items: center;
gap: 10px;
margin-top: 6px;
}
.btn {
padding: 12px 14px;
border-radius: 10px;
border: 1px solid var(--border);
background: linear-gradient(180deg, #238636, #196c2e);
color: white;
cursor: pointer;
font-weight: 600;
letter-spacing: 0.2px;
transition: transform .05s ease, filter .2s ease;
}
.btn:active { transform: translateY(1px); }
.btn.secondary {
background: linear-gradient(180deg, #30363d, #20262d);
color: var(--text);
}
.error {
color: var(--danger);
font-size: 13px;
margin-top: 6px;
min-height: 18px;
}
.result {
margin-top: 16px;
padding: 14px;
border-radius: 12px;
background: rgba(46,160,67,0.08);
border: 1px solid rgba(46,160,67,0.35);
}
.result h2 {
font-size: 18px;
margin: 0 0 8px;
}
.kv {
display: grid;
grid-template-columns: 1fr auto;
gap: 6px 12px;
align-items: center;
font-size: 14px;
}
.kv .k { color: var(--muted); }
.kv .v { font-weight: 600; }
.footer {
margin-top: 14px;
font-size: 12px;
color: var(--muted);
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.tag {
padding: 4px 8px;
border-radius: 999px;
background: #0d1117;
border: 1px solid var(--border);
font-weight: 600;
color: var(--muted);
}
</style>
</head>
<body>
<div class="app" role="application" aria-labelledby="title">
<div class="grid">
<div class="group">
<div>
<label for="price">Harga satuan (Rp)</label>
<input id="price" class="input" type="number" min="0" step="100" placeholder="contoh: 150000" inputmode="decimal">
</div>
<div class="row">
<div>
<label for="discountType">Jenis diskon</label>
<select id="discountType" class="input">
<option value="percent">Persentase (%)</option>
<option value="nominal">Nominal (Rp)</option>
</select>
</div>
<div>
<label for="discountValue">Nilai diskon</label>
<input id="discountValue" class="input" type="number" min="0" step="0.01" placeholder="mis. 15 atau 20000">
</div>
</div>
<div class="row">
<div>
<label for="qty">Kuantitas</label>
<input id="qty" class="input" type="number" min="1" step="1" value="1">
</div>
<div>
<label for="extra">Diskon tambahan (%)</label>
<input id="extra" class="input" type="number" min="0" step="0.01" placeholder="opsional, mis. 5">
</div>
</div>
<div class="inline">
<label for="useTax" style="margin:0;">Tambahkan PPN</label>
<input id="tax" class="input" style="max-width:140px;" type="number" min="0" step="0.01" value="11" aria-label="Persentase PPN">
<span class="tag">%</span>
</div>
<div class="inline">
<button id="calc" class="btn">Hitung</button>
<button id="reset" class="btn secondary">Reset</button>
</div>
<div id="err" class="error" role="alert" aria-live="polite"></div>
</div>
<div class="group">
<div class="result" aria-live="polite">
<h2>Hasil</h2>
<div class="kv">
<div class="k">Harga awal x kuantitas</div><div id="r-subtotal" class="v">-</div>
<div class="k">Diskon utama</div><div id="r-main-disc" class="v">-</div>
<div class="k">Diskon tambahan</div><div id="r-extra-disc" class="v">-</div>
<div class="k">Subtotal setelah diskon</div><div id="r-after-disc" class="v">-</div>
<div class="k">PPN</div><div id="r-tax" class="v">-</div>
<div class="k">Total bayar</div><div id="r-total" class="v" style="font-size:18px;">-</div>
<div class="k">Total hemat</div><div id="r-saved" class="v">-</div>
</div>
</div>
<div class="footer">
<span class="tag">Format: Rupiah (id-ID)</span>
<span class="tag">Validasi input</span>
</div>
</div>
</div>
</div>
<script>
// Formatter Rupiah
const rupiah = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 });
// Elemen
const el = id => document.getElementById(id);
const price = el('price');
const discountType = el('discountType');
const discountValue = el('discountValue');
const qty = el('qty');
const extra = el('extra');
const useTax = el('useTax');
const tax = el('tax');
const err = el('err');
const rSubtotal = el('r-subtotal');
const rMainDisc = el('r-main-disc');
const rExtraDisc = el('r-extra-disc');
const rAfterDisc = el('r-after-disc');
const rTax = el('r-tax');
const rTotal = el('r-total');
const rSaved = el('r-saved');
const calcBtn = el('calc');
const resetBtn = el('reset');
function parseNum(input, fallback = 0) {
const v = parseFloat(String(input).replace(',', '.'));
return isNaN(v) ? fallback : v;
}
function validateInputs() {
err.textContent = '';
const p = parseNum(price.value);
const d = parseNum(discountValue.value);
const q = parseInt(qty.value, 10);
const e = parseNum(extra.value || 0);
const t = parseNum(tax.value || 0);
if (p <= 0) return 'Harga harus lebih dari 0.';
if (!Number.isInteger(q) || q <= 0) return 'Kuantitas harus bilangan bulat minimal 1.';
if (discountType.value === 'percent' && (d < 0 || d > 100)) return 'Persentase diskon harus 0–100.';
if (discountType.value === 'nominal' && d < 0) return 'Diskon nominal tidak boleh negatif.';
if (e < 0 || e > 100) return 'Diskon tambahan harus 0–100.';
if (useTax.checked && (t < 0 || t > 100)) return 'PPN harus 0–100.';
if (discountType.value === 'nominal' && d > p) return 'Diskon nominal melebihi harga satuan.';
return '';
}
function compute() {
const p = parseNum(price.value);
const d = parseNum(discountValue.value || 0);
const q = Math.max(1, parseInt(qty.value || '1', 10));
const e = parseNum(extra.value || 0);
const t = parseNum(tax.value || 0);
const useT = useTax.checked;
// Subtotal awal
const subtotal = p * q;
// Diskon utama
let mainDisc = 0;
if (discountType.value === 'percent') {
mainDisc = subtotal * (d / 100);
} else {
// nominal per item
mainDisc = Math.min(d * q, subtotal);
}
const afterMain = subtotal - mainDisc;
// Diskon tambahan (persentase berjenjang atas nilai setelah diskon utama)
const extraDisc = afterMain * (e / 100);
const afterAllDisc = afterMain - extraDisc;
// Pajak
const taxValue = useT ? afterAllDisc * (t / 100) : 0;
// Total
const total = afterAllDisc + taxValue;
// Hemat total
const saved = subtotal - afterAllDisc;
return {
subtotal, mainDisc, extraDisc, afterAllDisc, taxValue, total, saved
};
}
function render(res) {
rSubtotal.textContent = rupiah.format(res.subtotal);
rMainDisc.textContent = '-' + rupiah.format(res.mainDisc);
rExtraDisc.textContent = '-' + rupiah.format(res.extraDisc);
rAfterDisc.textContent = rupiah.format(res.afterAllDisc);
rTax.textContent = res.taxValue > 0 ? rupiah.format(res.taxValue) : '-';
rTotal.textContent = rupiah.format(res.total);
rSaved.textContent = '-' + rupiah.format(res.saved);
}
function handleCalc() {
const msg = validateInputs();
if (msg) {
err.textContent = msg;
return;
}
err.textContent = '';
render(compute());
}
function handleReset() {
price.value = '';
discountType.value = 'percent';
discountValue.value = '';
qty.value = 1;
extra.value = '';
useTax.checked = false;
tax.value = 11;
err.textContent = '';
render({
subtotal: 0, mainDisc: 0, extraDisc: 0,
afterAllDisc: 0, taxValue: 0, total: 0, saved: 0
});
}
// UX kecil: ubah placeholder diskon sesuai pilihan
discountType.addEventListener('change', () => {
if (discountType.value === 'percent') {
discountValue.placeholder = 'mis. 15';
discountValue.step = '0.01';
} else {
discountValue.placeholder = 'mis. 20000';
discountValue.step = '100';
}
});
// Event
calcBtn.addEventListener('click', handleCalc);
resetBtn.addEventListener('click', handleReset);
// Enter untuk hitung
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') handleCalc();
});
// Render awal
handleReset();
</script>
</body>
</html>

0 Komentar
Silahkan Isi Komentar dibawah ini