Initial commit
This commit is contained in:
421
app/components/viewer/Dashboard.vue
Normal file
421
app/components/viewer/Dashboard.vue
Normal file
@@ -0,0 +1,421 @@
|
||||
<script setup>
|
||||
const costChartOptions = {
|
||||
chart: {
|
||||
type: 'column',
|
||||
},
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
xAxis: {
|
||||
categories: ['T10', 'T11', 'T12', 'T1', 'T2', 'T3'],
|
||||
crosshair: true,
|
||||
accessibility: {
|
||||
description: 'Tháng',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: 'Triệu VNĐ',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
valueSuffix: ' (triệu VNĐ)',
|
||||
},
|
||||
plotOptions: {
|
||||
column: {
|
||||
pointPadding: 0,
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Chi phí',
|
||||
data: [180, 195, 210, 225, 205, 215],
|
||||
showInLegend: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const efficiencyChartOptions = {
|
||||
chart: {
|
||||
type: 'line',
|
||||
},
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
xAxis: {
|
||||
categories: [
|
||||
'T10',
|
||||
'T11',
|
||||
'T12',
|
||||
'T1',
|
||||
'T2',
|
||||
'T3',
|
||||
'T4',
|
||||
'T5',
|
||||
'T6',
|
||||
'T7',
|
||||
'T8',
|
||||
'T9',
|
||||
'T10',
|
||||
],
|
||||
crosshair: true,
|
||||
accessibility: {
|
||||
description: 'Tháng',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: '%',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
valueSuffix: '%',
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
label: {
|
||||
connectorAllowed: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Hiệu suất',
|
||||
data: [50, 62, 75, 78, 80, 74, 79, 70, 68, 73, 82, 76, 77],
|
||||
showInLegend: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const alloChartOptions = {
|
||||
chart: {
|
||||
type: 'pie',
|
||||
zooming: {
|
||||
type: 'xy',
|
||||
},
|
||||
panning: {
|
||||
enabled: true,
|
||||
type: 'xy',
|
||||
},
|
||||
panKey: 'shift',
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
tooltip: {
|
||||
valueSuffix: '%',
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: [
|
||||
{
|
||||
enabled: true,
|
||||
distance: 20,
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
distance: -40,
|
||||
format: '{point.percentage:.1f}%',
|
||||
style: {
|
||||
fontSize: '1.2em',
|
||||
textOutline: 'none',
|
||||
opacity: 0.7,
|
||||
},
|
||||
filter: {
|
||||
operator: '>',
|
||||
property: 'percentage',
|
||||
value: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Percentage',
|
||||
colorByPoint: true,
|
||||
data: [
|
||||
{
|
||||
name: 'Kinh doanh',
|
||||
y: 43.02,
|
||||
},
|
||||
{
|
||||
name: 'Hành chính',
|
||||
y: 26.71,
|
||||
},
|
||||
{
|
||||
name: 'Kế toán',
|
||||
y: 10.09,
|
||||
},
|
||||
{
|
||||
name: 'HR',
|
||||
y: 15.5,
|
||||
},
|
||||
{
|
||||
name: 'Marketing',
|
||||
y: 20.68,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const barChartOptions = {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
},
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
xAxis: {
|
||||
categories: ['Hà Nội', 'TP.HCM', 'Đà Nẵng', 'Cần Thơ'],
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
gridLineWidth: 1,
|
||||
lineWidth: 0,
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: 'Số lượng',
|
||||
},
|
||||
labels: {
|
||||
overflow: 'justify',
|
||||
},
|
||||
gridLineWidth: 0,
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
},
|
||||
groupPadding: 0.1,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Số lượng',
|
||||
data: [24, 33, 30, 19],
|
||||
showInLegend: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="columns is-multiline">
|
||||
<div class="column columns is-12">
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<p class="">Tổng nhân viên</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="title is-4">8</p>
|
||||
<p class="subtitle is-6">+2 so với tháng trước</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<p class="">Đang làm việc</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="title is-4">7</p>
|
||||
<p class="subtitle is-6">1 nhân viên nghỉ phép</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<p class="">Chấm công hôm nay</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="title is-4">7</p>
|
||||
<p class="subtitle is-6">2 nhân viên đi muộn</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<p class="">Chi phí nhân sự</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="title is-4">215M</p>
|
||||
<p class="subtitle is-6">+4.8% so với tháng trước</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column columns is-12">
|
||||
<div class="column is-6">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<p class="has-text-danger">Cảnh báo hợp đồng sắp hết hạn</p>
|
||||
</div>
|
||||
<div class="content is-flex is-flex-direction-column is-gap-1">
|
||||
<div
|
||||
class="card m-0"
|
||||
style="box-shadow: none; background-color: #fff5f5"
|
||||
>
|
||||
<div
|
||||
class="card-content p-3 is-flex is-justify-content-space-between is-align-items-center"
|
||||
>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="m-0">Trần Thị Bình</p>
|
||||
<p class="m-0 fs-14 has-text-grey">
|
||||
Nhân viên - Showroom Hà Nội
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="fs-13 has-text-danger px-2 py-1"
|
||||
style="border: 1px solid red; border-radius: 8px"
|
||||
>
|
||||
2026-04-15
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="card m-0"
|
||||
style="box-shadow: none; background-color: #fff5f5"
|
||||
>
|
||||
<div
|
||||
class="card-content p-3 is-flex is-justify-content-space-between is-align-items-center"
|
||||
>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="m-0">Trần Thị Bình</p>
|
||||
<p class="m-0 fs-14 has-text-grey">
|
||||
Nhân viên - Showroom Hà Nội
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="fs-13 has-text-danger px-2 py-1"
|
||||
style="border: 1px solid red; border-radius: 8px"
|
||||
>
|
||||
2026-04-15
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="card m-0"
|
||||
style="box-shadow: none; background-color: #fff5f5"
|
||||
>
|
||||
<div
|
||||
class="card-content p-3 is-flex is-justify-content-space-between is-align-items-center"
|
||||
>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="m-0">Trần Thị Bình</p>
|
||||
<p class="m-0 fs-14 has-text-grey">
|
||||
Nhân viên - Showroom Hà Nội
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="fs-13 has-text-danger px-2 py-1"
|
||||
style="border: 1px solid red; border-radius: 8px"
|
||||
>
|
||||
2026-04-15
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="card h-full">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<p style="color: #34d">Tình trạng chấm công hôm nay</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<p class="fs-14">Đúng giờ</p>
|
||||
<span class="tag" style="background-color: lightgoldenrodyellow"
|
||||
>4 người</span
|
||||
>
|
||||
</div>
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<p class="fs-14">Đi muộn</p>
|
||||
<span class="tag" style="background-color: lightblue"
|
||||
>2 người</span
|
||||
>
|
||||
</div>
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<p class="fs-14">Về sớm</p>
|
||||
<span class="tag" style="background-color: lightgreen"
|
||||
>1 người</span
|
||||
>
|
||||
</div>
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<p class="fs-14">Nghỉ phép</p>
|
||||
<span class="tag" style="background-color: lightpink"
|
||||
>1 người</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column columns is-12 is-multiline">
|
||||
<div class="column is-6">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Chi phí nhân sự 6 tháng</p>
|
||||
<highcharts :options="costChartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Hiệu suất làm việc trung bình</p>
|
||||
<highcharts :options="efficiencyChartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Phân bổ nhân sự theo phòng ban</p>
|
||||
<highcharts :options="alloChartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Phân bổ nhân sự theo showroom</p>
|
||||
<highcharts :options="barChartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
54
app/components/viewer/EmpRow.vue
Normal file
54
app/components/viewer/EmpRow.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: Object,
|
||||
});
|
||||
|
||||
const showEmployeeInfoModal = ref();
|
||||
|
||||
function toggleModal() {
|
||||
showEmployeeInfoModal.value = {
|
||||
component: 'viewer/EmployeeInfo',
|
||||
width: '600px',
|
||||
height: 'auto',
|
||||
vbind: {
|
||||
row: props.row,
|
||||
},
|
||||
title: 'Thông tin chi tiết nhân viên',
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<tr>
|
||||
<td>{{ row.code }}</td>
|
||||
<td>
|
||||
<div>
|
||||
<p>{{ row.name }}</p>
|
||||
<p class="has-text-grey fs-13">{{ row.email }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ row.department }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>{{ row.showroom }}</td>
|
||||
<td>
|
||||
<span class="tag has-text-secondary">{{ row.status }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="is-flex is-gap-1">
|
||||
<button @click="toggleModal" class="button is-white p-1">
|
||||
<SvgIcon v-bind="{ name: 'eye.svg', type: 'success', size: 17 }" />
|
||||
</button>
|
||||
<button class="button is-white p-1">
|
||||
<SvgIcon v-bind="{ name: 'pen1.svg', type: 'success', size: 17 }" />
|
||||
</button>
|
||||
<button class="button is-white p-1">
|
||||
<SvgIcon v-bind="{ name: 'bin.svg', type: 'success', size: 17 }" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<Modal
|
||||
v-bind="showEmployeeInfoModal"
|
||||
v-if="showEmployeeInfoModal"
|
||||
@close="showEmployeeInfoModal = undefined"
|
||||
/>
|
||||
</tr>
|
||||
</template>
|
||||
152
app/components/viewer/Employee.vue
Normal file
152
app/components/viewer/Employee.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script setup>
|
||||
const { $dayjs } = useNuxtApp();
|
||||
const xinNghis = [
|
||||
{
|
||||
name: 'Nghỉ phép năm',
|
||||
date: '2026-03-25',
|
||||
status: 'Chờ duyệt',
|
||||
},
|
||||
{
|
||||
name: 'Nghỉ ốm',
|
||||
date: '2026-03-15',
|
||||
status: 'Đã duyệt',
|
||||
},
|
||||
];
|
||||
|
||||
const employee = {
|
||||
name: 'Nguyễn Văn An',
|
||||
email: 'an.nguyen@company.com',
|
||||
phone: '0901234567',
|
||||
department: 'Kinh doanh',
|
||||
title: 'Trưởng phòng',
|
||||
start_date: '2020-01-15',
|
||||
contract_type: 'Chính thức',
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img
|
||||
src="https://bulma.io/assets/images/placeholders/48x48.png"
|
||||
alt="Placeholder image"
|
||||
style="border-radius: 9999px; overflow: hidden"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title fs-18 mb-1">Nguyễn Văn An</p>
|
||||
<p class="subtitle is-6">Trưởng phòng - Kinh doanh</p>
|
||||
<p>Mã NV: EMP001</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-align-items-stretch">
|
||||
<div class="column">
|
||||
<div class="card h-full fs-14">
|
||||
<div class="card-content">
|
||||
<p class="fs-18 mb-2">Chấm công hôm nay</p>
|
||||
<p class="has-text-grey mt-1">Trạng thái</p>
|
||||
<span class="tag is-success">Đã chấm công</span>
|
||||
<p class="has-text-grey mt-1">Giờ vào</p>
|
||||
<p class="fsb-22">8h</p>
|
||||
<div class="buttons mt-4">
|
||||
<button class="button fs-14 is-fullwidth is-primary">
|
||||
Chấm công (GPS)
|
||||
</button>
|
||||
<button class="button fs-14 is-fullwidth">Chấm công (Wifi)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card h-full">
|
||||
<div class="card-content">
|
||||
<p class="fs-18 mb-2">Đơn xin nghỉ phép</p>
|
||||
<button class="button is-fullwidth is-primary">
|
||||
Gửi đơn xin nghỉ
|
||||
</button>
|
||||
<p class="has-text-grey mt-4">Số ngày phép còn lại</p>
|
||||
<p class="fsb-22">12</p>
|
||||
<p class="has-text-grey">ngày/năm</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card h-full">
|
||||
<div class="card-content">
|
||||
<p class="fs-18 mb-2">Lương tháng gần nhất</p>
|
||||
<button class="button is-accent mb-4 is-fullwidth">
|
||||
Xem chi tiết
|
||||
</button>
|
||||
<p class="fs-14 has-text-grey">Tháng 2026/02</p>
|
||||
<p class="fsb-22 has-text-green">31.5M</p>
|
||||
<p class="fs-14 has-text-grey">VNĐ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-gap-1">
|
||||
<SvgIcon v-bind="{ name: 'list.svg', size: 16, type: 'primary' }" />
|
||||
<p>Lịch sử đơn xin nghỉ</p>
|
||||
</div>
|
||||
<div class="is-flex is-flex-direction-column is-gap-2 mt-4">
|
||||
<div v-for="xinNghi in xinNghis">
|
||||
<div class="card">
|
||||
<div class="card-content is-flex is-justify-content-space-between">
|
||||
<div>
|
||||
<p>{{ xinNghi.name }}</p>
|
||||
<p class="fs-14 has-text-grey">
|
||||
Ngày {{ $dayjs(xinNghi.date).format('L') }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="tag">{{ xinNghi.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-6">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-gap-1 mb-4">
|
||||
<SvgIcon v-bind="{ name: 'person.svg', size: 20, type: 'primary' }" />
|
||||
<p>Thông tin cá nhân</p>
|
||||
</div>
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-6">
|
||||
<p class="fs-14 has-text-grey">Email</p>
|
||||
<p>{{ employee.email }}</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<p class="fs-14 has-text-grey">Số điện thoại</p>
|
||||
<p>{{ employee.phone }}</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<p class="fs-14 has-text-grey">Phòng ban</p>
|
||||
<p>{{ employee.department }}</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<p class="fs-14 has-text-grey">Chức vụ</p>
|
||||
<p>{{ employee.title }}</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<p class="fs-14 has-text-grey">Ngày vào làm</p>
|
||||
<p>{{ employee.start_date }}</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<p class="fs-14 has-text-grey">Loại hợp đồng</p>
|
||||
<p>{{ employee.contract_type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="button">Cập nhật thông tin</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
58
app/components/viewer/EmployeeInfo.vue
Normal file
58
app/components/viewer/EmployeeInfo.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup>
|
||||
import FormatNumber from '@/components/datatable/format/FormatNumber.vue';
|
||||
import EmployeeInfoBlock from '@/components/viewer/EmployeeInfoBlock.vue';
|
||||
|
||||
const props = defineProps({
|
||||
row: Object,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<p class="fs-17 font-semibold mb-3">Thông tin cá nhân</p>
|
||||
<div class="fixed-grid">
|
||||
<div class="grid">
|
||||
<EmployeeInfoBlock label="Mã nhân viên">{{
|
||||
row.code
|
||||
}}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Họ và tên">{{ row.name }}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Email">{{ row.email }}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Số điện thoại">{{
|
||||
row.phone
|
||||
}}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Địa chỉ">{{ row.address }}</EmployeeInfoBlock>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fs-17 font-semibold mb-3">Thông tin công việc</p>
|
||||
<div class="fixed-grid">
|
||||
<div class="grid">
|
||||
<EmployeeInfoBlock label="Phòng ban">{{
|
||||
row.department
|
||||
}}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Chức vụ">{{ row.title }}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Showroom">{{
|
||||
row.showroom
|
||||
}}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Ngày vào làm">{{
|
||||
row.start_date
|
||||
}}</EmployeeInfoBlock>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fs-17 font-semibold mb-3">Hợp đồng lao động</p>
|
||||
<div class="fixed-grid">
|
||||
<div class="grid">
|
||||
<EmployeeInfoBlock label="Loại hợp đồng">{{
|
||||
row.contract_type
|
||||
}}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Ngày hết hạn">{{
|
||||
row.contract_exp
|
||||
}}</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Lương cơ bản">
|
||||
<FormatNumber :value="row.contract_basic_salary" /> VNĐ
|
||||
</EmployeeInfoBlock>
|
||||
<EmployeeInfoBlock label="Trạng thái">
|
||||
<span class="tag">{{ row.status }}</span>
|
||||
</EmployeeInfoBlock>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
11
app/components/viewer/EmployeeInfoBlock.vue
Normal file
11
app/components/viewer/EmployeeInfoBlock.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
label: String,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="cell">
|
||||
<p class="fs-14 has-text-grey mb-1">{{ label }}</p>
|
||||
<p><slot></slot></p>
|
||||
</div>
|
||||
</template>
|
||||
224
app/components/viewer/HRM.vue
Normal file
224
app/components/viewer/HRM.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<script setup>
|
||||
import EmpRow from '@/components/viewer/EmpRow.vue';
|
||||
|
||||
const employees = [
|
||||
{
|
||||
code: 'EMP001',
|
||||
name: 'Nguyễn Văn An',
|
||||
email: 'an.nguyen@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Kinh doanh',
|
||||
title: 'Trưởng phòng',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Hà Nội',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP002',
|
||||
name: 'Trần Thị Bình',
|
||||
email: 'binh.tran@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Marketing',
|
||||
title: 'Nhân viên',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Hà Nội',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP003',
|
||||
name: 'Lê Hoàng Cường',
|
||||
email: 'cuong.le@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Kinh doanh',
|
||||
title: 'Nhân viên kinh doanh',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Hồ Chí Minh',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP004',
|
||||
name: 'Phạm Thị Dung',
|
||||
email: 'dung.pham@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Kế toán',
|
||||
title: 'Kế toán trưởng',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Hà Nội',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP005',
|
||||
name: 'Võ Minh Em',
|
||||
email: 'em.vo@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Kỹ thuật',
|
||||
title: 'Kỹ sư',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Hà Nội',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP006',
|
||||
name: 'Hoàng Văn Phúc',
|
||||
email: 'phuc.hoang@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Kinh doanh',
|
||||
title: 'Nhân viên kinh doanh',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Đà Nẵng',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP007',
|
||||
name: 'Đỗ Thị Giang',
|
||||
email: 'giang.do@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Hành chính',
|
||||
title: 'Nhân viên hành chính',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Cần Thơ',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
{
|
||||
code: 'EMP008',
|
||||
name: 'Bùi Văn Hải',
|
||||
email: 'hai.bui@company.com',
|
||||
phone: '0923839126',
|
||||
department: 'Kinh doanh',
|
||||
title: 'Quản lý showroom',
|
||||
start_date: '2024-02-15',
|
||||
showroom: 'Showroom Hà Nội',
|
||||
status: 'Đang làm',
|
||||
address: 'Hà Nội',
|
||||
contract_type: 'Thử việc',
|
||||
contract_exp: '2026-05-20',
|
||||
contract_basic_salary: '12000000.00',
|
||||
},
|
||||
];
|
||||
|
||||
const inputVal = ref('');
|
||||
const selectedDepartment = ref('');
|
||||
const selectedShowroom = ref('');
|
||||
|
||||
const filteredEmps = computed(() => {
|
||||
return employees.filter((emp) => {
|
||||
const values = Object.values(emp);
|
||||
|
||||
const departmentCond = selectedDepartment.value
|
||||
? selectedDepartment.value === emp.department
|
||||
: true;
|
||||
|
||||
const showroomCond = selectedShowroom.value
|
||||
? selectedShowroom.value === emp.showroom
|
||||
: true;
|
||||
|
||||
const inputCond = values.some((val) =>
|
||||
inputVal.value
|
||||
? val.toLowerCase().includes(inputVal.value.toLowerCase())
|
||||
: true,
|
||||
);
|
||||
return inputCond && departmentCond && showroomCond;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div id="search" class="mb-4 columns">
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<div class="control has-icons-left has-icons-right">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Tìm kiếm theo tên hoặc mã nhân viên"
|
||||
v-model="inputVal"
|
||||
/>
|
||||
<span class="icon is-small is-left">
|
||||
<SvgIcon v-bind="{ name: 'search.svg', size: 16, type: 'grey' }" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="selectedDepartment">
|
||||
<option value="">Tất cả phòng ban</option>
|
||||
<option>Kinh doanh</option>
|
||||
<option>Marketing</option>
|
||||
<option>Kế toán</option>
|
||||
<option>Kỹ thuật</option>
|
||||
<option>Hành chính</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="selectedShowroom">
|
||||
<option value="">Tất cả showroom</option>
|
||||
<option>Showroom Hà Nội</option>
|
||||
<option>Showroom Hồ Chí Minh</option>
|
||||
<option>Showroom Đà Nẵng</option>
|
||||
<option>Showroom Cần Thơ</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2">Danh sách nhân viên</p>
|
||||
<table class="table w-full fs-14">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Mã NV</th>
|
||||
<th scope="col">Họ tên</th>
|
||||
<th scope="col">Phòng ban</th>
|
||||
<th scope="col">Chức vụ</th>
|
||||
<th scope="col">Showroom</th>
|
||||
<th scope="col">Trạng thái</th>
|
||||
<th scope="col">Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<EmpRow
|
||||
v-for="employee in filteredEmps"
|
||||
:key="employee.code"
|
||||
:row="employee"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
220
app/components/viewer/Kpi.vue
Normal file
220
app/components/viewer/Kpi.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<script setup>
|
||||
import KpiRow from '@/components/viewer/KpiRow.vue';
|
||||
|
||||
const costChartOptions = {
|
||||
chart: {
|
||||
type: 'column',
|
||||
},
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
xAxis: {
|
||||
categories: [
|
||||
'Nguyễn Văn An',
|
||||
'Lê Hoàng Cường',
|
||||
'Hoàng Văn Phúc',
|
||||
'Bùi Văn Hải',
|
||||
],
|
||||
crosshair: true,
|
||||
accessibility: {
|
||||
description: 'Tháng',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: 'Triệu VNĐ',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
valueSuffix: ' (triệu VNĐ)',
|
||||
},
|
||||
plotOptions: {
|
||||
column: {
|
||||
pointPadding: 0.1,
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Chỉ tiêu',
|
||||
data: [180, 120, 210, 240],
|
||||
},
|
||||
{
|
||||
name: 'Đạt được',
|
||||
data: [190, 136, 210, 248],
|
||||
},
|
||||
],
|
||||
colors: ['#ddddee', '#2caffe'],
|
||||
};
|
||||
|
||||
const kpis = [
|
||||
{
|
||||
empcode: 'EMP001',
|
||||
empname: 'Nguyễn Văn An',
|
||||
title: 'Trưởng phòng',
|
||||
target: '500000000.00',
|
||||
reached: '520000000.00',
|
||||
progress: 104,
|
||||
evaluation: 'Đạt',
|
||||
},
|
||||
{
|
||||
empcode: 'EMP003',
|
||||
empname: 'Lê Hoàng Cường',
|
||||
title: 'Nhân viên kinh doanh',
|
||||
target: '200000000.00',
|
||||
reached: '235000000.00',
|
||||
progress: 118,
|
||||
evaluation: 'Đạt',
|
||||
},
|
||||
{
|
||||
empcode: 'EMP006',
|
||||
empname: 'Hoàng Văn Phúc',
|
||||
title: 'Nhân viên kinh doanh',
|
||||
target: '180000000.00',
|
||||
reached: '165000000.00',
|
||||
progress: 92,
|
||||
evaluation: 'Gần đạt',
|
||||
},
|
||||
{
|
||||
empcode: 'EMP008',
|
||||
empname: 'Bùi Văn Hải',
|
||||
title: 'Quản lý showroom',
|
||||
target: '800000000.00',
|
||||
reached: '855000000.00',
|
||||
progress: 106,
|
||||
evaluation: 'Đạt',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4 has-text-grey">KPI trung bình</p>
|
||||
<p class="title is-4 mb-0">104.9%</p>
|
||||
<p class="has-text-grey fs-14">Trung bình toàn công ty</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4 has-text-grey">Nhân viên đạt KPI</p>
|
||||
<p class="title is-4 mb-0">3</p>
|
||||
<p class="has-text-grey fs-14">/4 nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4 has-text-grey">Chưa đạt KPI</p>
|
||||
<p class="title is-4 mb-0">1</p>
|
||||
<p class="has-text-grey fs-14">Cần hỗ trợ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4 has-text-grey">Tổng doanh số</p>
|
||||
<p class="title is-4 mb-0">1.8B</p>
|
||||
<p class="has-text-grey fs-14">VNĐ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content is-flex is-gap-2 is-align-items-center">
|
||||
<p>Kỳ đánh giá</p>
|
||||
<div class="select">
|
||||
<select style="width: 300px">
|
||||
<option>Tháng 1/2026</option>
|
||||
<option>Tháng 2/2026</option>
|
||||
<option>Tháng 3/2026</option>
|
||||
<option>Quý 1/2026</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Biểu đồ hiệu suất KPI</p>
|
||||
<highcharts
|
||||
style="max-width: 1000px"
|
||||
class="mx-auto"
|
||||
:options="costChartOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Bảng theo dõi KPI</p>
|
||||
<table class="table is-fullwidth is-narrow fs-14">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mã NV</th>
|
||||
<th>Họ tên</th>
|
||||
<th>Chức vụ</th>
|
||||
<th>Chỉ tiêu</th>
|
||||
<th>Đạt được</th>
|
||||
<th class="is-narrow">Tiến độ</th>
|
||||
<th>Đánh giá</th>
|
||||
<th>Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<KpiRow v-for="kpi in kpis" :key="kpi.empname" :row="kpi" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Mẫu KPI theo vị trí</p>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18">Nhân viên kinh doanh</p>
|
||||
<ul>
|
||||
<li>Doanh số bán hàng: 200M/tháng</li>
|
||||
<li>Số khách hàng mới: 20 khách/tháng</li>
|
||||
<li>Tỷ lệ chốt đơn: 75%</li>
|
||||
<li>Tỷ lệ khách hàng quay lại: 60%</li>
|
||||
</ul>
|
||||
<button class="button">Sử dụng mẫu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-16">Quản lý showroom</p>
|
||||
<ul>
|
||||
<li>Doanh số showroom: 800M/tháng</li>
|
||||
<li>Tăng trưởng doanh số: 10%</li>
|
||||
<li>Quản lý tồn kho: <15%</li>
|
||||
<li>Đánh giá khách hàng: 4.5/5 sao</li>
|
||||
</ul>
|
||||
<button class="button">Sử dụng mẫu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
th:nth-child(4),
|
||||
th:nth-child(5),
|
||||
th:nth-child(7),
|
||||
th:nth-child(8) {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
51
app/components/viewer/KpiRow.vue
Normal file
51
app/components/viewer/KpiRow.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: Object,
|
||||
});
|
||||
|
||||
function shortenCurrency(amount) {
|
||||
return new Intl.NumberFormat('en', { notation: 'compact' }).format(
|
||||
Number(amount),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<tr>
|
||||
<td>{{ row.empcode }}</td>
|
||||
<td>{{ row.empname }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>{{ shortenCurrency(row.target) }}</td>
|
||||
<td>{{ shortenCurrency(row.reached) }}</td>
|
||||
<td>
|
||||
<div class="is-flex is-align-items-center is-gap-1">
|
||||
<progress
|
||||
class="progress is-small is-primary m-0"
|
||||
style="width: 100px; background-color: #ddd !important"
|
||||
:value="row.progress"
|
||||
max="100"
|
||||
></progress>
|
||||
<span class="fs-13">{{ row.progress }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ row.evaluation }}</td>
|
||||
<td>
|
||||
<button class="button">
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'pen1.svg', size: 16, type: 'primary' }" />
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<style scoped>
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td:nth-child(4),
|
||||
td:nth-child(5),
|
||||
td:nth-child(7),
|
||||
td:nth-child(8) {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
183
app/components/viewer/Rollcall.vue
Normal file
183
app/components/viewer/Rollcall.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<script setup>
|
||||
import RollcallRow from '@/components/viewer/RollcallRow.vue';
|
||||
|
||||
const rollcall = [
|
||||
{
|
||||
id: 1,
|
||||
empcode: 'EMP001',
|
||||
empname: 'Nguyễn Văn An',
|
||||
date: '2026-03-20',
|
||||
checkin: '2026-03-20T01:00:00.000Z',
|
||||
checkout: '2026-03-20T09:00:00.000Z',
|
||||
duration: '8',
|
||||
status: 'Đúng giờ',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
empcode: 'EMP002',
|
||||
empname: 'Trần Thị Bình',
|
||||
date: '2026-03-20',
|
||||
checkin: '2026-03-20T01:15:00.000Z',
|
||||
checkout: '2026-03-20T09:25:00.000Z',
|
||||
duration: '8.17',
|
||||
status: 'Đi muộn',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
empcode: 'EMP003',
|
||||
empname: 'Lê Hoàng Cường',
|
||||
date: '2026-03-20',
|
||||
checkin: '2026-03-20T00:45:00.000Z',
|
||||
checkout: '2026-03-20T09:00:00.000Z',
|
||||
duration: '8.25',
|
||||
status: 'Đúng giờ',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
empcode: 'EMP004',
|
||||
empname: 'Phạm Thị Dung',
|
||||
date: '2026-03-20',
|
||||
checkin: '2026-03-20T01:05:00.000Z',
|
||||
checkout: '2026-03-20T09:30:00.000Z',
|
||||
duration: '7.42',
|
||||
status: 'Về sớm',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
empcode: 'EMP005',
|
||||
empname: 'Võ Minh Em',
|
||||
date: '2026-03-20',
|
||||
checkin: null,
|
||||
checkout: null,
|
||||
duration: null,
|
||||
status: 'Nghỉ phép',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
<div class="columns">
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<p>Đúng giờ</p>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="fsb-22 m-0">8</p>
|
||||
<p class="fs-14 m-0 has-text-grey">nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<p>Đi muộn</p>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="fsb-22 m-0">2</p>
|
||||
<p class="fs-14 m-0 has-text-grey">nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<p>Nghỉ phép</p>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="fsb-22 m-0">8</p>
|
||||
<p class="fs-14 m-0 has-text-grey">nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<p>Tổng giờ làm</p>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<p class="fsb-22 m-0">58.3</p>
|
||||
<p class="fs-14 m-0 has-text-grey">giờ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content is-flex is-gap-2">
|
||||
<Datepicker />
|
||||
<div class="select">
|
||||
<select style="width: 300px">
|
||||
<option>Tất cả trạng thái</option>
|
||||
<option>Đúng giờ</option>
|
||||
<option>Đi muộn</option>
|
||||
<option>Về sớm</option>
|
||||
<option>Nghỉ phép</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-4">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">
|
||||
Bảng chấm công ngày {{ $dayjs('2026-03-20').format('DD/MM/YYYY') }}
|
||||
</p>
|
||||
<table class="table is-fullwidth fs-14">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mã NV</th>
|
||||
<th>Họ tên</th>
|
||||
<th>Ngày</th>
|
||||
<th>Giờ vào</th>
|
||||
<th>Giờ ra</th>
|
||||
<th>Giờ làm</th>
|
||||
<th>Trạng thái</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<RollcallRow v-for="row in rollcall" :key="row.id" :row="row" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-4">
|
||||
<div class="card-content">
|
||||
<p class="mb-4">Quản lý ca làm việc</p>
|
||||
<div class="columns">
|
||||
<div class="column is-4">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18 mb-3">Ca sáng</p>
|
||||
<p class="fs-14">08:00 - 12:00</p>
|
||||
<p class="fs-14 has-text-grey">28 nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18 mb-3">Ca chiều</p>
|
||||
<p class="fs-14">13:00 - 17:30</p>
|
||||
<p class="fs-14 has-text-grey">20 nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18 mb-3">Ca tối</p>
|
||||
<p class="fs-14">18:00 - 21:00</p>
|
||||
<p class="fs-14 has-text-grey">4 nhân viên</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
20
app/components/viewer/RollcallRow.vue
Normal file
20
app/components/viewer/RollcallRow.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: Object,
|
||||
});
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
</script>
|
||||
<template>
|
||||
<tr>
|
||||
<td>{{ row.empcode }}</td>
|
||||
<td>{{ row.empname }}</td>
|
||||
<td>{{ $dayjs(row.date).format('DD/MM/YYYY') }}</td>
|
||||
<td>{{ row.checkin ? $dayjs(row.checkin).format('HH:mm') : '-' }}</td>
|
||||
<td>{{ row.checkout ? $dayjs(row.checkout).format('HH:mm') : '-' }}</td>
|
||||
<td>{{ row.duration ? row.duration + ' giờ' : '-' }}</td>
|
||||
<td>
|
||||
<span class="tag">{{ row.status }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
146
app/components/viewer/Salary.vue
Normal file
146
app/components/viewer/Salary.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<script setup>
|
||||
import SalaryRow from '@/components/viewer/SalaryRow.vue';
|
||||
|
||||
const salaries = [
|
||||
{
|
||||
empcode: 'EMP001',
|
||||
empname: 'Nguyễn Văn An',
|
||||
basic_salary: '25000000.00',
|
||||
commission: '5000000.00',
|
||||
kpi_bonus: '3000000.00',
|
||||
deduction: '1500000.00',
|
||||
net: '31500000.00',
|
||||
},
|
||||
{
|
||||
empcode: 'EMP002',
|
||||
empname: 'Trần Thị Bình',
|
||||
basic_salary: '15000000.00',
|
||||
commission: '2000000.00',
|
||||
kpi_bonus: '1500000.00',
|
||||
deduction: '800000.00',
|
||||
net: '17700000.00',
|
||||
},
|
||||
{
|
||||
empcode: 'EMP003',
|
||||
empname: 'Lê Hoàng Cường',
|
||||
basic_salary: '12000000.00',
|
||||
commission: '3500000.00',
|
||||
kpi_bonus: '1000000.00',
|
||||
deduction: '600000.00',
|
||||
net: '15900000.00',
|
||||
},
|
||||
{
|
||||
empcode: 'EMP004',
|
||||
empname: 'Phạm Thị Dung',
|
||||
basic_salary: '20000000.00',
|
||||
commission: '0',
|
||||
kpi_bonus: '2000000.00',
|
||||
deduction: '1000000.00',
|
||||
net: '21000000.00',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
<div class="columns">
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p>Lương cơ bản</p>
|
||||
<p class="fsb-21 mt-5">72M</p>
|
||||
<p class="fs-13 has-text-grey">VNĐ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p>Hoa hồng</p>
|
||||
<p class="fsb-21 mt-5">11M</p>
|
||||
<p class="fs-13 has-text-grey">VNĐ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p>Thưởng KPI</p>
|
||||
<p class="fsb-21 mt-5">8M</p>
|
||||
<p class="fs-13 has-text-grey">VNĐ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p>Tổng chi lương</p>
|
||||
<p class="fsb-21 mt-5">86M</p>
|
||||
<p class="fs-13 has-text-grey">VNĐ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-4">Cấu hình tính lương</p>
|
||||
<div class="columns">
|
||||
<div class="column is-4">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18 mb-1">Lương cứng</p>
|
||||
<p class="has-text-grey fs-14">
|
||||
Lương cơ bản theo vị trí và cấp bậc
|
||||
</p>
|
||||
<button class="button is-small mt-2">Cấu hình</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18 mb-1">Hoa hồng doanh số</p>
|
||||
<p class="has-text-grey fs-14">% hoa hồng theo doanh số bán hàng</p>
|
||||
<button class="button is-small mt-2">Cấu hình</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fsb-18 mb-1">Thưởng KPI</p>
|
||||
<p class="has-text-grey fs-14">Thưởng đạt và vượt KPI</p>
|
||||
<button class="button is-small mt-2">Cấu hình</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="my-4">Bảng lương tháng 2026/02</p>
|
||||
<table class="table is-bordered is-narrow is-hoverable is-fullwidth fs-14">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mã NV</th>
|
||||
<th>Họ tên</th>
|
||||
<th>Lương cơ bản</th>
|
||||
<th>Hoa hồng</th>
|
||||
<th>Thưởng KPI</th>
|
||||
<th>Khấu trừ</th>
|
||||
<th>Thực lãnh</th>
|
||||
<th>Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<SalaryRow v-for="row in salaries" key="row.empcode" :row="row" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
th:nth-child(3),
|
||||
th:nth-child(4),
|
||||
th:nth-child(5),
|
||||
th:nth-child(6),
|
||||
th:nth-child(7),
|
||||
th:nth-child(8) {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
45
app/components/viewer/SalaryRow.vue
Normal file
45
app/components/viewer/SalaryRow.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: Object,
|
||||
});
|
||||
|
||||
function shortenCurrency(amount) {
|
||||
return new Intl.NumberFormat('en', { notation: 'compact' }).format(
|
||||
Number(amount),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<tr>
|
||||
<td>{{ row.empcode }}</td>
|
||||
<td>{{ row.empname }}</td>
|
||||
<td>{{ shortenCurrency(row.basic_salary) }}</td>
|
||||
<td>{{ shortenCurrency(row.commission) }}</td>
|
||||
<td>{{ shortenCurrency(row.kpi_bonus) }}</td>
|
||||
<td>{{ shortenCurrency(row.deduction) }}</td>
|
||||
<td>{{ shortenCurrency(row.net) }}</td>
|
||||
<td>
|
||||
<button class="button is-white">
|
||||
<span class="icon">
|
||||
<SvgIcon
|
||||
v-bind="{ name: 'eye-autodesk-on.svg', size: 18, type: 'primary' }"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<style scoped>
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td:nth-child(3),
|
||||
td:nth-child(4),
|
||||
td:nth-child(5),
|
||||
td:nth-child(6),
|
||||
td:nth-child(7),
|
||||
td:nth-child(8) {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user