Files
E-Vote/resources/assets/js/components/demo/admin/home/final.vue
T

435 lines
12 KiB
Vue

<template>
<div class="final-dashboard">
<div class="container-fluid final-dashboard__container">
<div class="row final-split-row">
<div class="col-md-6 final-split-col final-split-col--dashboard">
<div class="final-dashboard__shell">
<div class="final-header">
<div>
<div class="final-kicker">Keputusan akhir</div>
<h4 class="final-title">Dashboard keputusan</h4>
<div class="final-subtitle">Carta interaktif mengikut jawatan</div>
</div>
<div class="final-actions">
<button type="button" class="btn btn-info" :disabled="finalLoading" @click="loadData">
<i class="fa" :class="finalLoading ? 'fa-refresh fa-spin' : 'fa-refresh'"></i>
Kemaskini
</button>
</div>
</div>
<div class="final-vote-summary">
<div class="final-section-title">Ringkasan undian</div>
<div class="final-stats">
<div class="final-stat">
<div class="final-stat__label">Jumlah undi</div>
<div class="final-stat__value">{{ dashboardTotalVotes }}</div>
</div>
<div class="final-stat">
<div class="final-stat__label">Bil. jawatan</div>
<div class="final-stat__value">{{ positions.length }}</div>
</div>
<div class="final-stat">
<div class="final-stat__label">Calon terunggul</div>
<div class="final-stat__value final-stat__value--text">{{ topOverallLabel }}</div>
<div class="final-stat__hint">{{ topOverallVotes }} undi</div>
</div>
</div>
</div>
<div class="final-filters">
<span class="final-filters__label">Jawatan:</span>
<button type="button" class="final-chip" :class="{ 'is-active': selectedPositionId === 0 }" @click="setPosition(0)">
Semua
</button>
<button
v-for="position in positions"
:key="position.id"
type="button"
class="final-chip"
:class="{ 'is-active': selectedPositionId === position.id }"
@click="setPosition(position.id)"
>
{{ position.name }}
</button>
</div>
<div v-if="!finalLoading && positions.length === 0" class="final-empty">
Tiada jawatan untuk pilihan raya ini.
</div>
<div v-else class="row final-charts-row final-charts-row--split">
<div class="col-xs-12">
<div class="panel panel-default final-card">
<div class="panel-heading final-card__heading">
<div class="final-card__title">{{ mainChartTitle }}</div>
<div class="final-card__meta">{{ mainChartSubtitle }}</div>
</div>
<div class="panel-body final-card__body">
<div class="final-chart-wrap">
<bar-chart v-if="mainChartReady" v-bind="mainBarChartProps" />
</div>
</div>
</div>
</div>
<div class="col-xs-12">
<div class="panel panel-default final-card">
<div class="panel-heading final-card__heading final-card__heading--alt">
<div class="final-card__title">{{ sideChartTitle }}</div>
<div class="final-card__meta">{{ sideChartSubtitle }}</div>
</div>
<div class="panel-body final-card__body">
<div class="final-chart-wrap final-chart-wrap--doughnut">
<pie-chart v-if="mainChartReady" v-bind="sidePieChartProps" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 final-split-col final-split-col--attendance">
<div class="final-attendance-shell">
<div class="final-attendance-shell__head">
<div>
<div class="final-attendance-shell__kicker">Kehadiran</div>
<h4 class="final-attendance-shell__title">Ringkasan kehadiran</h4>
<div v-if="attendanceSummary" class="final-attendance__meta">
Kadar daftar: <b>{{ attendanceSummary.attendance_rate }}%</b>
<span class="final-attendance__meta-sep">·</span>
{{ attendanceSummary.marked_present }} / {{ attendanceSummary.total_registered }} pengundi
</div>
</div>
</div>
<div v-if="!attendanceSummaryReady" class="final-attendance__state">Memuatkan kehadiran</div>
<div v-else-if="attendanceSummary" class="final-attendance__body">
<div
v-if="attendanceSummary.total_registered > 0"
class="final-attendance__charts final-attendance__charts--standalone"
>
<div class="final-attendance__chart-block">
<div class="final-attendance__chart-caption">Taburan kaedah kehadiran</div>
<div class="final-attendance__chart-wrap final-attendance__chart-wrap--donut">
<pie-chart v-bind="attendanceBreakdownPieProps" />
</div>
</div>
<div v-if="showAttendanceFizikalChart" class="final-attendance__chart-block">
<div class="final-attendance__chart-caption">Status pendaftaran fizikal</div>
<div class="final-attendance__chart-wrap final-attendance__chart-wrap--bar">
<bar-chart v-bind="attendanceFizikalBarProps" />
</div>
</div>
</div>
</div>
<div v-else class="final-attendance__state final-attendance__state--muted">
Ringkasan kehadiran tidak dapat dimuatkan. Semak sambungan atau log masuk pentadbir.
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BarChart from '../../../common/BarChart.vue';
import PieChart from '../../../common/PieChart.vue';
export default {
components: {
BarChart,
PieChart
},
data: function () {
return {
nominees: [],
results: [],
positions: [],
finalLoading: false,
selectedPositionId: 0,
attendanceSummary: null,
attendanceSummaryReady: false
};
},
computed: {
election_id: function () {
return this.$route.params.election_id;
},
no_votes: function () {
var nominees = this.nominees;
var results = this.results;
var no_votes = [];
for (var i in nominees) {
var hasvote = false;
for (var y in results) {
if (results[y]['nominee_id'] == nominees[i]['id']) {
hasvote = true;
}
}
if (!hasvote) {
no_votes.push(nominees[i]);
}
}
return no_votes;
},
dashboardTotalVotes: function () {
return this.results.reduce(function (s, r) {
return s + Number(r.votes || 0);
}, 0);
},
topOverallLabel: function () {
if (!this.results.length) {
return '—';
}
var best = this.results.reduce(function (a, b) {
return Number(b.votes || 0) > Number(a.votes || 0) ? b : a;
});
var n = this.getNominee(best.nominee_id);
return (n && n.name) ? n.name : '—';
},
topOverallVotes: function () {
if (!this.results.length) {
return 0;
}
return this.results.reduce(function (m, r) {
return Math.max(m, Number(r.votes || 0));
}, 0);
},
mainChartTitle: function () {
if (this.selectedPositionId === 0) {
return 'Jumlah undi mengikut jawatan';
}
var p = this.positions.find(function (x) {
return Number(x.id) === Number(this.selectedPositionId);
}, this);
return (p && p.name) ? 'Undi calon — ' + p.name : 'Undi calon';
},
mainChartSubtitle: function () {
return this.selectedPositionId === 0
? 'Perbandingan antara jawatan'
: 'Carta mengikut calon';
},
sideChartTitle: function () {
return this.selectedPositionId === 0 ? 'Taburan undi (jawatan)' : 'Peratus undi';
},
sideChartSubtitle: function () {
return this.selectedPositionId === 0 ? 'Perkongsian jumlah undi' : 'Bahagian setiap calon';
},
showAttendanceFizikalChart: function () {
if (!this.attendanceSummary) {
return false;
}
return Number(this.attendanceSummary.fizikal || 0) > 0;
},
mainChartReady: function () {
return !this.finalLoading && this.positions.length > 0;
},
positionTotalRows: function () {
var vm = this;
return this.positions.map(function (pos) {
var t = vm.results
.filter(function (r) {
return Number(r.position_id) === Number(pos.id);
})
.reduce(function (s, r) {
return s + Number(r.votes || 0);
}, 0);
return { id: pos.id, name: pos.name, total: t };
});
},
mainBarChartProps: function () {
if (!this.mainChartReady) {
return {};
}
if (this.selectedPositionId === 0) {
var rows = this.positionTotalRows;
return {
labels: rows.map(function (r) {
return r.name.length > 22 ? r.name.slice(0, 20) + '…' : r.name;
}),
values: rows.map(function (r) {
return r.total;
}),
datasetLabel: 'Jumlah undi'
};
}
var rows = this.getSinglePositionChartRows();
return {
labels: rows.map(function (r) {
return r.label;
}),
values: rows.map(function (r) {
return r.votes;
}),
datasetLabel: 'Undi'
};
},
sidePieChartProps: function () {
if (!this.mainChartReady) {
return {};
}
if (this.selectedPositionId === 0) {
var rows = this.positionTotalRows;
return {
labels: rows.map(function (r) {
return r.name;
}),
values: rows.map(function (r) {
return r.total;
})
};
}
var rows = this.getSinglePositionChartRows();
return {
labels: rows.map(function (r) {
return r.label;
}),
values: rows.map(function (r) {
return r.votes;
})
};
},
attendanceBreakdownPieProps: function () {
if (!this.attendanceSummary || this.attendanceSummary.total_registered <= 0) {
return { labels: [], values: [] };
}
var s = this.attendanceSummary;
return {
labels: ['Fizikal', 'Maya', 'Belum ditetapkan'],
values: [
Number(s.fizikal || 0),
Number(s.maya || 0),
Number(s.unset || 0)
]
};
},
attendanceFizikalBarProps: function () {
if (!this.showAttendanceFizikalChart || !this.attendanceSummary) {
return { labels: [], values: [] };
}
var s = this.attendanceSummary;
return {
labels: ['Menunggu pengesahan', 'Disahkan'],
values: [
Number(s.fizikal_registration_pending || 0),
Number(s.fizikal_registration_verified || 0)
],
datasetLabel: 'Pengundi'
};
}
},
methods: {
setPosition: function (id) {
this.selectedPositionId = id === 0 ? 0 : Number(id);
},
getSinglePositionChartRows: function () {
var pid = this.selectedPositionId;
var vm = this;
var rows = this.results
.filter(function (r) {
return Number(r.position_id) === Number(pid);
})
.map(function (r) {
var n = vm.getNominee(r.nominee_id) || {};
var label = n.name || ('#' + r.nominee_id);
if (label.length > 28) {
label = label.slice(0, 26) + '…';
}
return {
label: label,
votes: Number(r.votes || 0)
};
});
this.no_votes.forEach(function (nv) {
if (Number(nv.position_id) === Number(pid)) {
var label = nv.name || '';
if (label.length > 28) {
label = label.slice(0, 26) + '…';
}
rows.push({
label: label || 'Calon',
votes: 0
});
}
});
rows.sort(function (a, b) {
return b.votes - a.votes;
});
return rows;
},
loadData: function () {
var vm = this;
this.util.notify('Loading please wait...', 'loading');
this.finalLoading = true;
this.attendanceSummaryReady = false;
this.attendanceSummary = null;
axios
.get(config.API + 'election/' + this.election_id + '/attendance-summary')
.then(function (r) {
vm.attendanceSummary = r.data;
})
.catch(function () {
vm.attendanceSummary = null;
})
.finally(function () {
vm.attendanceSummaryReady = true;
});
axios
.get(config.API + 'election/result/' + this.election_id)
.then(function (response) {
$.notifyClose();
vm.nominees = response.data.nominee || [];
vm.results = response.data.result || [];
vm.positions = response.data.position || [];
})
.catch(function (error) {
$.notifyClose();
vm.util.showResult(error, 'error');
})
.finally(function () {
vm.finalLoading = false;
});
},
getNominee: function (id) {
var nominees = this.nominees;
for (var i in nominees) {
if (id == nominees[i]['id']) {
return nominees[i];
}
}
return {};
}
},
created: function () {
this.util.setTitle('MyKoPKB - Keputusan akhir');
this.loadData();
}
};
</script>
<style scoped src="./final.css"></style>