- run by vite

- change camera
This commit is contained in:
Warunee Tamkoo 2023-11-14 17:47:43 +07:00
parent 782fa7f59f
commit 85d163fb64
57 changed files with 1494 additions and 1375 deletions

View file

@ -1,20 +1,19 @@
module.exports = {
root: true,
env: {
node: true
node: true,
es2022: true,
},
'extends': [
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended'
'@vue/typescript/recommended',
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
// 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
// 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
'vue/no-mutating-props': 'off'
}
'vue/no-mutating-props': 'off',
// '@typescript-eslint/no-explicit-any': 'off',
'vue/multi-word-component-names': 'off'
},
}

5
.prettierrc.json Normal file
View file

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true
}

View file

@ -1,24 +1,30 @@
# maptest
# BMA ระบบลงเวลาเข้า-ออกงาน
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run dev
```
### Compiles and minifies for production
```
npm run build
npm run preview
```
### Lints and fixes files
### Format and fixes files
```
npm run lint
npm run format
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View file

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

8
cypress.config.ts Normal file
View file

@ -0,0 +1,8 @@
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
baseUrl: 'http://localhost:4173',
},
})

1
env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
index.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BMA ระบบลงเวลาเข้า-ออกงาน</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -3,39 +3,60 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview --port 3008",
"test:unit": "vitest --environment jsdom --root src/",
"test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'",
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier . --write"
},
"dependencies": {
"@fullcalendar/core": "^6.1.8",
"@fullcalendar/daygrid": "^6.1.8",
"@fullcalendar/interaction": "^6.1.8",
"@fullcalendar/list": "^6.1.8",
"@fullcalendar/timegrid": "^6.1.8",
"@fullcalendar/vue3": "^6.1.8",
"@googlemaps/js-api-loader": "^1.16.2",
"@quasar/cli": "^2.3.0",
"@quasar/extras": "^1.0.0",
"@vuepic/vue-datepicker": "^7.2.1",
"core-js": "^3.8.3",
"@quasar/extras": "^1.15.8",
"@vuepic/vue-datepicker": "^5.2.1",
"moment": "^2.29.4",
"pinia": "^2.1.7",
"quasar": "^2.0.0",
"pinia": "^2.1.4",
"quasar": "^2.11.1",
"register-service-worker": "^1.7.2",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"simple-vue-camera": "^1.1.3",
"vite-plugin-pwa": "^0.16.7",
"vue": "^3.2.45",
"vue-router": "^4.1.6",
"vue3-google-map": "^0.18.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-pwa": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^8.53.0",
"eslint-plugin-prettier": "^5.0.1",
"sass": "1.32.12",
"sass-loader": "^12.0.0",
"typescript": "~4.5.5",
"vue-cli-plugin-quasar": "~5.0.2"
"@quasar/vite-plugin": "^1.3.0",
"@rushstack/eslint-patch": "^1.1.4",
"@types/jsdom": "^20.0.1",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/test-utils": "^2.2.6",
"@vue/tsconfig": "^0.1.3",
"cypress": "^12.0.2",
"eslint": "^8.22.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^9.3.0",
"jsdom": "^20.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"sass": "^1.32.12",
"start-server-and-test": "^1.15.2",
"typescript": "~4.7.4",
"vite": "^4.0.0",
"vitest": "^0.25.6",
"vue-tsc": "^1.0.12"
}
}

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 731 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app">
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBzPSF5NxUZ1G8DKBnJvJPTqCR0Ct2xf58&libraries=places"></script>
</div>
<!-- built files will be auto injected -->
</body>
</html>

View file

@ -35,44 +35,43 @@
</template>
<script setup lang="ts">
import { defineEmits, defineProps } from "vue";
import { useDialogPluginComponent } from "quasar";
import { useDialogPluginComponent } from 'quasar'
const props = defineProps({
color: {
type: String,
default: "primary",
default: 'primary',
},
textOk: {
type: String,
default: "ตกลง",
default: 'ตกลง',
},
title: {
type: String,
default: "หัวข้อ?",
default: 'หัวข้อ?',
},
message: {
type: String,
default: "ข้อความ",
default: 'ข้อความ',
},
icon: {
type: String,
default: "question_mark",
default: 'question_mark',
},
onlycancel: {
type: Boolean,
default: false,
},
});
})
defineEmits([
// REQUIRED; need to specify some events that your
// component will emit through useDialogPluginComponent()
...useDialogPluginComponent.emits,
]);
])
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
useDialogPluginComponent()
// dialogRef - Vue ref to be applied to QDialog
// onDialogHide - Function to be used as handler for @hide on QDialog
// onDialogOK - Function to call to settle dialog with "ok" outcome
@ -84,7 +83,7 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
function onOKClick() {
// on OK, it is REQUIRED to
// call onDialogOK (with optional payload)
onDialogOK();
onDialogOK()
// or with payload: onDialogOK({ ... })
// ...and it will also hide the dialog automatically
}

View file

@ -1,16 +1,16 @@
<script setup lang="ts">
import { ref, watch, onMounted, defineProps } from "vue";
import { useQuasar } from "quasar";
import moment, { Moment } from "moment";
import { ref, watch, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import moment from 'moment'
// importStores
import { useCounterMixin } from "@/stores/mixin";
import { useCounterMixin } from '@/stores/mixin'
// importType
import type { FormRef } from "@/interface/index/Main";
import type { FormRef } from '@/interface/index/Main'
const $q = useQuasar();
const mixin = useCounterMixin();
const { date2Thai, covertDateObject, dialogConfirm } = mixin;
const $q = useQuasar()
const mixin = useCounterMixin()
const { date2Thai, covertDateObject, dialogConfirm } = mixin
const props = defineProps({
dataById: {
@ -21,53 +21,53 @@ const props = defineProps({
type: Function,
required: true,
},
});
})
const dataById = ref<any>([]);
const date = ref<Date | null>(null);
const checkboxIn = ref<boolean>(false);
const checkboxOut = ref<boolean>(false);
const reason = ref<string>("");
const statusAction = ref<boolean>(false);
const dataById = ref<any>([])
const date = ref<Date | null>(null)
const checkboxIn = ref<boolean>(false)
const checkboxOut = ref<boolean>(false)
const reason = ref<string>('')
const statusAction = ref<boolean>(false)
onMounted(() => {
updateClock();
dataById.value = props.dataById;
updateClock()
dataById.value = props.dataById
if (dataById.value == null) {
statusAction.value = true;
statusAction.value = true
}
});
const dateNow = ref<Date>(new Date());
const timeNoew = ref<string>("");
})
const dateNow = ref<Date>(new Date())
const timeNoew = ref<string>('')
function updateClock() {
const date = Date.now();
let hh = moment(date).format("HH");
let mm = moment(date).format("mm");
timeNoew.value = `${hh}:${mm} น.`;
const date = Date.now()
const hh = moment(date).format('HH')
const mm = moment(date).format('mm')
timeNoew.value = `${hh}:${mm} น.`
}
const dateRef = ref<object | null>(null);
const reasonRef = ref<object | null>(null);
const dateRef = ref<object | null>(null)
const reasonRef = ref<object | null>(null)
const objectRef: FormRef = {
date: dateRef,
reason: reasonRef,
};
const checkstatusBox = ref<boolean>(false);
}
const checkstatusBox = ref<boolean>(false)
function onCkickSave() {
const hasError = [];
const hasError = []
for (const key in objectRef) {
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
const property = objectRef[key];
if (property.value && typeof property.value.validate === "function") {
const isValid = property.value.validate();
hasError.push(isValid);
const property = objectRef[key]
if (property.value && typeof property.value.validate === 'function') {
const isValid = property.value.validate()
hasError.push(isValid)
}
}
}
if (checkboxIn.value === false && checkboxOut.value === false) {
checkstatusBox.value = true;
checkstatusBox.value = true
}
if (
hasError.every(
@ -75,11 +75,11 @@ function onCkickSave() {
)
) {
dialogConfirm($q, async () => {
console.log("save");
props.closePopup();
});
console.log('save')
props.closePopup()
})
} else {
console.log("ไม่ผ่าน ");
console.log('ไม่ผ่าน ')
}
}
@ -88,11 +88,11 @@ watch(
([newCheckboxIn, newCheckboxOut]) => {
if (checkstatusBox.value) {
if (newCheckboxIn || newCheckboxOut) {
checkstatusBox.value = false;
checkstatusBox.value = false
}
}
}
);
)
</script>
<template>
<q-card-section class="col q-pt-none">

View file

@ -1,18 +1,17 @@
<script setup lang="ts">
import { defineProps } from "vue";
const props = defineProps({
title: {
type: String,
default: "",
default: '',
},
clickClose: {
type: Function,
default: null,
},
});
})
function clickClosePopup() {
props.clickClose();
props.clickClose()
}
</script>
<template>

View file

@ -1,16 +1,16 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { GoogleMap, Marker } from "vue3-google-map";
import { ref, onMounted } from 'vue'
import { GoogleMap, Marker } from 'vue3-google-map'
declare var google: any;
const center = ref<any>();
const location = ref<string>("");
const test = ref();
declare var google: any
const center = ref<any>()
const location = ref<string>('')
const test = ref()
// hook
onMounted(() => {
findNearestPlace();
});
findNearestPlace()
})
//
function findNearestPlace() {
@ -20,43 +20,43 @@ function findNearestPlace() {
const userLocation = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
}
// Location API
findNearestPlaceFromAPI(userLocation);
center.value = userLocation;
console.log(center.value);
findNearestPlaceFromAPI(userLocation)
center.value = userLocation
console.log(center.value)
},
(error) => {
console.error(error);
console.log("erroe");
console.error(error)
console.log('erroe')
}
);
)
} else {
console.error("เบราว์เซอร์ไม่รองรับการระบุตำแหน่ง");
console.error('เบราว์เซอร์ไม่รองรับการระบุตำแหน่ง')
}
}
function findNearestPlaceFromAPI(userLocation: any) {
const placesService = new google.maps.places.PlacesService(
document.createElement("div")
);
document.createElement('div')
)
const request = {
location: userLocation,
radius: 1000, //
types: ["point_of_interest"], //
};
types: ['point_of_interest'], //
}
placesService.nearbySearch(request, (results: any, status: any) => {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log("Nearby places:", results[0]);
const place = results[0];
location.value = place.name;
test.value = place.geometry.location;
console.log('Nearby places:', results[0])
const place = results[0]
location.value = place.name
test.value = place.geometry.location
} else {
console.error("Error fetching nearby places:", status);
console.error('Error fetching nearby places:', status)
}
});
})
}
</script>

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, defineProps, watch } from "vue";
import HeaderPopup from "@/components/HeaderPopup.vue";
import FormTime from "@/components/FormTime.vue";
import { ref, watch } from 'vue'
import HeaderPopup from '@/components/HeaderPopup.vue'
import FormTime from '@/components/FormTime.vue'
const props = defineProps({
modal: {
@ -10,7 +10,7 @@ const props = defineProps({
},
title: {
type: String,
default: "",
default: '',
},
clickClose: {
type: Function,
@ -20,18 +20,18 @@ const props = defineProps({
type: Object,
default: null,
},
});
})
// -
const data = ref<any>();
const data = ref<any>()
function clickClosePopup() {
props.clickClose();
props.clickClose()
}
watch(props, () => {
if (props.modal === true) {
data.value = props.dataById;
data.value = props.dataById
}
});
})
</script>
<template>
<q-dialog v-model="props.modal" full-height>

View file

@ -1,60 +1,60 @@
<script setup lang="ts">
import type { QTableProps } from "quasar";
import { ref } from "vue";
import type { QTableProps } from 'quasar'
import { ref } from 'vue'
// importStores
import { useChekIn } from "@/stores/chekin";
import { useChekIn } from '@/stores/chekin'
// importComponent
import Popup from "@/components/PopUp.vue";
import Popup from '@/components/PopUp.vue'
const stores = useChekIn();
const stores = useChekIn()
const columns = ref<QTableProps["columns"]>([
const columns = ref<QTableProps['columns']>([
{
name: "date",
align: "left",
label: "วัน/เดือน/ปี",
name: 'date',
align: 'left',
label: 'วัน/เดือน/ปี',
sortable: true,
field: "date",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
field: 'date',
headerStyle: 'font-size: 14px',
style: 'font-size: 14px; width:15%;',
},
{
name: "in",
align: "left",
label: "เวลาเข้างาน",
name: 'in',
align: 'left',
label: 'เวลาเข้างาน',
sortable: true,
field: "in",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
field: 'in',
headerStyle: 'font-size: 14px',
style: 'font-size: 14px; width:15%;',
},
{
name: "out",
align: "left",
label: "เวลาออกงาน",
name: 'out',
align: 'left',
label: 'เวลาออกงาน',
sortable: true,
field: "out",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
field: 'out',
headerStyle: 'font-size: 14px',
style: 'font-size: 14px; width:15%;',
},
{
name: "Morningstatus",
align: "left",
label: "สถานะช่วงเช้า",
name: 'Morningstatus',
align: 'left',
label: 'สถานะช่วงเช้า',
sortable: true,
field: "Morningstatus",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
field: 'Morningstatus',
headerStyle: 'font-size: 14px',
style: 'font-size: 14px; width:15%;',
},
{
name: "AfternoonStatus",
align: "left",
label: "สถานะช่วงบ่าย",
name: 'AfternoonStatus',
align: 'left',
label: 'สถานะช่วงบ่าย',
sortable: true,
field: "AfternoonStatus",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
field: 'AfternoonStatus',
headerStyle: 'font-size: 14px',
style: 'font-size: 14px; width:15%;',
},
// {
// name: "loIn",
@ -84,36 +84,36 @@ const columns = ref<QTableProps["columns"]>([
// headerStyle: "font-size: 14px",
// style: "font-size: 14px; width:10%;",
// },
]);
])
// popup
const modalPopup = ref<boolean>(false);
const titlePopup = ref<string>("");
const dataRow = ref<any>();
const modalPopup = ref<boolean>(false)
const titlePopup = ref<string>('')
const dataRow = ref<any>()
function openPopup(data: any) {
const title = "แก้ไขลงเวลา";
modalPopup.value = true;
titlePopup.value = title;
dataRow.value = data;
const title = 'แก้ไขลงเวลา'
modalPopup.value = true
titlePopup.value = title
dataRow.value = data
}
function closePopup() {
modalPopup.value = false;
modalPopup.value = false
}
function classStatus(status: string) {
switch (status) {
case "ขาดราชการ":
return "text-red";
case "ปกติ":
return "text-blue";
case "สาย":
return "text-yellow-8";
case 'ขาดราชการ':
return 'text-red'
case 'ปกติ':
return 'text-blue'
case 'สาย':
return 'text-yellow-8'
default:
break;
break
}
}
const pagination = ref({
page: 1,
rowsPerPage: 10,
});
})
</script>
<template>
<q-table

View file

@ -1,20 +1,20 @@
<script setup lang="ts">
import { ref } from "vue";
import type { DataOption } from "@/interface/index/Main";
import Popup from "@/components/PopUp.vue";
import { ref } from 'vue'
import type { DataOption } from '@/interface/index/Main'
import Popup from '@/components/PopUp.vue'
// import HeaderPopup from "@/components/HeaderPopup.vue";
// import FormTime from "@/components/FormTime.vue";
const filterYear = ref<string>("");
const yearOption = ref<DataOption[]>([{ id: "2566", name: "2566" }]);
const titleName = ref<string>("เพิ่มรายการลงเวลากรณีพิเศษ");
const filterYear = ref<string>('')
const yearOption = ref<DataOption[]>([{ id: '2566', name: '2566' }])
const titleName = ref<string>('เพิ่มรายการลงเวลากรณีพิเศษ')
const modalPopup = ref<boolean>(false);
const modalPopup = ref<boolean>(false)
function onClickopen() {
modalPopup.value = true;
modalPopup.value = true
}
function onClickClose() {
modalPopup.value = false;
modalPopup.value = false
}
</script>
<template>

View file

@ -1,10 +1,10 @@
interface DataOption {
id: string;
name: string;
id: string
name: string
}
interface FormRef {
date: object | null;
reason: object | null;
[key: string]: any;
date: object | null
reason: object | null
[key: string]: any
}
export type { DataOption, FormRef };
export type { DataOption, FormRef }

View file

@ -1,7 +1,7 @@
interface FormRef {
model: object | null;
useLocation: object | null;
[key: string]: any;
model: object | null
useLocation: object | null
[key: string]: any
}
export type { FormRef };
export type { FormRef }

View file

@ -1,29 +1,36 @@
import { createApp } from "vue";
import App from "./App.vue";
import "./registerServiceWorker";
import router from "./router";
import { createPinia } from "pinia";
import { createApp, defineAsyncComponent } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import { createPinia } from 'pinia'
import { Quasar, Dialog, Notify, Loading } from "quasar";
import '@quasar/extras/material-icons/material-icons.css';
import './styles/quasar.sass';
import quasarUserOptions from "./quasar-user-options";
import VueDatePicker from "@vuepic/vue-datepicker";
import "@vuepic/vue-datepicker/dist/main.css";
import { Quasar, Dialog, Notify, Loading } from 'quasar'
import '@vuepic/vue-datepicker/dist/main.css'
import quasarUserOptions from './quasar-user-options'
const app = createApp(App);
const pinia = createPinia();
import 'quasar/src/css/index.sass'
import th from "quasar/lang/th";
const app = createApp(App)
const pinia = createPinia()
app.use(router)
app.use(pinia)
app.use(router);
app.use(Quasar, quasarUserOptions);
app.use(Quasar, {
...quasarUserOptions,
plugins: {
Notify,
Dialog,
Loading,
},
});
app.use(pinia);
app.component("VueDatePicker", VueDatePicker);
lang: th,
})
app.mount("#app");
app.component(
'datepicker',
defineAsyncComponent(() => import('@vuepic/vue-datepicker'))
)
app.mount('#app')

View file

@ -1,10 +1,11 @@
import "./styles/quasar.sass";
// import "quasar/dist/quasar.css";
import "@quasar/extras/material-icons/material-icons.css";
import { Dialog, Notify } from "quasar";
// import "./styles/quasar.scss"
import '@quasar/extras/material-icons/material-icons.css'
import '@quasar/extras/material-icons-outlined/material-icons-outlined.css'
import '@quasar/extras/fontawesome-v5/fontawesome-v5.css'
import '@quasar/extras/mdi-v4/mdi-v4.css'
// To be used on app.use(Quasar, { ... })
export default {
config: {},
plugins: { Dialog, Notify },
};
plugins: {},
}

View file

@ -3,30 +3,32 @@
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
register('registerSW.js', {
ready() {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
registered() {
console.log('Service worker has been registered.')
},
cached () {
cached() {
console.log('Content has been cached for offline use.')
},
updatefound () {
updatefound() {
console.log('New content is downloading.')
},
updated () {
updated() {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
offline() {
console.log(
'No internet connection found. App is running in offline mode.'
)
},
error (error) {
error(error) {
console.error('Error during service worker registration:', error)
}
},
})
}

View file

@ -1,32 +1,42 @@
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
import HistoryView from "@/views/HistoryView.vue";
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
const routes: Array<RouteRecordRaw> = [
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
path: '/',
name: 'home',
component: HomeView,
},
{
path: "/about",
name: "about",
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
component: () => import('@/views/AboutView.vue'),
},
{
path: "/history",
name: "history",
component: HistoryView,
path: '/history',
name: 'history',
component: () => import('@/views/HistoryView.vue'),
},
];
// {
// path: '/camera',
// name: 'camera',
// component: () => import('@/views/SampleCamera.vue'),
// },
/**
* 404 Not Found
* ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route
*/
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/ErrorNotFoundPage.vue'),
},
],
})
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
export default router

2
src/shims-vue.d.ts vendored
View file

@ -1 +1 @@
declare module '*.vue';
declare module '*.vue'

View file

@ -1,11 +1,11 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useChekIn = defineStore("checkin", () => {
const rows = ref<any>();
export const useChekIn = defineStore('checkin', () => {
const rows = ref<any>()
function fetchHistoryList(data: any) {
console.log(data);
console.log(data)
const datalist = data.map((e: any) => ({
no: e.no,
date: e.date,
@ -18,38 +18,38 @@ export const useChekIn = defineStore("checkin", () => {
AfternoonStatus: convertStatus(e.AfternoonStatus),
statusEdit: e.statusEdit,
statusEditName: convertStatusEdit(e.statusEdit),
}));
rows.value = datalist;
}))
rows.value = datalist
}
function convertStatus(status: string) {
switch (status) {
case "1":
return "ขาดราชการ";
case "2":
return "ปกติ";
case "3":
return "สาย";
case '1':
return 'ขาดราชการ'
case '2':
return 'ปกติ'
case '3':
return 'สาย'
default:
return "";
return ''
}
}
function convertStatusEdit(val: string) {
switch (val) {
case "edit":
return "ขอแก้ไข";
case "wait":
return "รออนุมัติ";
case "approve":
return "อนุมัติ";
case 'edit':
return 'ขอแก้ไข'
case 'wait':
return 'รออนุมัติ'
case 'approve':
return 'อนุมัติ'
}
}
function classColorStatus(val: string) {
switch (val) {
case "wait":
return "orange";
case "approve":
return "green";
case 'wait':
return 'orange'
case 'approve':
return 'green'
}
}
@ -57,5 +57,5 @@ export const useChekIn = defineStore("checkin", () => {
rows,
fetchHistoryList,
classColorStatus,
};
});
}
})

View file

@ -1,122 +1,122 @@
import { defineStore } from "pinia";
import CustomComponent from "@/components/CustomDialog.vue";
import { useQuasar } from "quasar";
const $q = useQuasar();
import { defineStore } from 'pinia'
import CustomComponent from '@/components/CustomDialog.vue'
export const useCounterMixin = defineStore("mixin", () => {
export const useCounterMixin = defineStore('mixin', () => {
function date2Thai(srcDate: Date, isFullMonth = false, isTime = false) {
if (srcDate == null) {
return null;
return null
;`
`
`;
}
const date = new Date(srcDate);
const isValidDate = Boolean(+date);
if (!isValidDate) return srcDate.toString();
if (isValidDate && date.getFullYear() < 1000) return srcDate.toString();
const date = new Date(srcDate)
const isValidDate = Boolean(+date)
if (!isValidDate) return srcDate.toString()
if (isValidDate && date.getFullYear() < 1000) return srcDate.toString()
const fullMonthThai = [
"มกราคม",
"กุมภาพันธ์",
"มีนาคม",
"เมษายน",
"พฤษภาคม",
"มิถุนายน",
"กรกฎาคม",
"สิงหาคม",
"กันยายน",
"ตุลาคม",
"พฤศจิกายน",
"ธันวาคม",
];
'มกราคม',
'กุมภาพันธ์',
'มีนาคม',
'เมษายน',
'พฤษภาคม',
'มิถุนายน',
'กรกฎาคม',
'สิงหาคม',
'กันยายน',
'ตุลาคม',
'พฤศจิกายน',
'ธันวาคม',
]
const abbrMonthThai = [
"ม.ค.",
"ก.พ.",
"มี.ค.",
"เม.ย.",
"พ.ค.",
"มิ.ย.",
"ก.ค.",
"ส.ค.",
"ก.ย.",
"ต.ค.",
"พ.ย.",
"ธ.ค.",
];
let dstYear = 0;
'ม.ค.',
'ก.พ.',
'มี.ค.',
'เม.ย.',
'พ.ค.',
'มิ.ย.',
'ก.ค.',
'ส.ค.',
'ก.ย.',
'ต.ค.',
'พ.ย.',
'ธ.ค.',
]
let dstYear = 0
if (date.getFullYear() > 2500) {
dstYear = date.getFullYear();
dstYear = date.getFullYear()
} else {
dstYear = date.getFullYear() + 543;
dstYear = date.getFullYear() + 543
}
let dstMonth = "";
let dstMonth = ''
if (isFullMonth) {
dstMonth = fullMonthThai[date.getMonth()];
dstMonth = fullMonthThai[date.getMonth()]
} else {
dstMonth = abbrMonthThai[date.getMonth()];
dstMonth = abbrMonthThai[date.getMonth()]
}
let dstTime = "";
let dstTime = ''
if (isTime) {
const H = date.getHours().toString().padStart(2, "0");
const M = date.getMinutes().toString().padStart(2, "0");
const H = date.getHours().toString().padStart(2, '0')
const M = date.getMinutes().toString().padStart(2, '0')
// const S = date.getSeconds().toString().padStart(2, "0")
// dstTime = " " + H + ":" + M + ":" + S + " น."
dstTime = " " + H + ":" + M + " น.";
dstTime = ' ' + H + ':' + M + ' น.'
}
return (
date.getDate().toString().padStart(2, "0") +
" " +
date.getDate().toString().padStart(2, '0') +
' ' +
dstMonth +
" " +
' ' +
dstYear +
dstTime
);
)
}
function covertDateObject(date: string) {
if (date) {
const dateParts = date.split("/");
const dateParts = date.split('/')
// ประกาศตัวแปรเพื่อเก็บค่าวันที่, เดือน, และ ปี
const day = parseInt(dateParts[0], 10);
const month = parseInt(dateParts[1], 10) - 1;
const year = parseInt(dateParts[2], 10) + 2500;
const day = parseInt(dateParts[0], 10)
const month = parseInt(dateParts[1], 10) - 1
const year = parseInt(dateParts[2], 10) + 2500
// สร้างอ็อบเจ็กต์ Date ด้วยค่าที่ได้
const dateObject = new Date(year, month, day);
return date2Thai(dateObject);
const dateObject = new Date(year, month, day)
return date2Thai(dateObject)
}
}
type OkCallback = () => void;
type CancelCallback = () => void;
const dialogConfirm = (
type OkCallback = () => void
type CancelCallback = () => void
function dialogConfirm(
q: any,
ok?: OkCallback,
title?: string, // ถ้ามี cancel action ใส่เป็น null
desc?: string, // ถ้ามี cancel action ใส่เป็น null
cancel?: CancelCallback
) => {
) {
q.dialog({
component: CustomComponent,
componentProps: {
title: title && title != null ? title : "ยืนยันการบันทึก",
title: title && title != null ? title : 'ยืนยันการบันทึก',
message:
desc && desc != null
? desc
: "ต้องการยืนยันการบันทึกข้อมูลนี้ใช่หรือไม่?",
icon: "info",
color: "public",
textOk: "ตกลง",
: 'ต้องการยืนยันการบันทึกข้อมูลนี้ใช่หรือไม่?',
icon: 'info',
color: 'public',
textOk: 'ตกลง',
onlycancel: false,
},
})
.onOk(() => {
if (ok) ok();
if (ok) ok()
})
.onCancel(() => {
if (cancel) cancel();
});
};
if (cancel) cancel()
})
}
return {
date2Thai,
covertDateObject,
dialogConfirm,
};
});
}
})

View file

@ -1,3 +0,0 @@
@import './quasar.variables.sass'
@import '~quasar-styl'
// @import '~quasar-addon-styl'

View file

@ -0,0 +1,27 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "Error404NotFound",
});
</script>
<template>
<div
class="fullscreen bg-secondary text-white text-center q-pa-md flex flex-center"
>
<div>
<div class="text-h1">ไมพบหนาทองการ</div>
<div class="text-h2">(404 Page Not Found)</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="secondary"
unelevated
to="/"
label="กลับไปหน้าหลัก"
no-caps
/>
</div>
</div>
</template>

View file

@ -1,72 +1,72 @@
<script setup lang="ts">
import type { QTableProps } from "quasar";
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import Table from "@/components/TableHistory.vue";
import ToolBar from "@/components/ToolBar.vue";
import type { QTableProps } from 'quasar'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import Table from '@/components/TableHistory.vue'
import ToolBar from '@/components/ToolBar.vue'
// importStores
import { useChekIn } from "@/stores/chekin";
import { useChekIn } from '@/stores/chekin'
const router = useRouter();
const stores = useChekIn();
const router = useRouter()
const stores = useChekIn()
onMounted(() => {
fetchlist();
});
fetchlist()
})
function fetchlist() {
const listData = [
{
no: "1",
date: "13/08/66",
in: "11:20",
loIn: "สำนักงาน ก.ก ",
out: "",
loOut: "",
status: "",
Morningstatus: "1",
AfternoonStatus: "1",
statusEdit: "wait",
no: '1',
date: '13/08/66',
in: '11:20',
loIn: 'สำนักงาน ก.ก ',
out: '',
loOut: '',
status: '',
Morningstatus: '1',
AfternoonStatus: '1',
statusEdit: 'wait',
},
{
no: "2",
date: "12/08/66",
in: "08:04",
loIn: "สำนักงาน ก.ก ",
out: "17:01",
loOut: "สำนักงาน ก.ก ",
status: "ลงเวลาเรียบร้อย",
Morningstatus: "2",
AfternoonStatus: "2",
statusEdit: "edit",
no: '2',
date: '12/08/66',
in: '08:04',
loIn: 'สำนักงาน ก.ก ',
out: '17:01',
loOut: 'สำนักงาน ก.ก ',
status: 'ลงเวลาเรียบร้อย',
Morningstatus: '2',
AfternoonStatus: '2',
statusEdit: 'edit',
},
{
no: "3",
date: "11/08/66",
in: "08:34",
loIn: "สำนักงาน ก.ก ",
out: "17:36",
loOut: "สำนักงาน ก.ก ",
status: "สาย ทำงานครบ",
Morningstatus: "3",
AfternoonStatus: "2",
statusEdit: "edit",
no: '3',
date: '11/08/66',
in: '08:34',
loIn: 'สำนักงาน ก.ก ',
out: '17:36',
loOut: 'สำนักงาน ก.ก ',
status: 'สาย ทำงานครบ',
Morningstatus: '3',
AfternoonStatus: '2',
statusEdit: 'edit',
},
{
no: "4",
date: "10/08/66",
in: "08:48",
loIn: "สำนักงาน ก.ก ",
out: "17:00",
loOut: "สำนักงาน ก.ก ",
status: "สาย ทำงานไม่ครบ",
Morningstatus: "3",
AfternoonStatus: "3",
statusEdit: "approve",
no: '4',
date: '10/08/66',
in: '08:48',
loIn: 'สำนักงาน ก.ก ',
out: '17:00',
loOut: 'สำนักงาน ก.ก ',
status: 'สาย ทำงานไม่ครบ',
Morningstatus: '3',
AfternoonStatus: '3',
statusEdit: 'approve',
},
];
stores.fetchHistoryList(listData);
]
stores.fetchHistoryList(listData)
}
</script>
<template>

View file

@ -1,202 +1,134 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useQuasar } from "quasar";
import moment, { Moment } from "moment";
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useQuasar } from 'quasar'
import moment from 'moment'
import Camera from 'simple-vue-camera'
// import Type
import type { FormRef } from "@/interface/response/checkin";
import type { FormRef } from '@/interface/response/checkin'
// import components
import MapCheck from "@/components/MapCheckin.vue";
import MapCheck from '@/components/MapCheckin.vue'
// import Stores
import { useCounterMixin } from "@/stores/mixin";
import { useCounterMixin } from '@/stores/mixin'
const mixin = useCounterMixin();
const { date2Thai, dialogConfirm } = mixin;
const router = useRouter();
const $q = useQuasar();
const mixin = useCounterMixin()
const { date2Thai, dialogConfirm } = mixin
const router = useRouter()
const $q = useQuasar()
const stetusCheckin = ref(true);
const stetusCheckin = ref(true)
onMounted(() => {
updateClock();
});
updateClock()
})
//time
const dateNow = ref<Date>(new Date());
const Thai = ref<Date>(dateNow.value);
const formattedS = ref();
const formattedM = ref();
const formattedH = ref();
const dateNow = ref<Date>(new Date())
const Thai = ref<Date>(dateNow.value)
const formattedS = ref()
const formattedM = ref()
const formattedH = ref()
function updateClock() {
const date = Date.now();
let hh = moment(date).format("HH");
let mm = moment(date).format("mm");
let ss = moment(date).format("ss");
formattedS.value = ss;
formattedM.value = mm;
formattedH.value = hh;
const date = Date.now()
const hh = moment(date).format('HH')
const mm = moment(date).format('mm')
const ss = moment(date).format('ss')
formattedS.value = ss
formattedM.value = mm
formattedH.value = hh
}
setInterval(updateClock, 1000);
setInterval(updateClock, 1000)
//location
const location = ref<string>("");
const coordinates = ref<string>("13° 43 45” N 100° 31 26” E");
const workplace = ref<string>("in-place");
const useLocation = ref<string | null>("");
const model = ref<string | null>("");
const location = ref<string>('')
const coordinates = ref<string>('13° 43 45” N 100° 31 26” E')
const workplace = ref<string>('in-place')
const useLocation = ref<string | null>('')
const model = ref<string | null>('')
const options = ref<string[]>([
"ปฏิบัติงานที่บ้าน",
"ลืมลงเวลาปฏิบัติงาน",
"ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่",
"ขออนุญาตออกนอกสถานที่",
"อื่นๆ",
]);
'ปฏิบัติงานที่บ้าน',
'ลืมลงเวลาปฏิบัติงาน',
'ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่',
'ขออนุญาตออกนอกสถานที่',
'อื่นๆ',
])
function selectLocation() {
if (model.value === "อื่นๆ") {
useLocation.value = "";
if (model.value === 'อื่นๆ') {
useLocation.value = ''
} else {
useLocation.value = model.value;
useLocation.value = model.value
}
}
//camera
const camera = ref(false);
const hasPhoto = ref<boolean>(true);
const mediaStream = ref<MediaStream | null>(null);
const video = ref<HTMLVideoElement | null>(null);
const canvas = ref<HTMLCanvasElement | null>(null);
const img = ref<any>(null);
const camera = ref<InstanceType<typeof Camera>>()
const cameraIsOn = ref<boolean>(false)
const img = ref<any>(undefined)
const photoWidth = ref<number>(350)
const photoHeight = ref<number>(350)
const openCamera = () => {
camera.value = true;
camera.value && setupCamera();
};
const setupCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
if (video.value) {
video.value.srcObject = stream;
}
mediaStream.value = stream;
} catch (error) {
console.error("Error accessing camera:", error);
}
};
function capturePhoto() {
const videoElement = video.value;
const canvasElement = canvas.value;
if (!videoElement || !canvasElement) {
console.error("Video or Canvas element not available");
return;
}
const context = canvasElement.getContext("2d");
if (!context) {
console.error("Canvas context not available");
return;
}
const desiredWidth = 150;
const desiredHeight = 200;
const zoomFactor = 10;
cameraIsOn.value ? camera.value?.stop() : camera.value?.start()
cameraIsOn.value = !cameraIsOn.value
}
const videoAspectRatio = videoElement.videoWidth / videoElement.videoHeight;
const canvasAspectRatio = desiredWidth / desiredHeight;
let drawWidth, drawHeight;
if (videoAspectRatio > canvasAspectRatio) {
drawWidth = desiredWidth * zoomFactor;
drawHeight = (desiredWidth * zoomFactor) / videoAspectRatio;
} else {
drawWidth = desiredHeight * zoomFactor * videoAspectRatio;
drawHeight = desiredHeight * zoomFactor;
}
canvasElement.width = drawWidth;
canvasElement.height = drawHeight;
if (context) {
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = "low";
}
// context.drawImage(
// videoElement,
// 0,
// 0,
// canvasElement.width,
// canvasElement.height
// );
context.drawImage(
videoElement,
0,
0,
videoElement.videoWidth,
videoElement.videoHeight,
0,
0,
drawWidth,
drawHeight
);
async function capturePhoto() {
const imageBlob: any = await camera.value?.snapshot(
{ width: photoWidth.value, height: photoHeight.value },
'image/png',
0.5
)
//
const dataURL = canvasElement.toDataURL(".image/.png");
img.value = dataURL;
console.log(img.value);
camera.value?.stop()
const url = URL.createObjectURL(imageBlob)
img.value = url
}
if (mediaStream.value) {
mediaStream.value.getTracks().forEach((track) => track.stop());
videoElement.srcObject = null;
hasPhoto.value = false;
}
}
function refreshPhoto() {
hasPhoto.value = true;
img.value = "";
img.value = undefined
camera.value?.start()
}
// validate
const useLocationRef = ref<object | null>(null);
const modelRef = ref<object | null>(null);
const useLocationRef = ref<object | null>(null)
const modelRef = ref<object | null>(null)
const objectRef: FormRef = {
model: modelRef,
useLocation: useLocationRef,
};
}
function validateForm() {
const hasError = [];
const hasError = []
for (const key in objectRef) {
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
const property = objectRef[key];
if (property.value && typeof property.value.validate === "function") {
const isValid = property.value.validate();
hasError.push(isValid);
const property = objectRef[key]
if (property.value && typeof property.value.validate === 'function') {
const isValid = property.value.validate()
hasError.push(isValid)
}
}
}
if (hasError.every((result) => result === true)) {
confirm();
confirm()
} else {
console.log("ไม่ผ่าน ");
console.log('ไม่ผ่าน ')
}
}
//
const dialogTime = ref<boolean>(false);
const dialogTime = ref<boolean>(false)
const confirm = () => {
dialogConfirm(
$q,
async () => {
dialogTime.value = true;
},
"ยืนยันการบันทึกเวลา ?",
"ต้องการยืนยันการบันทึกการลงเวลานี้ใช่หรือไม่"
);
};
// api popup
dialogTime.value = true
}
// class
const getClass = (val: boolean) => {
return {
"bg-primary text-white col-12 row items-center q-px-md q-py-sm": val,
"bg-red-9 text-white col-12 row items-center q-px-md q-py-sm": !val,
};
};
'bg-primary text-white col-12 row items-center q-px-md q-py-sm': val,
'bg-red-9 text-white col-12 row items-center q-px-md q-py-sm': !val,
}
}
</script>
<template>
@ -263,13 +195,12 @@ const getClass = (val: boolean) => {
<MapCheck />
</div>
<div class="col-12 col-sm-4">
<q-card
flat
bordered
class="card-container"
<q-card flat bordered class="card-container">
<div
v-if="!cameraIsOn && img == null"
class="preview-placeholder"
@click="openCamera()"
>
<div v-if="!camera" class="preview-placeholder">
<div class="text-center">
<q-icon
name="photo_camera"
@ -279,18 +210,26 @@ const getClass = (val: boolean) => {
/>
</div>
</div>
<div v-else>
<div v-if="hasPhoto" class="video-container">
<video ref="video" autoplay class="video-element"></video>
<canvas ref="canvas" class="canvas-element"></canvas>
</div>
<div v-else class="image-container">
<div>
<!-- แสดงกลองตอนกดถายภาพ -->
<Camera
:resolution="{ width: photoWidth, height: photoHeight }"
ref="camera"
:autoplay="false"
:style="!img ? 'display: block' : 'display: none'"
/>
<!-- แสดงรปเมอกด capture -->
<div v-if="img" class="image-container">
<q-img :src="img" class="image-element"></q-img>
<canvas ref="canvas" class="canvas-element"></canvas>
</div>
<div class="absolute-bottom-right q-ma-md">
<div
v-if="cameraIsOn"
class="absolute-bottom-right q-ma-md"
>
<q-btn
v-if="hasPhoto"
v-if="img == null"
round
push
icon="photo_camera"
@ -311,6 +250,7 @@ const getClass = (val: boolean) => {
</div>
</q-card>
</div>
<div class="col-12 q-mb-md">
<q-card
bordered
@ -377,6 +317,7 @@ const getClass = (val: boolean) => {
/>
</div>
</q-card>
<div class="col-12 text-right">
<q-separator />
<div class="col-12 q-pa-md">
@ -471,8 +412,6 @@ const getClass = (val: boolean) => {
height: 350px; /* Adjust as needed */
background: #f6f5f5;
}
.video-container,
.image-container {
position: absolute;
top: 0;
@ -481,7 +420,6 @@ const getClass = (val: boolean) => {
height: 100%;
}
.video-element,
.image-element {
width: 100%;
height: 100%;
@ -489,7 +427,8 @@ const getClass = (val: boolean) => {
border-radius: 5px; /* Adjust as needed */
}
.canvas-element {
display: none; /* Adjust as needed */
.preview-placeholder {
width: 100%;
height: 100%;
}
</style>

View file

@ -0,0 +1,42 @@
<script setup lang="ts">
import Camera from 'simple-vue-camera'
import { ref } from 'vue'
const camera = ref<InstanceType<typeof Camera>>();
const cameraIsOn = ref<boolean>(false)
const cameraOff = () => {
cameraIsOn.value ? camera.value?.stop() : camera.value?.start()
cameraIsOn.value = !cameraIsOn.value
}
// Use camera reference to call functions
const takePicture = async () => {
const imageBlob = await camera.value?.snapshot(
{ width: 640, height: 480 },
'image/png',
0.5
)
console.log('imageBlob', imageBlob)
camera.value?.stop()
}
</script>
<template>
<div class="row col-12">
<div class="col-3">
<Camera
:resolution="{ width: 500, height: 500 }"
ref="camera"
:autoplay="false"
>
<q-btn color="primary" @click="cameraOff"
>I'm on top of the video</q-btn
>
<q-btn color="primary" @click="takePicture">Take Picture</q-btn>
</Camera>
</div>
</div>
</template>

16
tsconfig.app.json Normal file
View file

@ -0,0 +1,16 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"esModuleInterop": true,
"ignoreDeprecations": "5.0",
"allowSyntheticDefaultImports": true,
"lib": ["dom", "es2015", "es2018", "es2018.promise"]
}
}

14
tsconfig.config.json Normal file
View file

@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"ignoreDeprecations": "5.0",
"types": ["node"]
}
}

View file

@ -1,29 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": ["webpack-env"],
"paths": {
"@/*": ["src/*"]
"files": [],
"references": [
{
"path": "./tsconfig.config.json"
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
{
"path": "./tsconfig.app.json"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": ["node_modules"]
{
"path": "./tsconfig.vitest.json"
}
]
}

9
tsconfig.vitest.json Normal file
View file

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.app.json",
"exclude": [],
"compilerOptions": {
"composite": true,
"lib": [],
"types": ["node", "jsdom"]
}
}

60
vite.config.js Normal file
View file

@ -0,0 +1,60 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
import { VitePWA } from 'vite-plugin-pwa'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
template: { transformAssetUrls },
}),
quasar({
sassVariables: 'src/style/quasar-variables.sass',
}),
vueJsx(),
VitePWA({
registerType: 'autoUpdate',
injectRegister: 'auto',
workbox: {
cleanupOutdatedCaches: true,
globPatterns: ['**/*.*'],
},
includeAssets: ['icons/safari-pinned-tab.svg'],
manifest: {
name: 'BMA-Checkin',
short_name: 'EHR Checkin',
theme_color: '#ffffff',
icons: [
{
src: 'icons/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'icons/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: 'icons/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: ['any', 'maskable'],
},
],
},
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 3008,
},
})

View file

@ -1,13 +0,0 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: [
'quasar'
],
pluginOptions: {
quasar: {
importStrategy: 'kebab',
rtlSupport: false
}
}
})