editView almost done

This commit is contained in:
2025-03-12 19:38:00 +01:00
commit cf2049798a
29 changed files with 3663 additions and 0 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules
dist
.git
Dockerfile
docker-compose.yml

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
# Budowanie aplikacji
FROM node:lts-alpine AS build
WORKDIR /app
COPY ./app/package*.json ./
RUN npm install
COPY ./app .
RUN npm run build
# Serwowanie za pomocą NGINX
FROM nginx:alpine
# Skopiuj zbudowaną aplikację
COPY --from=build /app/dist /usr/share/nginx/html
# Skopiuj własną konfigurację NGINX
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

30
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

3
app/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

29
app/README.md Normal file
View File

@@ -0,0 +1,29 @@
# app
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

18
app/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Odoo Hours</title>
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
app/jsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

2820
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
app/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "app",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"bootstrap": "^5.3.3",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.1.0",
"vite-plugin-vue-devtools": "^7.7.2"
}
}

BIN
app/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

14
app/src/App.vue Normal file
View File

@@ -0,0 +1,14 @@
<template>
<navbar></navbar>
<RouterView></RouterView>
</template>
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import navbar from './components/Navbar.vue'
</script>

1
app/src/assets/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1,24 @@
<template>
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-3">
<div class="container-fluid">
<!-- <a class="navbar-brand" href="#">Odoo Hours</a> -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-center" id="navbarNavAltMarkup">
<div class="navbar-nav">
<RouterLink class="nav-link" to="/" activeClass="active">Edycja</RouterLink>
<RouterLink class="nav-link" to="/table" activeClass="active">Tabela</RouterLink>
<RouterLink class="nav-link" to="/calendar" activeClass="active">Kalendarz</RouterLink>
<!-- <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> -->
</div>
</div>
</div>
</nav>
</template>
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="d-flex align-items-center justify-content-center me-4">
<!-- Godziny -->
<button class="btn btn-outline-secondary me-2 " tabindex="-1" @mousedown="onDecrementClick('hour')"
@mouseup="stopInterval" @mouseleave="stopInterval" :class="{ 'visibility-hidden': !showButtons }"><i
class="fa fa-minus" aria-hidden="true"></i>
</button>
<button class="btn btn-outline-secondary me-1" tabindex="-1" @mousedown="onIncrementClick('hour')"
@mouseup="stopInterval" @mouseleave="stopInterval" :class="{ 'visibility-hidden': !showButtons }">
<i class="fa fa-plus" aria-hidden="true"></i>
</button>
<input class="form-control text-center mx-1" style="width: 3rem;" type="text" v-model="tempHour"
@blur="validateHour" @keyup.enter="validateHour">
<span class="mx-1">:</span>
<!-- Minuty -->
<input class="form-control text-center mx-1" style="width: 3rem;" type="text" v-model="tempMinute"
@blur="validateMinute" @keyup.enter="validateMinute">
<button class="btn btn-outline-secondary ms-1" tabindex="-1" @mousedown="onDecrementClick('minute')"
@mouseup="stopInterval" @mouseleave="stopInterval" :class="{ 'visibility-hidden': !showButtons }">
<i class="fa fa-minus" aria-hidden="true"></i>
</button>
<button class="btn btn-outline-secondary ms-2" tabindex="-1" @mousedown="onIncrementClick('minute')"
@mouseup="stopInterval" @mouseleave="stopInterval" :class="{ 'visibility-hidden': !showButtons }">
<i class="fa fa-plus" aria-hidden="true"></i>
</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref, watch, computed } from "vue";
const props = defineProps({
hour: Number,
minute: Number,
showButtons: Boolean,
});
const emit = defineEmits(["update:hour", "update:minute"]);
const interval = ref(null);
const timeout = ref(null);
// Tymczasowe zmienne na input
const tempHour = ref(props.hour);
const tempMinute = ref(props.minute);
// Nasłuchiwanie zmian propsów, aby synchronizować z ref
watch(() => props.hour, (newVal) => {
tempHour.value = newVal;
console.log("watch")
});
watch(() => props.minute, (newVal) => {
tempMinute.value = newVal;
});
// Funkcje walidacyjne
const validateHour = () => {
const hour = Number(tempHour.value);
if (!isNaN(hour) && hour >= 6 && hour <= 18) {
emit("update:hour", hour);
} else {
tempHour.value = props.hour; // Przywróć poprzednią wartość
}
};
const validateMinute = () => {
const minute = Number(tempMinute.value);
if (!isNaN(minute) && minute >= 0 && minute <= 59) {
emit("update:minute", minute);
} else {
tempMinute.value = props.minute; // Przywróć poprzednią wartość
}
};
const incrementValue = (type) => {
if (type == "hour") {
let newHour = tempHour.value + 1;
if (newHour >= 6 && newHour <= 18) {
// tempHour.value = newHour;
emit("update:hour", newHour);
}
// validateHour();
} else if (type === "minute") {
let newMinute = tempMinute.value + 1;
if (newMinute >= 0 && newMinute <= 59) {
emit("update:minute", newMinute);
}
}
}
const decrementValue = (type) => {
if (type == "hour") {
let newHour = tempHour.value - 1;
if (newHour >= 6 && newHour <= 18) {
emit("update:hour", newHour);
}
} else if (type === "minute") {
let newMinute = tempMinute.value - 1;
if (newMinute >= 0 && newMinute <= 59) {
emit("update:minute", newMinute);
}
}
}
const onIncrementClick = (type) => {
incrementValue(type);
timeout.value = setTimeout(() => {
interval.value = setInterval(() => {
incrementValue(type);
}, 100);
}, 1000);
}
const onDecrementClick = (type) => {
decrementValue(type);
timeout.value = setTimeout(() => {
interval.value = setInterval(() => {
decrementValue(type);
}, 100);
}, 1000);
}
const stopInterval = () => {
clearTimeout(timeout.value);
clearInterval(interval.value);
timeout.value = null;
interval.value = null;
}
</script>
<style scoped>
button {
width: 2.5rem;
}
.visibility-hidden {
visibility: hidden;
}
</style>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

17
app/src/main.js Normal file
View File

@@ -0,0 +1,17 @@
// import './assets/main.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.use(createPinia())
app.mount('#app')

35
app/src/router/index.js Normal file
View File

@@ -0,0 +1,35 @@
import { createRouter, createWebHistory } from 'vue-router'
import EditView from '../views/EditView.vue'
import TableView from '../views/TableView.vue'
import CalendarView from '../views/CalendarView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'edit',
component: EditView,
},
{
path: '/table',
name: 'table',
component: TableView,
},
{
path: '/calendar',
name: 'calendar',
component: CalendarView,
},
// {
// 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('../views/AboutView.vue'),
// },
],
})
export default router

99
app/src/stores/counter.js Normal file
View File

@@ -0,0 +1,99 @@
import { ref, computed, watch } from 'vue'
import { defineStore } from 'pinia'
export const useWorkHoursStore = defineStore('workHours', () => {
const workHours = ref([
{
date: new Date(2025, 1, 1),
enterHour: 7,
enterMinute: 20,
leaveHour: 15,
leaveMinute: 15,
workHours: 7,
workMinutes: 55,
},
{
date: new Date(2025, 1, 2),
enterHour: 8,
enterMinute: 21,
leaveHour: 16,
leaveMinute: 16,
workHours: 7,
workMinutes: 55,
},
{
date: new Date(2025, 1, 3),
enterHour: 9,
enterMinute: 22,
leaveHour: 17,
leaveMinute: 17,
workHours: 7,
workMinutes: 55,
},
]);
const calculateWorkTime = (entry) => {
let totalMinutes = (entry.leaveHour * 60 + entry.leaveMinute) - (entry.enterHour * 60 + entry.enterMinute);
if (totalMinutes < 0) {
totalMinutes = 0;
}
entry.workHours = Math.floor(totalMinutes / 60);
entry.workMinutes = totalMinutes % 60;
};
// Automatyczne przeliczanie czasu pracy
watch(
() => workHours.value.map((entry, index) => ({
index,
enterHour: entry.enterHour,
enterMinute: entry.enterMinute,
leaveHour: entry.leaveHour,
leaveMinute: entry.leaveMinute,
})),
(newVal, oldVal) => {
newVal.forEach((newEntry, index) => {
const oldEntry = oldVal[index];
// Sprawdzamy, czy cokolwiek się zmieniło
if (
newEntry.enterHour !== oldEntry.enterHour ||
newEntry.enterMinute !== oldEntry.enterMinute ||
newEntry.leaveHour !== oldEntry.leaveHour ||
newEntry.leaveMinute !== oldEntry.leaveMinute
) {
calculateWorkTime(workHours.value[index]);
}
});
},
{ deep: true }
);
const getDate = (index) => {
const date = workHours.value[index].date; // Pobieramy datę
const day = String(date.getDate()).padStart(2, "0");
const month = String(date.getMonth() + 1).padStart(2, "0");
const year = date.getFullYear();
const formattedDate = `${day}.${month}.${year}`;
return formattedDate;
};
const getWeekday = (index) => {
const dayNames = ['NIE', 'PON', 'WTO', 'śRO', 'CZW', 'PIĄ', 'SOB'];
const weekday = workHours.value[index].date.getDay();
return [weekday, dayNames[weekday]];
}
const clear = () => {
workHours.value = [];
}
return {
workHours,
getDate,
getWeekday,
clear
}
})

View File

@@ -0,0 +1,7 @@
<template>
<h1>Calendar</h1>
</template>
<script setup>
</script>

229
app/src/views/EditView.vue Normal file
View File

@@ -0,0 +1,229 @@
<template>
<div class="container text-center">
<div class="row align-items-center">
<!-- Przyciski -->
<div class="col-3"></div>
<div class="col d-flex justify-content-center align-items-center">
<button type="button" class="btn btn-outline-primary mx-2" style="width: 150px;" data-bs-toggle="modal"
data-bs-target="#inputModal">WCZYTAJ</button>
<button type="button" class="btn btn-outline-primary mx-2" style="width: 150px;" data-bs-toggle="modal"
data-bs-target="#clearAllModal">USUŃ</button>
<button type="button" class="btn btn-outline-primary mx-2" style="width: 150px;">DODAJ</button>
</div>
<!-- Toggle -->
<div class="col-3">
<div class="form-check form-switch d-flex justify-content-start align-items-center">
<input class="form-check-input p-2" type="checkbox" id="toggleSwitch" v-model="showButtons">
<label class="form-check-label p-2" for="toggleSwitch">Pokaż przyciski</label>
</div>
</div>
</div>
<!-- <div class="d-flex flex-row justify-content-center align-items-center">
Przyciski
<div class="d-flex">
<button type="button" class="btn btn-outline-primary mx-2" style="width: 150px;" data-bs-toggle="modal"
data-bs-target="#inputModal">WCZYTAJ</button>
<button type="button" class="btn btn-outline-primary mx-2" style="width: 150px;" data-bs-toggle="modal"
data-bs-target="#clearAllModal">USUŃ</button>
<button type="button" class="btn btn-outline-primary mx-2" style="width: 150px;">DODAJ</button>
</div>
Toggle
<div class="form-check form-switch ms-5">
<input class="form-check-input" type="checkbox" id="toggleSwitch">
<label class="form-check-label" for="toggleSwitch">Toggle</label>
</div>
</div> -->
</div>
<div class="container-fluid ">
<div class="text-center">
<table class="table w-50 mx-auto my-3">
<thead>
<tr>
<th class="text-center" scope="col">Data</th>
<th class="text-center" scope="col">Dzień</th>
<th class="text-center" scope="col">Wejście</th>
<th class="text-center" scope="col">Wyjście</th>
<th class="text-center" scope="col">Godziny</th>
</tr>
</thead>
<tbody>
<template v-for="(day, index) in storeWorkHours.workHours" :key="day.date">
<tr>
<td class="align-middle">
<div class="text-center">{{ storeWorkHours.getDate(index) }}</div>
</td>
<td class="align-middle">
<span class="text-center badge mx-3" :class="getClassWeekday(index)">
{{ storeWorkHours.getWeekday(index)[1] }}</span>
</td>
<td class="align-middle">
<!-- <div class="d-flex align-items-center justify-content-center">
Godziny dla Enter
<input class="form-control text-center" style="width: 4rem;" type="text"
v-model="day.enterHour" @focus="saveOldValue(day, 'enterHour')"
@blur="validate(day, 'enterHour', 6, 18, $event)">
<span class="mx-1">:</span>
Minuty dla Enter
<input class="form-control text-center" style="width: 4rem;" type="text"
v-model="day.enterMinute" @focus="saveOldValue(day, 'enterMinute')"
@blur="validate(day, 'enterMinute', 0, 59, $event)">
</div> -->
<TimeInputComponent v-model:hour="day.enterHour" v-model:minute="day.enterMinute"
:showButtons="showButtons" />
</td>
<td class="align-middle">
<!-- <div class="d-flex align-items-center justify-content-center">
Godziny dla Leave
<input class="form-control text-center" style="width: 4rem;" type="text"
v-model="day.leaveHour" @focus="saveOldValue(day, 'leaveHour')"
@blur="validate(day, 'leaveHour', 6, 18, $event)">
<span class="mx-1">:</span>
Minuty dla Leave
<input class="form-control text-center" style="width: 4rem;" type="text"
v-model="day.leaveMinute" @focus="saveOldValue(day, 'leaveMinute')"
@blur="validate(day, 'leaveMinute', 0, 59, $event)">
</div> -->
<TimeInputComponent v-model:hour="day.leaveHour" v-model:minute="day.leaveMinute"
:showButtons="showButtons" />
</td>
<td class="align-middle">
<div class="d-flex align-items-center justify-content-center">
<!-- Godziny dla Hours -->
<!-- <input class="form-control text-center" style="width: 4rem;" type="text"
v-model="day.workHours" @focus="saveOldValue(day, 'workHours')"
@blur="validate(day, 'workHours', 6, 18, $event)"> -->
<span class="mx-1 fs-5">{{ formattedWorkTime(day) }}</span>
<!-- Minuty dla Hours -->
<!-- <input class="form-control text-center" style="width: 4rem;" type="text"
v-model="day.workMinutes" @focus="saveOldValue(day, 'workMinutes')"
@blur="validate(day, 'workMinutes', 0, 59, $event)"> -->
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- modal input text -->
<div class="modal fade" id="inputModal" tabindex="-1" aria-labelledby="inputModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="inputModalLabel">Wklej swój tekst</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- <label for="inputText" class="form-label">Wklej swój tekst:</label> -->
<textarea name="input_text" id="inputText" class="form-control" rows="10"
placeholder="Wpisz lub wklej tutaj swój tekst" v-model="rawText" required></textarea>
<div class="invalid-feedback">Pole tekstowe nie może być puste.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zamknij</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" :disabled="!rawText"
@click="processInputText">Przetwórz teskt</button>
</div>
</div>
</div>
</div>
<!-- modal confirm clear -->
<div class="modal fade" id="clearAllModal" tabindex="-1" aria-labelledby="clearAllModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="clearAllModalLabel">Usuń wszystkie dane</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Czy jesteś pewien?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Nie</button>
<button type="button" class="btn btn-danger" data-bs-dismiss="modal"
@click="storeWorkHours.clear()">USUŃ</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import TimeInputComponent from "@/components/TimeInputComponent.vue";
import { useWorkHoursStore } from "../stores/counter.js";
import { Modal } from 'bootstrap';
import { computed, watch, ref, onMounted } from "vue";
const rawText = ref("");
const storeWorkHours = useWorkHoursStore();
// const oldValue = ref(0);
const showButtons = ref(false);
onMounted(() => {
const modalElement = document.getElementById('inputModal');
if (modalElement) {
modalElement.addEventListener('hidden.bs.modal', () => {
console.log('Modal został zamknięty kliknięciem poza oknem lub przez kliknięcie przycisku zamykania.');
// Tutaj możesz dodać własną logikę
clearInput();
});
}
});
const formattedWorkTime = (day) => {
const hours = String(day.workHours).padStart(2, '0'); // Dodaj wiodące zero do godzin
const minutes = String(day.workMinutes).padStart(2, '0'); // Dodaj wiodące zero do minut
return `${hours}:${minutes}`; // Zwróć sformatowany czas
};
// function saveOldValue(item, field) {
// oldValue.value = item[field];
// };
// const validate = (item, field, min, max, event) => {
// const newValue = Number(event.target.value);
// console.log("validate: " + field + ", min: " + min + ", max: " + max + ", newValue: " + newValue + ", oldValue: " + oldValue.value);
// if (isNaN(newValue) || newValue < min || newValue > max) {
// item[field] = oldValue.value; // Przywróć starą wartość
// }
// };
const getClassWeekday = (index) => {
switch (storeWorkHours.getWeekday(index)[0]) {
case 0:
return "text-bg-danger";
case 6:
return "text-bg-success";
default:
return "text-bg-secondary";
}
}
const processInputText = () => {
console.log(rawText.value);
}
const clearInput = () => {
rawText.value = '';
}
// const closeModal = () => {
// const modalElement = document.getElementById('staticBackdrop');
// const modal = Modal.getInstance(modalElement);
// if (modal) {
// modal.hide();
// }
// }
</script>

View File

@@ -0,0 +1,7 @@
<template>
<h1>Table</h1>
</template>
<script setup>
</script>

26
app/vite.config.js Normal file
View File

@@ -0,0 +1,26 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'bootstrap5': 'bootstrap/dist/css/bootstrap.min.css'
},
},
server: {
host: '0.0.0.0', // Udostępnia serwer na zewnątrz kontenera (Docker)
port: 5172, // Zmiana portu na 5172
watch: {
usePolling: true // Poprawka dla Docker na Linuxie
}
},
})

12
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,12 @@
services:
vue-app-dev:
image: node:lts-alpine
working_dir: /app
volumes:
- ./app:/app
- /app/node_modules
ports:
- "5172:5172" # Vite domyślnie na 5173
environment:
- NODE_ENV=development
command: sh -c "npm install && npm run dev"

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
services:
vue-app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:80"
volumes:
- ./app/src:/app/src
environment:
- NODE_ENV=production
restart: unless-stopped

35
nginx/default.conf Normal file
View File

@@ -0,0 +1,35 @@
server {
listen 80;
server_name bartool.ovh;
root /usr/share/nginx/html;
# Obsługa aplikacji pod ścieżką /vue-app
location /vue-app {
root /usr/share/nginx/html;
index index.html;
# Przekierowanie dla Vue Router (SPA mode)
try_files $uri /index.html;
# Przepuszczenie nagłówków
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Optymalizacja statycznych plików
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ {
expires 6M;
access_log off;
add_header Cache-Control "public";
}
# Kompresja
gzip on;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/json;
gzip_min_length 1000;
# Logi
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}