460 lines
13 KiB
Vue
460 lines
13 KiB
Vue
<template>
|
|
<div class="panel panel-default">
|
|
<div class="panel-body">
|
|
<ul class="nav nav-tabs election-tabs">
|
|
<li :class="{ active: activeTab === 'current' }">
|
|
<a href="#" @click.prevent="activeTab = 'current'">Terkini</a>
|
|
</li>
|
|
<li :class="{ active: activeTab === 'history' }">
|
|
<a href="#" @click.prevent="activeTab = 'history'">Sejarah Undian</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content election-tab-content">
|
|
<div class="tab-pane" :class="{ active: activeTab === 'current' }">
|
|
<div class="election-header">
|
|
<div class="election-header__title">
|
|
<div class="election-header__kicker">Undian Terkini</div>
|
|
<div class="election-header__name">{{ electionLatestHeading }}</div>
|
|
</div>
|
|
<div class="election-header__meta">
|
|
<span class="election-status" :class="electionStatusClass">{{ electionStatusLabel }}</span>
|
|
<label v-if="data.election.status == 2" class="election-label">
|
|
Undian bermula pada {{ data.election.start }}
|
|
</label>
|
|
<div class="election-header__meta-actions">
|
|
<button class="btn btn-success btn-xs"
|
|
v-if="Number(data.election.status) === 1 && data.user.role == 1"
|
|
@click="openStartElectionModal">
|
|
Mula Undian
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="election-actions form-group">
|
|
<button class="btn btn-primary" v-if="Number(data.election.status) === 3 && data.user.role == 1"
|
|
@click="util.showModal('#register-next-session-modal')">Daftar Sesi Mengundi Baru</button>
|
|
|
|
<router-link :to="{ name: 'Current Result' }" class="btn btn-info"
|
|
v-if="data.election.status == 2">
|
|
Keputusan Semasa
|
|
</router-link>
|
|
|
|
<button class="btn btn-danger" v-if="data.election.status == 2"
|
|
@click="util.showModal('#stop-election-modal')">Tamat Undian</button>
|
|
</div>
|
|
|
|
<div class="alert alert-info election-hint" v-if="Number(data.user.role) === 1">
|
|
<b>Makluman:</b> Sila semak & kemaskini <b>Maklumat Calon</b> dan <b>Anggota Koperasi</b>
|
|
(butiran
|
|
pengundi) untuk sesi ini sebelum memulakan undian.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane" :class="{ active: activeTab === 'history' }">
|
|
<div class="election-history">
|
|
<div class="election-history__title">Sejarah Undian</div>
|
|
<admin-data-table :headers="electionTableHeaders" :items="data.elections"
|
|
:loading="electionLoading" :items-per-page="10" :show-pagination="true"
|
|
empty-text="Tiada undian" :exportable="true" export-file-name="undian">
|
|
<template v-slot:item-actions="{ item }">
|
|
<router-link :to="{ name: 'Election Result', params: { election_id: item.id } }"
|
|
class="btn btn-info">
|
|
Ringkasan Keputusan
|
|
</router-link>
|
|
<a class="btn btn-warning" :href="getPDFurl(item.id)">Muat Turun PDF</a>
|
|
</template>
|
|
</admin-data-table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form @submit.prevent="start">
|
|
<div class="modal fade" id="start-election-modal" tabindex="-1" role="dialog"
|
|
aria-labelledby="start-election-modal" aria-hidden="true">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="exampleModalLabel">Mulakan Undian</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="start-election-name">Nama Undian</label>
|
|
<input id="start-election-name" v-model.trim="startForm.name" type="text"
|
|
name="name" autocomplete="off" class="form-control" placeholder="(Pilihan)"
|
|
readonly tabindex="-1" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="start-election-password">Kata Laluan</label>
|
|
<input id="start-election-password" v-model="startForm.password" type="password"
|
|
name="password" class="form-control" required autocomplete="current-password"
|
|
:disabled="startSubmitting" />
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="submit" class="btn btn-info" :disabled="startSubmitting">
|
|
{{ startSubmitting ? 'Memproses…' : 'Mula' }}
|
|
</button>
|
|
<input type="button" data-dismiss="modal" class="btn btn-default" value="Batal" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<form @submit.prevent="registerNextSession" id="register_next_form">
|
|
<div class="modal fade" id="register-next-session-modal" tabindex="-1" role="dialog"
|
|
aria-labelledby="register-next-session-modal" aria-hidden="true">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="registerNextModalLabel">Daftar Sesi Mengundi Baru</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-muted">Sesi baharu akan dibuka untuk pusingan undian seterusnya. Undian
|
|
yang
|
|
telah tamat kekal dalam rekod.</p>
|
|
<div class="form-group">
|
|
<label for="register-next-name">Nama Undian</label>
|
|
<input id="register-next-name" type="text" name="name" autocomplete="on"
|
|
class="form-control" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="register-next-password">Kata Laluan</label>
|
|
<input id="register-next-password" type="password" name="password"
|
|
class="form-control" required autocomplete="current-password" />
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<input type="submit" class="btn btn-primary" value="Daftar" />
|
|
<input type="button" data-dismiss="modal" class="btn btn-default" value="Batal" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<form @submit.prevent="stop" id="stop_form">
|
|
<div class="modal fade" id="stop-election-modal" tabindex="-1" role="dialog"
|
|
aria-labelledby="stop-election-modal" aria-hidden="true">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="exampleModalLabel">Tamat Undian</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="name">Nama Undian</label>
|
|
<input type="text" name="name" :value="data.election.name" autocomplete="on"
|
|
class="form-control" placeholder="(Optional)" readonly tabindex="-1" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Kata Laluan</label>
|
|
<input type="password" name="password" autocomplete="on" class="form-control"
|
|
required />
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<input type="submit" class="btn btn-info" value="Tamat Undian" />
|
|
<input type="button" data-dismiss="modal" class="btn btn-default" value="Batal" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.election-label {
|
|
display: inline-block;
|
|
margin-left: 10px;
|
|
font-size: 13px;
|
|
color: #ffffff;
|
|
background-color: #0a4c9c;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.election-tabs {
|
|
margin: 0 0 12px 0;
|
|
}
|
|
|
|
.election-tab-content>.tab-pane {
|
|
display: none;
|
|
}
|
|
|
|
.election-tab-content>.tab-pane.active {
|
|
display: block;
|
|
}
|
|
|
|
.election-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 14px 15px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.election-header__kicker {
|
|
font-size: 12px;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: #777;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.election-header__name {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
color: #222;
|
|
}
|
|
|
|
.election-status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
border: 1px solid transparent;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.election-status--draft {
|
|
background: #f5f5f5;
|
|
color: #555;
|
|
border-color: #e6e6e6;
|
|
}
|
|
|
|
.election-status--active {
|
|
background: #e8f5e9;
|
|
color: #2e7d32;
|
|
border-color: #c8e6c9;
|
|
}
|
|
|
|
.election-status--done {
|
|
background: #ffebee;
|
|
color: #c62828;
|
|
border-color: #ffcdd2;
|
|
}
|
|
|
|
.election-header__meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
.election-header__meta-actions .btn {
|
|
padding: 4px 10px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.election-actions {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.election-hint {
|
|
margin-top: 10px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.election-history {
|
|
margin-top: 4px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid #eee;
|
|
}
|
|
|
|
.election-history__title {
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
color: #666;
|
|
margin: 0 0 10px 0;
|
|
}
|
|
|
|
.center-header {
|
|
text-align: center;
|
|
}
|
|
|
|
/* Ensure Bootstrap modals appear above the admin floating header (z-index: 2000). */
|
|
.modal {
|
|
z-index: 2200;
|
|
}
|
|
|
|
.modal-backdrop {
|
|
z-index: 2190 !important;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
export default {
|
|
data: () => ({
|
|
activeTab: 'current',
|
|
electionLoading: false,
|
|
startSubmitting: false,
|
|
startForm: {
|
|
name: '',
|
|
password: ''
|
|
},
|
|
electionTableHeaders: [
|
|
{ title: 'Nama Undian', key: 'name', sortable: true },
|
|
{ title: 'Undian Mula', key: 'start', sortable: true },
|
|
{ title: 'Undian Tamat', key: 'end', sortable: true },
|
|
{ title: 'Tindakan', key: 'actions', sortable: false }
|
|
]
|
|
}),
|
|
created: function () {
|
|
this.refreshElection();
|
|
},
|
|
|
|
computed: {
|
|
electionLatestHeading: function () {
|
|
var e = this.data.election;
|
|
var n = e && e.name != null ? String(e.name).trim() : '';
|
|
return n !== '' ? n : 'Undian Terkini';
|
|
},
|
|
|
|
electionStatusLabel: function () {
|
|
var s = Number(this.data && this.data.election ? this.data.election.status : NaN);
|
|
if (s === 1) return 'Draf';
|
|
if (s === 2) return 'Sesi Mengundi Sedang Berjalan';
|
|
if (s === 3) return 'Tamat';
|
|
return '—';
|
|
},
|
|
|
|
electionStatusClass: function () {
|
|
var s = Number(this.data && this.data.election ? this.data.election.status : NaN);
|
|
if (s === 1) return 'election-status--draft';
|
|
if (s === 2) return 'election-status--active';
|
|
if (s === 3) return 'election-status--done';
|
|
return '';
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
|
|
openStartElectionModal: function () {
|
|
var e = this.data.election;
|
|
this.startForm.name = e && e.name != null ? String(e.name) : '';
|
|
this.startForm.password = '';
|
|
this.startSubmitting = false;
|
|
this.util.showModal('#start-election-modal');
|
|
},
|
|
|
|
stop: function () {
|
|
var vm = this;
|
|
// this.util.hideModal('#stop-election-modal');
|
|
this.util.notify('Stopping Election', 'loading');
|
|
axios.post(config.API + 'election/stop', $('#stop_form').serialize())
|
|
.then(response => {
|
|
$.notifyClose();
|
|
if (vm.util.showResult(response, 'success')) {
|
|
vm.refreshElection();
|
|
vm.data.election = response.data.election;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
$.notifyClose();
|
|
vm.util.showResult(error, 'error');
|
|
})
|
|
},
|
|
|
|
start: function () {
|
|
var vm = this;
|
|
if (vm.startSubmitting) {
|
|
return;
|
|
}
|
|
vm.startSubmitting = true;
|
|
this.util.notify('Memproses…', 'loading');
|
|
axios.post(config.API + 'election/start', {
|
|
name: vm.startForm.name,
|
|
password: vm.startForm.password
|
|
})
|
|
.then(response => {
|
|
$.notifyClose();
|
|
if (vm.util.showResult(response, 'success')) {
|
|
vm.refreshElection();
|
|
vm.data.election = response.data.election;
|
|
vm.startForm.password = '';
|
|
vm.util.hideModal('#start-election-modal');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
$.notifyClose();
|
|
vm.util.showResult(error, 'error');
|
|
})
|
|
.finally(function () {
|
|
vm.startSubmitting = false;
|
|
})
|
|
},
|
|
|
|
registerNextSession: function () {
|
|
var vm = this;
|
|
this.util.notify('Memproses…', 'loading');
|
|
axios.post(config.API + 'election/register-next', $('#register_next_form').serialize())
|
|
.then(response => {
|
|
$.notifyClose();
|
|
if (vm.util.showResult(response, 'success')) {
|
|
vm.refreshElection();
|
|
vm.data.election = response.data.election;
|
|
vm.util.hideModal('#register-next-session-modal');
|
|
axios.get(config.API + 'admin/information').then(function (info) {
|
|
vm.data.election = info.data.election;
|
|
vm.data.partylists = info.data.partylist;
|
|
vm.data.positions = info.data.position;
|
|
}).catch(function (err) {
|
|
vm.util.showResult(err, 'error');
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
$.notifyClose();
|
|
vm.util.showResult(error, 'error');
|
|
})
|
|
},
|
|
|
|
refreshElection: function () {
|
|
this.util.notify('Refreshing Election', 'loading');
|
|
var vm = this;
|
|
this.electionLoading = true;
|
|
axios.get(config.API + 'election')
|
|
.then(response => {
|
|
$.notifyClose();
|
|
vm.data.elections = response.data;
|
|
})
|
|
.catch(error => {
|
|
$.notifyClose();
|
|
vm.util.showResult(error, 'error');
|
|
})
|
|
.finally(function () {
|
|
vm.electionLoading = false;
|
|
})
|
|
},
|
|
|
|
|
|
getPDFurl(Id) {
|
|
// Construct and return the URL based on the item ID
|
|
return `/result/${Id}`;
|
|
}
|
|
}
|
|
}
|
|
</script> |