Initial commit

This commit is contained in:
Viet An
2026-04-06 13:47:10 +07:00
commit f423d9ab20
439 changed files with 97497 additions and 0 deletions

View 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 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 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 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>

View 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>

View 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> 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 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>

View 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 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>

View 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>

View 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 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"> 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>

View 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> 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 showroom</p>
<ul>
<li>Doanh số showroom: 800M/tháng</li>
<li>Tăng trưởng doanh số: 10%</li>
<li>Quản tồn kho: &lt;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>

View 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>

View 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> 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 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>

View 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>

View 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 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 bản theo vị trí 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ượ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> NV</th>
<th>Họ tên</th>
<th>Lương 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>

View 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>