ADMOD

header ads

UJI KOMPETENSI 2025 RPL HITUNGAN DISKON

 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">
    <h1 id="title">APLIKASI HITUNG DISKON SMK NURLIDA</h1>
    <div class="sub">Hitung harga setelah diskon dalam Rupiah dengan cepat dan akurat.</div>

    <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">
          <input id="useTax" type="checkbox" aria-label="Gunakan PPN">
          <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">Diskon berjenjang</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>



Posting Komentar

0 Komentar