Allowance check
This commit is contained in:
@@ -1,90 +1,242 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4>Jawatan</h4><hr/>
|
||||
<ul class="list-group">
|
||||
<router-link :key="position.id" v-for="position in data.positions" class="list-group-item" :class="{'active':position.id==position_id}" tag="li" :to="{query:{position_id:position.id}}" exact replace>
|
||||
<template>
|
||||
<div class="result-page">
|
||||
<div class="result-header">
|
||||
<div>
|
||||
<div class="result-kicker">Keputusan Undian</div>
|
||||
<h4 class="result-title">{{ String(position_id) === '0' ? 'Dashboard Keputusan' :
|
||||
(getPosition(position_id) || 'Keputusan') }}</h4>
|
||||
<div class="result-subtitle">Kemaskini terakhir: <b>{{ last_update }}</b></div>
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<button class="btn btn-info" @click="refreshNominees()">
|
||||
<i class="fa fa-refresh"></i> Kemaskini Keputusan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="String(position_id) === '0'" class="result-stats">
|
||||
<div class="result-stat">
|
||||
<div class="result-stat__label">Jumlah Undi</div>
|
||||
<div class="result-stat__value">{{ totalVotes }}</div>
|
||||
</div>
|
||||
<div class="result-stat">
|
||||
<div class="result-stat__label">Bil. Jawatan</div>
|
||||
<div class="result-stat__value">{{ (data.positions || []).length }}</div>
|
||||
</div>
|
||||
<div class="result-stat">
|
||||
<div class="result-stat__label">Calon Tertinggi</div>
|
||||
<div class="result-stat__value result-stat__value--text">{{ topNomineeLabel }}</div>
|
||||
<div class="result-stat__hint">{{ topNomineeVotes }} undi</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-filters">
|
||||
<span class="result-filters__label">Jawatan:</span>
|
||||
<button type="button" class="result-chip" :class="{ 'is-active': String(position_id) === '0' }"
|
||||
@click="setPosition(0)">
|
||||
Semua
|
||||
</button>
|
||||
<button v-for="position in data.positions" :key="position.id" type="button" class="result-chip"
|
||||
:class="{ 'is-active': String(position.id) === String(position_id) }" @click="setPosition(position.id)">
|
||||
{{ position.name }}
|
||||
</router-link>
|
||||
<li class="list-group-item">
|
||||
<center>
|
||||
<button class="btn btn-info" @click="refreshNominees()">
|
||||
Kemaskini Keputusan<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
</center>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h4>Keputusan</h4><hr/>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ getPosition(position_id) }} - Results as of : {{ last_update }}</div>
|
||||
<div class="panel-body">
|
||||
<div id="chart" style="height: 300px; width: 300px"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="panel panel-default result-card">
|
||||
<div class="panel-heading result-card__heading">
|
||||
<div class="result-card__title">
|
||||
{{ String(position_id) === '0' ? 'Pemimpin Mengikut Jawatan' : 'Kedudukan Calon' }}
|
||||
</div>
|
||||
<div class="result-card__meta">
|
||||
{{
|
||||
String(position_id) === '0' ? 'Siapa mendahului (dengan beza undi)' :
|
||||
'Mengikut jawatan dipilih'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body result-card__body">
|
||||
<template v-if="String(position_id) === '0'">
|
||||
<div v-if="!leadersByPosition.length" class="result-empty">
|
||||
Tiada data untuk dipaparkan.
|
||||
</div>
|
||||
<div v-else class="result-leaders">
|
||||
<div v-for="p in leadersByPosition" :key="p.position_id" class="result-leader">
|
||||
<div class="result-leader__top">
|
||||
<div class="result-leader__position">{{ p.position_name }}</div>
|
||||
<div class="result-leader__meta">
|
||||
<span><b>{{ p.winner_votes }}</b> undi</span>
|
||||
<span class="result-leader__delta">(+{{ p.delta }} beza)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-leader__name">{{ p.winner_name }}</div>
|
||||
<div class="result-leader__bar">
|
||||
<div class="result-leader__bar-fill" :style="{ width: p.pct + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="!rankedNominees.length" class="result-empty">
|
||||
Tiada calon untuk jawatan ini.
|
||||
</div>
|
||||
<div v-else class="result-rank">
|
||||
<div v-for="n in rankedNominees" :key="n.id" class="result-rank__row">
|
||||
<div class="result-rank__top">
|
||||
<div class="result-rank__name">{{ n.name }}</div>
|
||||
<div class="result-rank__votes">
|
||||
<b>{{ n.votes }}</b> undi
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-rank__bar">
|
||||
<div class="result-rank__bar-fill" :style="{ width: n.pct + '%' }"></div>
|
||||
</div>
|
||||
<div class="result-rank__meta">{{ n.pct }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="panel panel-default result-card">
|
||||
<div class="panel-heading result-card__heading">
|
||||
<div class="result-card__title">
|
||||
{{ String(position_id) === '0' ? 'Analitik Ringkas' : 'Ringkasan Jawatan' }}
|
||||
</div>
|
||||
<div class="result-card__meta">
|
||||
{{
|
||||
String(position_id) === '0' ? 'Top calon & jumlah undi mengikut jawatan' : ('Jumlah undi:' +
|
||||
totalVotes) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body result-card__body">
|
||||
<template v-if="String(position_id) === '0'">
|
||||
<div class="result-mini-title">Top Calon (Overall)</div>
|
||||
<div id="top-chart" class="result-top-chart"></div>
|
||||
<div v-if="!topNominees.length" class="result-empty">Tiada data untuk dipaparkan.</div>
|
||||
|
||||
<div class="result-mini-title" style="margin-top: 14px;">Jumlah Undi Mengikut Jawatan</div>
|
||||
<div id="votes-by-position-chart" class="result-top-chart"></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="result-empty">
|
||||
Pilih “Semua” untuk melihat analitik keseluruhan. Untuk jawatan ini, rujuk senarai di
|
||||
kiri.
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default{
|
||||
export default {
|
||||
|
||||
created: function () {
|
||||
console.log(this)
|
||||
created: function () {
|
||||
this.refreshNominees();
|
||||
this.$nextTick(function(){
|
||||
this.initChart();
|
||||
this.$nextTick(function () {
|
||||
this.initCharts();
|
||||
});
|
||||
},
|
||||
|
||||
watch: {
|
||||
position_id : function () {
|
||||
this.initChart();
|
||||
position_id: function () {
|
||||
var vm = this;
|
||||
this.$nextTick(function () {
|
||||
vm.initCharts();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setPosition: function (id) {
|
||||
// Prevent Vue Router NavigationDuplicated when clicking active chip
|
||||
var target = (!id || String(id) === '0') ? '0' : String(id);
|
||||
if (String(this.position_id) === target) return;
|
||||
|
||||
var q = Object.assign({}, this.$route.query);
|
||||
if (!id || String(id) === '0') delete q.position_id;
|
||||
else q.position_id = String(id);
|
||||
this.$router.replace({ query: q }).catch(function () { });
|
||||
},
|
||||
|
||||
refreshNominees: function () {
|
||||
var vm = this;
|
||||
this.util.notify('Refreshing results', 'loading');
|
||||
axios.get(config.API+'nominee')
|
||||
.then(response=>{
|
||||
// Always fetch all nominees (needed for overall dashboards and name lookups)
|
||||
axios.get(config.API + 'nominee')
|
||||
.then(response => {
|
||||
$.notifyClose();
|
||||
vm.data.nominees = response.data;
|
||||
vm.refreshResults();
|
||||
})
|
||||
.catch(error=>{
|
||||
.catch(error => {
|
||||
$.notifyClose();
|
||||
vm.util.showResult(error);
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
refreshResults: function () {
|
||||
var vm = this;
|
||||
this.util.notify('Refreshing results', 'loading');
|
||||
axios.get(config.API+'election/results')
|
||||
.then(response=>{
|
||||
axios.get(config.API + 'election/results')
|
||||
.then(response => {
|
||||
$.notifyClose();
|
||||
vm.data.results = response.data;
|
||||
vm.data.last_update= new Date();
|
||||
vm.initChart();
|
||||
vm.data.last_update = new Date();
|
||||
vm.$nextTick(function () {
|
||||
vm.initCharts();
|
||||
});
|
||||
})
|
||||
.catch(error=>{
|
||||
.catch(error => {
|
||||
$.notifyClose();
|
||||
vm.util.showResult(error);
|
||||
})
|
||||
},
|
||||
|
||||
initChart: function () {
|
||||
$.plot($('#chart'), this.datas, {
|
||||
series: {
|
||||
pie: {
|
||||
show: true,
|
||||
innerRadius: 0.5
|
||||
}
|
||||
initCharts: function () {
|
||||
// Clear any previously rendered plots to avoid overlap when switching views
|
||||
if ($('#top-chart').length) $('#top-chart').empty();
|
||||
if ($('#votes-by-position-chart').length) $('#votes-by-position-chart').empty();
|
||||
|
||||
// All positions summary
|
||||
if (String(this.position_id) === '0') {
|
||||
if ($('#top-chart').length) {
|
||||
var ticks = this.topNominees.map(function (n, i) { return [i, n.label]; });
|
||||
var series = [{
|
||||
label: 'Undi',
|
||||
data: this.topNominees.map(function (n, i) { return [i, n.votes]; }),
|
||||
bars: { show: true, barWidth: 0.6, align: 'center', fill: 0.85, lineWidth: 0 }
|
||||
}];
|
||||
$.plot($('#top-chart'), series, {
|
||||
xaxis: { ticks: ticks, rotateTicks: 45 },
|
||||
yaxis: { min: 0, tickDecimals: 0 },
|
||||
grid: { hoverable: true, borderColor: '#e5e7eb' }
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
if ($('#votes-by-position-chart').length) {
|
||||
var pt = this.positionTotals;
|
||||
var ptTicks = pt.map(function (p, i) { return [i, p.label]; });
|
||||
var ptSeries = [{
|
||||
label: 'Undi',
|
||||
data: pt.map(function (p, i) { return [i, p.votes]; }),
|
||||
bars: { show: true, barWidth: 0.6, align: 'center', fill: 0.55, lineWidth: 0 }
|
||||
}];
|
||||
$.plot($('#votes-by-position-chart'), ptSeries, {
|
||||
xaxis: { ticks: ptTicks, rotateTicks: 45 },
|
||||
yaxis: { min: 0, tickDecimals: 0 },
|
||||
grid: { hoverable: true, borderColor: '#e5e7eb' }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For per-position view, we intentionally do not render charts (UI request).
|
||||
},
|
||||
|
||||
getPosition: function (id) {
|
||||
@@ -113,13 +265,12 @@ export default{
|
||||
computed: {
|
||||
last_update: function () {
|
||||
let x = this.data.last_update;
|
||||
return x.toDateString() +' '+x.toLocaleTimeString();
|
||||
if (!x) return '-';
|
||||
return x.toDateString() + ' ' + x.toLocaleTimeString();
|
||||
},
|
||||
|
||||
position_id: function () {
|
||||
return this.$route.query.position_id ?
|
||||
this.$route.query.position_id :
|
||||
this.data.positions[0]['id'];
|
||||
return this.$route.query.position_id ? this.$route.query.position_id : 0;
|
||||
},
|
||||
|
||||
datas: function () {
|
||||
@@ -127,7 +278,7 @@ export default{
|
||||
var data = [];
|
||||
var nominees = this.data.nominees;
|
||||
for (var i in nominees) {
|
||||
if (nominees[i]['position_id']== this.position_id){
|
||||
if (nominees[i]['position_id'] == this.position_id) {
|
||||
let row = [];
|
||||
row['label'] = nominees[i]['name'];
|
||||
row['data'] = [[1, this.getVotes(nominees[i]['id'])]];
|
||||
@@ -135,7 +286,427 @@ export default{
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
positionTotals: function () {
|
||||
// Total votes per position (for "Semua" dashboard)
|
||||
var results = this.data.results || [];
|
||||
var totals = {};
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var pid = results[i].position_id;
|
||||
var v = Number(results[i].votes || 0);
|
||||
totals[pid] = (totals[pid] || 0) + v;
|
||||
}
|
||||
var positions = this.data.positions || [];
|
||||
var rows = positions.map(function (p) {
|
||||
return { position_id: p.id, label: p.name, votes: totals[p.id] || 0 };
|
||||
});
|
||||
rows.sort(function (a, b) { return (b.votes || 0) - (a.votes || 0); });
|
||||
return rows;
|
||||
},
|
||||
|
||||
topNominees: function () {
|
||||
// Highest vote nominees across all positions (sum by nominee_id)
|
||||
var results = this.data.results || [];
|
||||
var sum = {};
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var nid = results[i].nominee_id;
|
||||
var v = Number(results[i].votes || 0);
|
||||
sum[nid] = (sum[nid] || 0) + v;
|
||||
}
|
||||
var nominees = this.data.nominees || [];
|
||||
var nameById = {};
|
||||
for (var j = 0; j < nominees.length; j++) {
|
||||
nameById[nominees[j].id] = nominees[j].name;
|
||||
}
|
||||
var rows = Object.keys(sum).map(function (nid) {
|
||||
return { id: Number(nid), label: nameById[nid] || ('Nominee #' + nid), votes: sum[nid] };
|
||||
});
|
||||
rows.sort(function (a, b) { return (b.votes || 0) - (a.votes || 0); });
|
||||
return rows.slice(0, 8);
|
||||
},
|
||||
|
||||
topNomineeLabel: function () {
|
||||
return this.topNominees && this.topNominees[0] ? this.topNominees[0].label : '-';
|
||||
},
|
||||
|
||||
topNomineeVotes: function () {
|
||||
return this.topNominees && this.topNominees[0] ? (this.topNominees[0].votes || 0) : 0;
|
||||
},
|
||||
|
||||
leadersByPosition: function () {
|
||||
// For each position: winner + runner-up + margin
|
||||
var positions = this.data.positions || [];
|
||||
var nominees = this.data.nominees || [];
|
||||
var results = this.data.results || [];
|
||||
|
||||
var posName = {};
|
||||
positions.forEach(function (p) { posName[p.id] = p.name; });
|
||||
|
||||
var nomineeName = {};
|
||||
nominees.forEach(function (n) { nomineeName[n.id] = n.name; });
|
||||
|
||||
var byPos = {};
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var r = results[i];
|
||||
var pid = r.position_id;
|
||||
if (!byPos[pid]) byPos[pid] = [];
|
||||
byPos[pid].push({ nominee_id: r.nominee_id, votes: Number(r.votes || 0) });
|
||||
}
|
||||
|
||||
var rows = [];
|
||||
for (var j = 0; j < positions.length; j++) {
|
||||
var pid2 = positions[j].id;
|
||||
var arr = (byPos[pid2] || []).slice().sort(function (a, b) { return (b.votes || 0) - (a.votes || 0); });
|
||||
var w = arr[0] || { nominee_id: null, votes: 0 };
|
||||
var r2 = arr[1] || { nominee_id: null, votes: 0 };
|
||||
var total = arr.reduce(function (s, x) { return s + (x.votes || 0); }, 0);
|
||||
var pct = total > 0 ? Math.round((w.votes / total) * 100) : 0;
|
||||
rows.push({
|
||||
position_id: pid2,
|
||||
position_name: posName[pid2] || ('Jawatan #' + pid2),
|
||||
winner_id: w.nominee_id,
|
||||
winner_name: nomineeName[w.nominee_id] || '-',
|
||||
winner_votes: w.votes || 0,
|
||||
runnerup_id: r2.nominee_id,
|
||||
runnerup_name: nomineeName[r2.nominee_id] || '-',
|
||||
runnerup_votes: r2.votes || 0,
|
||||
delta: Math.max(0, (w.votes || 0) - (r2.votes || 0)),
|
||||
pct: pct
|
||||
});
|
||||
}
|
||||
// Sort: show positions with most votes first
|
||||
rows.sort(function (a, b) { return (b.winner_votes || 0) - (a.winner_votes || 0); });
|
||||
return rows;
|
||||
},
|
||||
|
||||
rankedNominees: function () {
|
||||
var nominees = this.data.nominees || [];
|
||||
var rows = [];
|
||||
for (var i = 0; i < nominees.length; i++) {
|
||||
if (String(nominees[i].position_id) !== String(this.position_id)) continue;
|
||||
var votes = this.getVotes(nominees[i].id);
|
||||
rows.push({
|
||||
id: nominees[i].id,
|
||||
name: nominees[i].name,
|
||||
votes: votes
|
||||
});
|
||||
}
|
||||
rows.sort(function (a, b) { return (b.votes || 0) - (a.votes || 0); });
|
||||
var total = rows.reduce(function (s, r) { return s + (r.votes || 0); }, 0);
|
||||
return rows.map(function (r) {
|
||||
var pct = total > 0 ? Math.round((r.votes / total) * 100) : 0;
|
||||
return Object.assign({}, r, { pct: pct });
|
||||
});
|
||||
},
|
||||
|
||||
totalVotes: function () {
|
||||
// In "All" view use all results; otherwise use per-position ranking
|
||||
if (String(this.position_id) === '0') {
|
||||
var results = this.data.results || [];
|
||||
return results.reduce(function (s, r) { return s + Number(r.votes || 0); }, 0);
|
||||
}
|
||||
var rows = this.rankedNominees;
|
||||
return rows.reduce(function (s, r) { return s + (r.votes || 0); }, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.result-page {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.result-kicker {
|
||||
color: #2f80ed;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
margin: 0 0 6px;
|
||||
font-weight: 800;
|
||||
color: #17324d;
|
||||
}
|
||||
|
||||
.result-subtitle {
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.result-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.result-stat {
|
||||
border: 1px solid #e6edf5;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%);
|
||||
padding: 12px 14px;
|
||||
box-shadow: 0 10px 22px rgba(35, 64, 97, 0.06);
|
||||
}
|
||||
|
||||
.result-stat__label {
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.result-stat__value {
|
||||
font-size: 22px;
|
||||
font-weight: 900;
|
||||
color: #17324d;
|
||||
}
|
||||
|
||||
.result-stat__value--text {
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.result-stat__hint {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.result-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 10px 0 16px;
|
||||
}
|
||||
|
||||
.result-filters__label {
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.result-chip {
|
||||
border: 1px solid #d1d5db;
|
||||
background: #fff;
|
||||
color: #374151;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.result-chip:hover {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.result-chip.is-active {
|
||||
background: #1976d2;
|
||||
border-color: #1976d2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 12px 28px rgba(35, 64, 97, 0.08);
|
||||
}
|
||||
|
||||
.result-card__heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
background: linear-gradient(135deg, #1d4e89, #2f80ed);
|
||||
color: #fff;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.result-card__title {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.result-card__meta {
|
||||
opacity: 0.95;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.result-card__body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.result-top-chart {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
.result-mini-title {
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: #17324d;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.result-empty {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.result-leaders {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.result-leader {
|
||||
padding: 12px 12px;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%);
|
||||
}
|
||||
|
||||
.result-leader__top {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.result-leader__position {
|
||||
font-weight: 900;
|
||||
color: #17324d;
|
||||
}
|
||||
|
||||
.result-leader__meta {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
color: #17324d;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.result-leader__delta {
|
||||
color: #2f80ed;
|
||||
}
|
||||
|
||||
.result-leader__name {
|
||||
font-weight: 800;
|
||||
color: #17324d;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.result-leader__bar {
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: #e8f0fb;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-leader__bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #2f80ed, #1d4e89);
|
||||
}
|
||||
|
||||
.result-leader__sub {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-rank {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.result-rank__row {
|
||||
padding: 12px 12px;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%);
|
||||
}
|
||||
|
||||
.result-rank__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.result-rank__name {
|
||||
font-weight: 800;
|
||||
color: #17324d;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.result-rank__votes {
|
||||
color: #17324d;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.result-rank__bar {
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: #e8f0fb;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-rank__bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #2f80ed, #1d4e89);
|
||||
}
|
||||
|
||||
.result-rank__meta {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.result-header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.result-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user