mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
Compare commits
5 Commits
f826474f88
...
feature/mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888c21369a | ||
|
|
89aff45cfb | ||
|
|
569d94b240 | ||
|
|
ca94021d99 | ||
|
|
b730cc21dd |
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Switch } from '@/Components/ui/switch';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/Components/ui/popover';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/packages/ui/src';
|
||||
import { Button } from '@/packages/ui/src';
|
||||
import {
|
||||
Select,
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
|
||||
import DialogModal from '@/packages/ui/src/DialogModal.vue';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { getUserTimezone } from '@/packages/ui/src/utils/settings';
|
||||
import { getDayJsInstance } from '@/packages/ui/src/utils/time';
|
||||
import { ref } from 'vue';
|
||||
import { useForm, usePage } from '@inertiajs/vue3';
|
||||
import type { User } from '@/types/models';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
import TimezoneMismatchModal from '@/packages/ui/src/TimezoneMismatchModal.vue';
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
const saving = defineModel('saving', { default: false });
|
||||
|
||||
const timezone = ref('');
|
||||
const userTimezone = ref('');
|
||||
const saving = ref(false);
|
||||
|
||||
const page = usePage<{
|
||||
auth: {
|
||||
@@ -21,27 +13,11 @@ const page = usePage<{
|
||||
};
|
||||
}>();
|
||||
|
||||
const hideTimezoneMismatchModal = useSessionStorage<boolean>('hide-timezone-mismatch-modal', false);
|
||||
|
||||
onMounted(() => {
|
||||
timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
userTimezone.value = getUserTimezone();
|
||||
|
||||
const now = getDayJsInstance()();
|
||||
|
||||
if (
|
||||
now.tz(timezone.value).format() !== now.tz(userTimezone.value).format() &&
|
||||
!hideTimezoneMismatchModal.value
|
||||
) {
|
||||
show.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
function submit() {
|
||||
function handleUpdate(timezone: string) {
|
||||
saving.value = true;
|
||||
const form = useForm({
|
||||
_method: 'PUT',
|
||||
timezone: timezone.value,
|
||||
timezone: timezone,
|
||||
name: page.props.auth.user.name,
|
||||
email: page.props.auth.user.email,
|
||||
week_start: page.props.auth.user.week_start,
|
||||
@@ -55,53 +31,15 @@ function submit() {
|
||||
show.value = false;
|
||||
location.reload();
|
||||
},
|
||||
onError: () => {
|
||||
saving.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
show.value = false;
|
||||
hideTimezoneMismatchModal.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogModal closeable :show="show" @close="show = false">
|
||||
<template #title>
|
||||
<div class="flex justify-center">
|
||||
<span> Timezone mismatch detected </span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1 space-y-2">
|
||||
<p>
|
||||
The timezone of your device does not match the timezone in your user
|
||||
settings. <br />
|
||||
<strong
|
||||
>We highly recommend that you update your timezone settings to your
|
||||
current timezone.</strong
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Want to change your timezone setting from
|
||||
<strong>{{ userTimezone }}</strong> to <strong>{{ timezone }}</strong
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="cancel"> Cancel</SecondaryButton>
|
||||
<PrimaryButton
|
||||
class="ms-3"
|
||||
:class="{ 'opacity-25': saving }"
|
||||
:disabled="saving"
|
||||
@click="submit()">
|
||||
Update timezone
|
||||
</PrimaryButton>
|
||||
</template>
|
||||
</DialogModal>
|
||||
<TimezoneMismatchModal v-model:show="show" :saving="saving" @update="handleUpdate" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/Components/ui/popover';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/packages/ui/src';
|
||||
import { Button } from '@/packages/ui/src';
|
||||
import { Calendar } from '@/Components/ui/calendar';
|
||||
import { CalendarIcon, XIcon } from 'lucide-vue-next';
|
||||
|
||||
193
resources/js/packages/api/package-lock.json
generated
193
resources/js/packages/api/package-lock.json
generated
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "@solidtime/api",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@solidtime/api",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@zodios/core": "^10.9.6",
|
||||
"axios": "^1.13.2",
|
||||
"typescript": "^5.5.4",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
@@ -1094,18 +1095,16 @@
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
|
||||
"integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -1127,12 +1126,24 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -1198,11 +1209,24 @@
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
@@ -1216,6 +1240,51 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
@@ -1280,7 +1349,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
@@ -1291,14 +1359,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1339,12 +1408,60 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -1362,11 +1479,37 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -1489,12 +1632,20 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -1504,7 +1655,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@@ -1657,8 +1807,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@solidtime/api",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"description": "Package containing the solidtime api client and type declarations",
|
||||
"main": "./dist/solidtime-api.umd.cjs",
|
||||
"module": "./dist/solidtime-api.js",
|
||||
@@ -29,6 +29,7 @@
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@zodios/core": "^10.9.6",
|
||||
"axios": "^1.13.2",
|
||||
"typescript": "^5.5.4",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
991
resources/js/packages/ui/package-lock.json
generated
991
resources/js/packages/ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@solidtime/ui",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.15",
|
||||
"description": "Package containing the solidtime ui components",
|
||||
"main": "./dist/solidtime-ui-lib.umd.cjs",
|
||||
"module": "./dist/solidtime-ui-lib.js",
|
||||
@@ -33,7 +33,9 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"styles.css",
|
||||
"tailwind.theme.js"
|
||||
],
|
||||
"keywords": [
|
||||
"solidtime",
|
||||
|
||||
@@ -712,28 +712,41 @@ onUnmounted(() => {
|
||||
|
||||
/* Activity status plugin styles */
|
||||
.fullcalendar :deep(.activity-status-box) {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
left: 0px;
|
||||
z-index: 10;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.fullcalendar :deep(.activity-status-box::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 5px;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fullcalendar :deep(.activity-status-box.idle) {
|
||||
background-color: rgba(156, 163, 175, 0.1) !important;
|
||||
.fullcalendar :deep(.activity-status-box.idle::before) {
|
||||
background-color: rgba(156, 163, 175, 0.1);
|
||||
}
|
||||
|
||||
.fullcalendar :deep(.activity-status-box.idle):hover {
|
||||
background-color: rgba(156, 163, 175, 0.5) !important;
|
||||
.fullcalendar :deep(.activity-status-box.idle):hover::before {
|
||||
background-color: rgba(156, 163, 175, 0.5);
|
||||
}
|
||||
|
||||
.fullcalendar :deep(.activity-status-box.active) {
|
||||
background-color: rgba(34, 197, 94, 0.3) !important;
|
||||
.fullcalendar :deep(.activity-status-box.active::before) {
|
||||
background-color: rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
.fullcalendar :deep(.activity-status-box.active):hover {
|
||||
background-color: rgba(34, 197, 94, 1) !important;
|
||||
.fullcalendar :deep(.activity-status-box.active):hover::before {
|
||||
background-color: rgba(34, 197, 94, 1);
|
||||
}
|
||||
|
||||
/* Add left margin to events only on days with activity status data */
|
||||
.fullcalendar :deep(.has-activity-status .fc-timegrid-event-harness) {
|
||||
margin-left: 15px !important;
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
.fullcalendar :deep(.fc-timegrid-event) {
|
||||
|
||||
@@ -1,42 +1,70 @@
|
||||
import { createPlugin, type PluginDef } from '@fullcalendar/core';
|
||||
import { computePosition, flip, shift, offset } from '@floating-ui/dom';
|
||||
import { computePosition, flip, shift, offset, autoUpdate } from '@floating-ui/dom';
|
||||
|
||||
export interface WindowActivityInPeriod {
|
||||
appName: string;
|
||||
url: string | null;
|
||||
count: number;
|
||||
icon?: string | null;
|
||||
}
|
||||
|
||||
export interface ActivityPeriod {
|
||||
start: string;
|
||||
end: string;
|
||||
isIdle: boolean;
|
||||
windowActivities?: WindowActivityInPeriod[];
|
||||
}
|
||||
|
||||
export interface ActivityStatusPluginOptions {
|
||||
activityPeriods?: ActivityPeriod[];
|
||||
}
|
||||
|
||||
// Tooltip state management - single instance per module
|
||||
let tooltipInstance: HTMLElement | null = null;
|
||||
let cleanupAutoUpdate: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* Creates and manages a tooltip element for activity status boxes
|
||||
*/
|
||||
function createTooltip(): HTMLElement {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className =
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground';
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.pointerEvents = 'none';
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.whiteSpace = 'nowrap';
|
||||
tooltip.style.transform = 'scale(0.95)';
|
||||
tooltip.style.transition = 'opacity 150ms, transform 150ms';
|
||||
document.body.appendChild(tooltip);
|
||||
return tooltip;
|
||||
function getOrCreateTooltip(): HTMLElement {
|
||||
if (!tooltipInstance) {
|
||||
tooltipInstance = document.createElement('div');
|
||||
tooltipInstance.className =
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground';
|
||||
tooltipInstance.style.position = 'fixed';
|
||||
tooltipInstance.style.pointerEvents = 'none';
|
||||
tooltipInstance.style.opacity = '0';
|
||||
tooltipInstance.style.whiteSpace = 'nowrap';
|
||||
tooltipInstance.style.transform = 'scale(0.95)';
|
||||
tooltipInstance.style.transition = 'opacity 150ms, transform 150ms';
|
||||
document.body.appendChild(tooltipInstance);
|
||||
}
|
||||
return tooltipInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows tooltip for an activity status box
|
||||
* Shows tooltip for an activity status box using Floating UI's autoUpdate
|
||||
*/
|
||||
function showTooltip(box: HTMLElement, tooltip: HTMLElement, text: string) {
|
||||
tooltip.textContent = text;
|
||||
function showTooltip(box: HTMLElement, tooltip: HTMLElement, content: string | HTMLElement) {
|
||||
// Clear previous content
|
||||
tooltip.innerHTML = '';
|
||||
|
||||
if (typeof content === 'string') {
|
||||
tooltip.textContent = content;
|
||||
} else {
|
||||
tooltip.appendChild(content);
|
||||
}
|
||||
|
||||
tooltip.style.opacity = '1';
|
||||
tooltip.style.transform = 'scale(1)';
|
||||
|
||||
const updatePosition = () => {
|
||||
// Clean up previous autoUpdate if it exists
|
||||
if (cleanupAutoUpdate) {
|
||||
cleanupAutoUpdate();
|
||||
}
|
||||
|
||||
// Use autoUpdate to automatically update position
|
||||
cleanupAutoUpdate = autoUpdate(box, tooltip, () => {
|
||||
computePosition(box, tooltip, {
|
||||
placement: 'right',
|
||||
middleware: [offset(8), flip(), shift({ padding: 5 })],
|
||||
@@ -44,17 +72,124 @@ function showTooltip(box: HTMLElement, tooltip: HTMLElement, text: string) {
|
||||
tooltip.style.left = `${x}px`;
|
||||
tooltip.style.top = `${y}px`;
|
||||
});
|
||||
};
|
||||
|
||||
updatePosition();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the tooltip
|
||||
* Hides the tooltip immediately
|
||||
*/
|
||||
function hideTooltip(tooltip: HTMLElement) {
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.transform = 'scale(0.95)';
|
||||
|
||||
// Clean up autoUpdate when tooltip is hidden
|
||||
if (cleanupAutoUpdate) {
|
||||
cleanupAutoUpdate();
|
||||
cleanupAutoUpdate = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats duration in minutes to human readable format
|
||||
*/
|
||||
function formatDuration(durationMinutes: number): string {
|
||||
const hours = Math.floor(durationMinutes / 60);
|
||||
const minutes = durationMinutes % 60;
|
||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates tooltip content for an activity period
|
||||
*/
|
||||
function createTooltipContent(
|
||||
status: string,
|
||||
durationText: string,
|
||||
windowActivities?: WindowActivityInPeriod[]
|
||||
): string | HTMLElement {
|
||||
if (!windowActivities || windowActivities.length === 0) {
|
||||
return `${status} (${durationText})`;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.maxWidth = '300px';
|
||||
|
||||
// Header with status and duration
|
||||
const header = document.createElement('div');
|
||||
header.style.fontWeight = '600';
|
||||
header.style.marginBottom = '8px';
|
||||
header.textContent = `${status} (${durationText})`;
|
||||
container.appendChild(header);
|
||||
|
||||
// Window activities list
|
||||
const totalActivities = windowActivities.reduce((sum, act) => sum + act.count, 0);
|
||||
|
||||
// Show top 5 activities
|
||||
const topActivities = windowActivities.slice(0, 5);
|
||||
|
||||
topActivities.forEach((activity) => {
|
||||
const activityDiv = document.createElement('div');
|
||||
activityDiv.style.marginTop = '4px';
|
||||
activityDiv.style.fontSize = '11px';
|
||||
activityDiv.style.opacity = '0.9';
|
||||
activityDiv.style.display = 'flex';
|
||||
activityDiv.style.alignItems = 'center';
|
||||
activityDiv.style.gap = '6px';
|
||||
|
||||
// Add icon if available
|
||||
if (activity.icon) {
|
||||
const icon = document.createElement('img');
|
||||
icon.src = activity.icon;
|
||||
icon.alt = activity.appName;
|
||||
icon.style.width = '16px';
|
||||
icon.style.height = '16px';
|
||||
icon.style.borderRadius = '2px';
|
||||
icon.style.flexShrink = '0';
|
||||
activityDiv.appendChild(icon);
|
||||
} else {
|
||||
// Placeholder for no icon
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.style.width = '16px';
|
||||
placeholder.style.height = '16px';
|
||||
placeholder.style.borderRadius = '2px';
|
||||
placeholder.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
|
||||
placeholder.style.display = 'flex';
|
||||
placeholder.style.alignItems = 'center';
|
||||
placeholder.style.justifyContent = 'center';
|
||||
placeholder.style.fontSize = '8px';
|
||||
placeholder.style.flexShrink = '0';
|
||||
placeholder.textContent = activity.appName.charAt(0).toUpperCase();
|
||||
activityDiv.appendChild(placeholder);
|
||||
}
|
||||
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.style.flex = '1';
|
||||
textSpan.style.overflow = 'hidden';
|
||||
textSpan.style.textOverflow = 'ellipsis';
|
||||
textSpan.style.whiteSpace = 'nowrap';
|
||||
|
||||
const percentage = ((activity.count / totalActivities) * 100).toFixed(0);
|
||||
const activityText = activity.url
|
||||
? `${activity.appName} - ${activity.url}`
|
||||
: activity.appName;
|
||||
|
||||
textSpan.textContent = `${percentage}% ${activityText}`;
|
||||
activityDiv.appendChild(textSpan);
|
||||
|
||||
container.appendChild(activityDiv);
|
||||
});
|
||||
|
||||
// Show "and X more" if there are more activities
|
||||
if (windowActivities.length > 5) {
|
||||
const moreDiv = document.createElement('div');
|
||||
moreDiv.style.marginTop = '4px';
|
||||
moreDiv.style.fontSize = '11px';
|
||||
moreDiv.style.opacity = '0.7';
|
||||
moreDiv.style.fontStyle = 'italic';
|
||||
moreDiv.textContent = `...and ${windowActivities.length - 5} more`;
|
||||
container.appendChild(moreDiv);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,49 +201,32 @@ export function renderActivityStatusBoxes(
|
||||
) {
|
||||
if (!calendarEl) return;
|
||||
|
||||
// Clean up existing activity boxes and markers first
|
||||
// Clean up existing activity boxes
|
||||
const existingBoxes = calendarEl.querySelectorAll('.activity-status-box');
|
||||
existingBoxes.forEach((box) => box.remove());
|
||||
|
||||
// Clean up existing tooltips
|
||||
const existingTooltips = document.querySelectorAll('.activity-status-tooltip');
|
||||
existingTooltips.forEach((tooltip) => tooltip.remove());
|
||||
|
||||
// Remove has-activity-status class from all lanes
|
||||
const allLanes = calendarEl.querySelectorAll('.fc-timegrid-col');
|
||||
allLanes.forEach((lane) => lane.classList.remove('has-activity-status'));
|
||||
|
||||
const timeGrid = calendarEl.querySelector('.fc-timegrid-body');
|
||||
if (!timeGrid) {
|
||||
console.log('No timegrid found');
|
||||
return;
|
||||
}
|
||||
if (!timeGrid) return;
|
||||
|
||||
const lanes = timeGrid.querySelectorAll('.fc-timegrid-col');
|
||||
if (lanes.length === 0) {
|
||||
console.log('No lanes found');
|
||||
return;
|
||||
}
|
||||
if (lanes.length === 0) return;
|
||||
|
||||
console.log(
|
||||
'Rendering activity status boxes, lanes:',
|
||||
lanes.length,
|
||||
'periods:',
|
||||
activityPeriods.length
|
||||
);
|
||||
// Get or reuse the single tooltip instance
|
||||
const tooltip = getOrCreateTooltip();
|
||||
|
||||
// Create a single tooltip instance to be reused
|
||||
const tooltip = createTooltip();
|
||||
// Get slot duration from calendar (fallback to 15 minutes)
|
||||
const slotDurationMinutes = getSlotDuration(calendarEl);
|
||||
|
||||
lanes.forEach((lane: Element, dayIndex: number) => {
|
||||
lanes.forEach((lane: Element) => {
|
||||
// Get the date for this lane from the data attribute
|
||||
const laneEl = lane as HTMLElement;
|
||||
const dateStr = laneEl.getAttribute('data-date');
|
||||
|
||||
if (!dateStr) {
|
||||
console.log('No date attribute found for lane', dayIndex);
|
||||
return;
|
||||
}
|
||||
if (!dateStr) return;
|
||||
|
||||
const laneDate = new Date(dateStr);
|
||||
const laneDateStart = new Date(laneDate);
|
||||
@@ -127,47 +245,46 @@ export function renderActivityStatusBoxes(
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the position and height of the idle box
|
||||
// Calculate actual start and end times for this day
|
||||
const actualStart = periodStart > laneDateStart ? periodStart : laneDateStart;
|
||||
const actualEnd = periodEnd < laneDateEnd ? periodEnd : laneDateEnd;
|
||||
|
||||
// Calculate the position and height of the activity box
|
||||
const { top, height } = calculateBoxPosition(
|
||||
calendarEl,
|
||||
periodStart > laneDateStart ? periodStart : laneDateStart,
|
||||
periodEnd < laneDateEnd ? periodEnd : laneDateEnd
|
||||
actualStart,
|
||||
actualEnd,
|
||||
slotDurationMinutes
|
||||
);
|
||||
|
||||
if (height <= 0) return;
|
||||
|
||||
hasActivityStatusForThisDay = true;
|
||||
|
||||
// Create and append the activity status box
|
||||
const box = document.createElement('div');
|
||||
box.className = `activity-status-box ${period.isIdle ? 'idle' : 'active'}`;
|
||||
box.style.position = 'absolute';
|
||||
box.style.top = `${top}px`;
|
||||
box.style.height = `${height}px`;
|
||||
box.style.width = '8px';
|
||||
box.style.left = '4px';
|
||||
box.style.right = '4px';
|
||||
box.style.zIndex = '10';
|
||||
box.style.cursor = 'default';
|
||||
|
||||
// Calculate duration in minutes
|
||||
const actualStart = periodStart > laneDateStart ? periodStart : laneDateStart;
|
||||
const actualEnd = periodEnd < laneDateEnd ? periodEnd : laneDateEnd;
|
||||
const durationMs = actualEnd.getTime() - actualStart.getTime();
|
||||
const durationMinutes = Math.round(durationMs / 60000);
|
||||
|
||||
// Format duration
|
||||
const hours = Math.floor(durationMinutes / 60);
|
||||
const minutes = durationMinutes % 60;
|
||||
const durationText = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
const durationText = formatDuration(durationMinutes);
|
||||
|
||||
// Add tooltip text based on status
|
||||
const status = period.isIdle ? 'Idling' : 'Active';
|
||||
const tooltipText = `${status} (${durationText})`;
|
||||
|
||||
// Create and append the activity status box
|
||||
const box = document.createElement('div');
|
||||
box.className = `activity-status-box ${period.isIdle ? 'idle' : 'active'}`;
|
||||
box.style.top = `${top}px`;
|
||||
box.style.height = `${height}px`;
|
||||
|
||||
// Store tooltip content generator in data attribute for event delegation
|
||||
const tooltipContent = createTooltipContent(
|
||||
status,
|
||||
durationText,
|
||||
period.windowActivities
|
||||
);
|
||||
|
||||
// Add hover event listeners for tooltip
|
||||
box.addEventListener('mouseenter', () => {
|
||||
showTooltip(box, tooltip, tooltipText);
|
||||
showTooltip(box, tooltip, tooltipContent);
|
||||
});
|
||||
|
||||
box.addEventListener('mouseleave', () => {
|
||||
@@ -178,8 +295,6 @@ export function renderActivityStatusBoxes(
|
||||
const laneFrame = lane.querySelector('.fc-timegrid-col-frame');
|
||||
if (laneFrame) {
|
||||
laneFrame.appendChild(box);
|
||||
} else {
|
||||
console.log('No lane frame found');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -190,18 +305,43 @@ export function renderActivityStatusBoxes(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the slot duration from the calendar configuration
|
||||
*/
|
||||
function getSlotDuration(calendarEl: HTMLElement): number {
|
||||
const slotsEl = calendarEl.querySelectorAll('.fc-timegrid-slot');
|
||||
if (slotsEl.length < 2) return 15; // Default to 15 minutes
|
||||
|
||||
// Try to calculate from the time difference between slots
|
||||
const firstSlot = slotsEl[0] as HTMLElement;
|
||||
const secondSlot = slotsEl[1] as HTMLElement;
|
||||
|
||||
const firstTime = firstSlot.getAttribute('data-time');
|
||||
const secondTime = secondSlot.getAttribute('data-time');
|
||||
|
||||
if (firstTime && secondTime) {
|
||||
const [h1, m1] = firstTime.split(':').map(Number);
|
||||
const [h2, m2] = secondTime.split(':').map(Number);
|
||||
const diff = h2 * 60 + m2 - (h1 * 60 + m1);
|
||||
if (diff > 0) return diff;
|
||||
}
|
||||
|
||||
// Fallback to 15 minutes
|
||||
return 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the pixel position and height for an activity status box
|
||||
*/
|
||||
function calculateBoxPosition(
|
||||
calendarEl: HTMLElement,
|
||||
startTime: Date,
|
||||
endTime: Date
|
||||
endTime: Date,
|
||||
slotDurationMinutes: number
|
||||
): { top: number; height: number } {
|
||||
// Get the slot duration and slot height
|
||||
const slotsEl = calendarEl.querySelectorAll('.fc-timegrid-slot');
|
||||
if (slotsEl.length === 0) {
|
||||
console.log('No slots found');
|
||||
return { top: 0, height: 0 };
|
||||
}
|
||||
|
||||
@@ -209,8 +349,6 @@ function calculateBoxPosition(
|
||||
const firstSlot = slotsEl[0] as HTMLElement;
|
||||
const slotHeight = firstSlot.offsetHeight;
|
||||
|
||||
// Each slot is 15 minutes by default (configured in TimeEntryCalendar)
|
||||
const slotDurationMinutes = 15;
|
||||
const pixelsPerMinute = slotHeight / slotDurationMinutes;
|
||||
|
||||
// Calculate start position (minutes from midnight)
|
||||
@@ -224,6 +362,20 @@ function calculateBoxPosition(
|
||||
return { top, height };
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup function to remove tooltip from DOM
|
||||
*/
|
||||
export function cleanupActivityStatusPlugin() {
|
||||
if (tooltipInstance) {
|
||||
tooltipInstance.remove();
|
||||
tooltipInstance = null;
|
||||
}
|
||||
if (cleanupAutoUpdate) {
|
||||
cleanupAutoUpdate();
|
||||
cleanupAutoUpdate = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FullCalendar plugin to display idle/active status boxes in the time grid
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/Components/ui/popover';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
||||
import Button from '../Buttons/Button.vue';
|
||||
import { RangeCalendar } from '@/Components/ui/range-calendar';
|
||||
import { RangeCalendar } from '../range-calendar';
|
||||
import { CalendarDate } from '@internationalized/date';
|
||||
import { CalendarIcon } from 'lucide-vue-next';
|
||||
import { computed, ref, inject, type ComputedRef, watch } from 'vue';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/Components/ui/popover';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
|
||||
109
resources/js/packages/ui/src/TimezoneMismatchModal.vue
Normal file
109
resources/js/packages/ui/src/TimezoneMismatchModal.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<script setup lang="ts">
|
||||
import SecondaryButton from './Buttons/SecondaryButton.vue';
|
||||
import DialogModal from './DialogModal.vue';
|
||||
import PrimaryButton from './Buttons/PrimaryButton.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { getUserTimezone } from './utils/settings';
|
||||
import { getDayJsInstance } from './utils/time';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [timezone: string];
|
||||
cancel: [];
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
saving?: boolean;
|
||||
}>();
|
||||
|
||||
const timezone = ref('');
|
||||
const userTimezone = ref('');
|
||||
const shouldShow = ref(false);
|
||||
|
||||
const hideTimezoneMismatchModal = useSessionStorage<boolean>('hide-timezone-mismatch-modal', false);
|
||||
|
||||
/**
|
||||
* Check if timezone mismatch exists and should be shown
|
||||
*/
|
||||
function checkTimezoneMismatch(): boolean {
|
||||
timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
userTimezone.value = getUserTimezone();
|
||||
|
||||
const now = getDayJsInstance()();
|
||||
|
||||
const hasMismatch =
|
||||
now.tz(timezone.value).format() !== now.tz(userTimezone.value).format() &&
|
||||
!hideTimezoneMismatchModal.value;
|
||||
|
||||
shouldShow.value = hasMismatch;
|
||||
return hasMismatch;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkTimezoneMismatch();
|
||||
if (shouldShow.value) {
|
||||
show.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
function submit() {
|
||||
emit('update', timezone.value);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
show.value = false;
|
||||
hideTimezoneMismatchModal.value = true;
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
// Expose methods for parent component
|
||||
defineExpose({
|
||||
checkTimezoneMismatch,
|
||||
currentTimezone: timezone,
|
||||
userTimezone,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogModal closeable :show="show && shouldShow" @close="cancel">
|
||||
<template #title>
|
||||
<div class="flex justify-center">
|
||||
<span> Timezone mismatch detected </span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1 space-y-2">
|
||||
<p>
|
||||
The timezone of your device does not match the timezone in your user
|
||||
settings. <br />
|
||||
<strong
|
||||
>We highly recommend that you update your timezone settings to your
|
||||
current timezone.</strong
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Want to change your timezone setting from
|
||||
<strong>{{ userTimezone }}</strong> to <strong>{{ timezone }}</strong
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="cancel"> Cancel</SecondaryButton>
|
||||
<PrimaryButton
|
||||
class="ms-3"
|
||||
:class="{ 'opacity-25': saving }"
|
||||
:disabled="saving"
|
||||
@click="submit()">
|
||||
Update timezone
|
||||
</PrimaryButton>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from '../utils/cn';
|
||||
import { AccordionContent, type AccordionContentProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from '../utils/cn';
|
||||
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from '../utils/cn';
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import { AccordionHeader, AccordionTrigger, type AccordionTriggerProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
@@ -37,7 +37,12 @@ import MoreOptionsDropdown from './MoreOptionsDropdown.vue';
|
||||
import FullCalendarEventContent from './FullCalendar/FullCalendarEventContent.vue';
|
||||
import FullCalendarDayHeader from './FullCalendar/FullCalendarDayHeader.vue';
|
||||
import TimeEntryCalendar from './FullCalendar/TimeEntryCalendar.vue';
|
||||
import DateRangePicker from './Input/DateRangePicker.vue';
|
||||
import TimezoneMismatchModal from './TimezoneMismatchModal.vue';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip/index';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './accordion/index';
|
||||
import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from './popover/index';
|
||||
import { RangeCalendar } from './range-calendar/index';
|
||||
export type { ActivityPeriod } from './FullCalendar/idleStatusPlugin';
|
||||
|
||||
export {
|
||||
@@ -69,8 +74,19 @@ export {
|
||||
FullCalendarEventContent,
|
||||
FullCalendarDayHeader,
|
||||
TimeEntryCalendar,
|
||||
DateRangePicker,
|
||||
TimezoneMismatchModal,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
PopoverAnchor,
|
||||
RangeCalendar,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user