diff --git a/configs/merchant-landing/index.html b/configs/merchant-landing/index.html index d69a228..71a3417 100644 --- a/configs/merchant-landing/index.html +++ b/configs/merchant-landing/index.html @@ -181,21 +181,47 @@ } .cur-panel .amt-secondary strong { color: var(--val); font-weight: 700; } - /* Recent activity */ + /* Recent activity — split by currency */ + .act-pair { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + margin-top: 0.35rem; + } + @media (max-width: 480px) { + .act-pair { grid-template-columns: 1fr; } + } + .act-col { + background: rgba(24, 18, 40, 0.45); + border: 1px solid rgba(167, 139, 250, 0.22); + border-radius: 12px; + padding: 0.55rem 0.55rem 0.35rem; + min-width: 0; + } + .act-col-h { + margin: 0 0 0.35rem; + font-size: 0.78rem; + font-weight: 750; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--goa); + text-align: center; + } + .act-col.chf .act-col-h { color: var(--chf); } .act-list { list-style: none; margin: 0; padding: 0; } .act-item { display: grid; grid-template-columns: auto 1fr auto; - gap: 0.35rem 0.65rem; + gap: 0.25rem 0.45rem; align-items: center; - padding: 0.55rem 0.35rem; + padding: 0.4rem 0.2rem; border-bottom: 1px solid rgba(255,255,255,0.06); - font-size: 0.88rem; + font-size: 0.82rem; } .act-item:last-child { border-bottom: 0; } .act-kind { - font-size: 0.65rem; font-weight: 800; letter-spacing: 0.06em; - text-transform: uppercase; padding: 0.18rem 0.4rem; border-radius: 6px; + font-size: 0.58rem; font-weight: 800; letter-spacing: 0.05em; + text-transform: uppercase; padding: 0.14rem 0.32rem; border-radius: 6px; align-self: center; } .act-kind.payment { color: #14532d; background: rgba(134, 239, 172, 0.85); } @@ -204,17 +230,17 @@ .act-main { min-width: 0; } .act-amt { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--val); - font-size: 0.95rem; + font-size: 0.88rem; } .act-sum { - color: var(--muted); font-size: 0.78rem; white-space: nowrap; + color: var(--muted); font-size: 0.72rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } .act-meta { - text-align: right; font-size: 0.68rem; color: var(--muted); + text-align: right; font-size: 0.62rem; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; } - .empty { text-align: center; color: var(--muted); font-size: 0.85rem; padding: 0.5rem; } + .empty { text-align: center; color: var(--muted); font-size: 0.8rem; padding: 0.45rem 0.2rem; } .stats-foot { margin: 0.75rem 0 0; font-size: 0.72rem; color: var(--muted); text-align: center; @@ -509,10 +535,21 @@

Recent activity

-

Latest payments & refunds across instances

- +

Latest 5 payments & refunds per currency

+
+
+

GOA

+
    +
  • Loading…
  • +
+
+
+

CHF

+
    +
  • Loading…
  • +
+
+
@@ -637,12 +674,14 @@ fillCurrency("goa", by.GOA); fillCurrency("chf", by.CHF); - var list = document.getElementById("st-activity"); - list.innerHTML = ""; - var acts = d.recent_activity || []; - if (!acts.length) { - list.innerHTML = '
  • No recent payments or refunds
  • '; - } else { + function fillActivityList(el, acts) { + if (!el) return; + el.innerHTML = ""; + acts = acts || []; + if (!acts.length) { + el.innerHTML = '
  • No recent payments or refunds
  • '; + return; + } acts.forEach(function (a) { var kind = (a.kind || "other").toLowerCase(); var li = document.createElement("li"); @@ -659,9 +698,27 @@ '
    ' + "" + esc(a.ts_human || a.ts || "") + "" + "
    "; - list.appendChild(li); + el.appendChild(li); }); } + // Prefer split-by-currency (≤5 each); fall back to flat list split by amount prefix + var byCur = {}; + (d.recent_activity_by_currency || []).forEach(function (block) { + byCur[(block.currency || "").toUpperCase()] = block.items || []; + }); + if (!Object.keys(byCur).length && (d.recent_activity || []).length) { + byCur.GOA = []; + byCur.CHF = []; + (d.recent_activity || []).forEach(function (a) { + var cur = String(a.amount || "").split(":")[0].toUpperCase(); + if (cur === "GOA") byCur.GOA.push(a); + else if (cur === "CHF") byCur.CHF.push(a); + }); + byCur.GOA = byCur.GOA.slice(0, 5); + byCur.CHF = byCur.CHF.slice(0, 5); + } + fillActivityList(document.getElementById("st-activity-goa"), byCur.GOA); + fillActivityList(document.getElementById("st-activity-chf"), byCur.CHF); if (foot) { foot.textContent = (d.dual_currency ? "Dual-currency · " : "") + @@ -725,8 +782,10 @@ }) .catch(function () { if (foot) foot.innerHTML = 'Stats offline · bank.hacktivism.ch'; - var list = document.getElementById("st-activity"); - if (list) list.innerHTML = '
  • Stats offline
  • '; + ["st-activity-goa", "st-activity-chf"].forEach(function (id) { + var list = document.getElementById(id); + if (list) list.innerHTML = '
  • Stats offline
  • '; + }); }); } load(); diff --git a/scripts/taler-merchant/landing-stats-merchant.sh b/scripts/taler-merchant/landing-stats-merchant.sh index 5a8487f..cee1bc7 100644 --- a/scripts/taler-merchant/landing-stats-merchant.sh +++ b/scripts/taler-merchant/landing-stats-merchant.sh @@ -206,18 +206,22 @@ for item in $SCHEMAS; do REFUNDS=$((REFUNDS + r)) done -# --- recent activity --- -PAY_LIMIT=10 -REF_LIMIT=5 -{ - echo "SELECT * FROM (" - echo "(" - echo "SELECT * FROM (" - first=1 - for item in $SCHEMAS; do - sch=${item%%:*} - if [ "$first" = 1 ]; then first=0; else echo " UNION ALL "; fi - cat <>'amount',''), ':', 1)) = upper('${cur}') SQL - done - if [ "$first" = 1 ]; then - echo "SELECT 0::bigint AS ts, ''::text AS kind, ''::text AS order_id, ''::text AS amount, ''::text AS summary, ''::text AS status WHERE false" - fi - echo ") pay ORDER BY ts DESC LIMIT ${PAY_LIMIT}" - echo ")" - echo "UNION ALL" - echo "(" - echo "SELECT * FROM (" - first=1 - for item in $SCHEMAS; do - sch=${item%%:*} - if [ "$first" = 1 ]; then first=0; else echo " UNION ALL "; fi - cat <"$out" || abort "activity query failed for $cur" +} + +# Build JSON array of activity items from a TSV file +activity_tsv_to_json() { + local tsv_file="$1" + local json="[" + local af=1 + local ts kind oid amt sum st sec human_fmt iso + while IFS=$'\t' read -r ts kind oid amt sum st; do + [ -z "${ts:-}" ] && continue + [ "$ts" = "0" ] && [ -z "$kind" ] && continue + sec=$(awk -v t="$ts" 'BEGIN{printf "%d", int(t/1000000)}') + human_fmt=$(date -d "@${sec}" +"%Y-%m-%d %H:%M %Z" 2>/dev/null || echo "$sec") + iso=$(date -d "@${sec}" +"%Y-%m-%dT%H:%M:%S%z" 2>/dev/null | sed -E 's/([+-][0-9]{2})([0-9]{2})$/\1:\2/' || true) + if [ "$af" = 1 ]; then af=0; else json="${json},"; fi + json="${json} + { + \"ts_us\": ${ts:-0}, + \"ts\": $(json_str "$iso"), + \"ts_human\": $(json_str "$human_fmt"), + \"kind\": $(json_str "$kind"), + \"order_id\": $(json_str "$oid"), + \"amount\": $(json_str "$amt"), + \"summary\": $(json_str "$sum"), + \"status\": $(json_str "$st") + }" + done <"$tsv_file" + json="${json} + ]" + printf '%s' "$json" +} + +query_activity_for_currency GOA "/tmp/merch_act_goa_$$.tsv" +query_activity_for_currency CHF "/tmp/merch_act_chf_$$.tsv" +ACT_GOA_JSON=$(activity_tsv_to_json "/tmp/merch_act_goa_$$.tsv") +ACT_CHF_JSON=$(activity_tsv_to_json "/tmp/merch_act_chf_$$.tsv") +rm -f "/tmp/merch_act_goa_$$.tsv" "/tmp/merch_act_chf_$$.tsv" + +# Flat recent_activity: GOA then CHF (compat; landing prefers by_currency) +ACT_JSON="[" +af=1 +for block in "$ACT_GOA_JSON" "$ACT_CHF_JSON"; do + # strip outer [ ] and inject if non-empty + inner=$(printf '%s' "$block" | sed '1s/^\s*\[//; $s/\]\s*$//') + # empty array → skip + if printf '%s' "$inner" | grep -q '"kind"'; then + if [ "$af" = 1 ]; then af=0; else ACT_JSON="${ACT_JSON},"; fi + ACT_JSON="${ACT_JSON}${inner}" fi - echo ") ref ORDER BY ts DESC LIMIT ${REF_LIMIT}" - echo ")" - echo ") act ORDER BY ts DESC;" -} | psql_pipe >"/tmp/merch_act_$$.tsv" || abort "activity query failed" -ACT_TSV=$(cat "/tmp/merch_act_$$.tsv" 2>/dev/null || true) -rm -f "/tmp/merch_act_$$.tsv" +done +ACT_JSON="${ACT_JSON} + ]" + +ACT_BY_CUR_JSON="[ + { + \"currency\": \"GOA\", + \"limit\": ${ACT_PER_CURRENCY}, + \"items\": ${ACT_GOA_JSON} + }, + { + \"currency\": \"CHF\", + \"limit\": ${ACT_PER_CURRENCY}, + \"items\": ${ACT_CHF_JSON} + } + ]" # by_currency JSON — GOA then CHF then rest CUR_JSON="[" @@ -297,30 +361,6 @@ done CUR_JSON="${CUR_JSON} ]" -ACT_JSON="[" -af=1 -while IFS=$'\t' read -r ts kind oid amt sum st; do - [ -z "${ts:-}" ] && continue - [ "$ts" = "0" ] && [ -z "$kind" ] && continue - sec=$(awk -v t="$ts" 'BEGIN{printf "%d", int(t/1000000)}') - human_fmt=$(date -d "@${sec}" +"%Y-%m-%d %H:%M %Z" 2>/dev/null || echo "$sec") - iso=$(date -d "@${sec}" +"%Y-%m-%dT%H:%M:%S%z" 2>/dev/null | sed -E 's/([+-][0-9]{2})([0-9]{2})$/\1:\2/' || true) - if [ "$af" = 1 ]; then af=0; else ACT_JSON="${ACT_JSON},"; fi - ACT_JSON="${ACT_JSON} - { - \"ts_us\": ${ts:-0}, - \"ts\": $(json_str "$iso"), - \"ts_human\": $(json_str "$human_fmt"), - \"kind\": $(json_str "$kind"), - \"order_id\": $(json_str "$oid"), - \"amount\": $(json_str "$amt"), - \"summary\": $(json_str "$sum"), - \"status\": $(json_str "$st") - }" -done <<<"$ACT_TSV" -ACT_JSON="${ACT_JSON} - ]" - GEN=$(now_iso) HUMAN=$(now_human) @@ -374,6 +414,8 @@ cat >"$TMP" <