fix: selects

This commit is contained in:
2026-04-01 13:41:21 +02:00
parent 729b54ca1a
commit aa57d38bd6
12 changed files with 197 additions and 139 deletions

View File

@@ -7,3 +7,37 @@ import { RouterView } from 'vue-router'
<RouterView />
</Suspense>
</template>
<!-- <template>
<component :is="layoutComponent">
<Suspense>
<RouterView />
</Suspense>
</component>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import DefaultLayout from '@/layouts/default.vue'
import EmptyLayout from '@/layouts/empty.vue'
const route = useRoute()
const layouts = {
default: DefaultLayout,
auth: EmptyLayout
}
console.log(route.fullPath)
console.log(route.name)
console.log(route.matched)
const layoutComponent = computed(() => {
console.log(route.meta);
return layouts[route.meta.layout as keyof typeof layouts] || DefaultLayout
})
</script> -->

View File

@@ -8,9 +8,9 @@ export const uiOptions: NuxtUIOptions = {
}
},
button: {
slots: {
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
},
// slots: {
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
// },
},
input: {
slots: {
@@ -40,9 +40,10 @@ export const uiOptions: NuxtUIOptions = {
},
selectMenu: {
slots: {
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
itemLeadingIcon: 'text-(--black)! dark:text-white!'
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80',
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
// itemLeadingIcon: 'text-(--black)! dark:text-white!'
}
},

1
bo/src/assets/error.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none"><path stroke="#5b5b5b" stroke-width="2" d="M3 11c0-3.771 0-5.657 1.172-6.828S7.229 3 11 3h2c3.771 0 5.657 0 6.828 1.172S21 7.229 21 11v2c0 3.771 0 5.657-1.172 6.828S16.771 21 13 21h-2c-3.771 0-5.657 0-6.828-1.172S3 16.771 3 13z"/><path fill="#5b5b5b" fill-rule="evenodd" d="m19 13.585l-.02-.02c-.39-.39-.726-.726-1.026-.979c-.317-.267-.662-.502-1.088-.63a3 3 0 0 0-1.732 0c-.426.129-.77.363-1.088.63c-.3.253-.636.59-1.025.98l-.029.028c-.307.306-.487.486-.628.601l-.02.017l-.012-.023c-.087-.16-.189-.393-.36-.792l-.053-.124l-.022-.052c-.356-.83-.655-1.528-.95-2.054c-.305-.541-.685-1.05-1.277-1.346a3 3 0 0 0-1.597-.307c-.66.055-1.201.386-1.685.775c-.406.327-.86.77-1.388 1.297V13q0 .774.003 1.411l1.114-1.114c.69-.69 1.15-1.147 1.525-1.45c.37-.298.53-.335.6-.34a1 1 0 0 1 .532.102c.061.031.196.124.43.54c.236.42.493 1.016.877 1.912l.053.124l.017.038c.149.348.287.67.425.924c.145.265.355.583.709.802a2 2 0 0 0 1.398.27c.41-.073.723-.29.956-.482c.222-.184.47-.432.738-.7l.03-.03c.425-.425.701-.7.928-.891c.218-.184.32-.228.376-.245a1 1 0 0 1 .578 0c.056.017.158.061.376.245c.227.191.503.466.929.892l1.35 1.35c.046-.718.054-1.61.056-2.773" clip-rule="evenodd"/><circle cx="16.5" cy="7.5" r="1.5" fill="#5b5b5b"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -9,7 +9,7 @@ body {
font-family: "Inter", sans-serif;
}
.container{
.container {
max-width: 2100px;
}
@@ -30,7 +30,7 @@ body {
/* text */
--accent-blue-dark: #3B82F6;
--accent-blue-light:#2563EB;
--accent-blue-light: #2563EB;
--text-dark: #FFFEFB;
/* placeholder */
@@ -52,22 +52,25 @@ body {
--ui-bg-elevated: var(--color-gray-500);
--ui-error: var(--dark-red);
--border: var(--border-dark);
--tw-border-style: var(--border-dark);
--tw-border-style: var(--border-dark);
}
.label-form {
@apply text-(--gray) dark:text-(--gray-dark) pl-0 md:pl-6 leading-none;
}
.label-form {
@apply text-(--gray) dark:text-(--gray-dark) pl-0 md:pl-6 leading-none;
}
.title {
@apply font-medium text-[19px] sm:text-xl md:text-[22px] leading-none text-(--black) dark:text-(--main-light);
}
.title {
@apply font-medium text-[19px] sm:text-xl md:text-[22px] leading-none text-(--black) dark:text-(--main-light);
}
.column-title {
@apply md:ml-[25px] mb-[25px] sm:mb-[30px];
}
.column-title {
@apply md:ml-[25px] mb-[25px] sm:mb-[30px];
}
.form-title {
@apply text-(--accent-green) dark:text-(--accent-green-dark) font-medium;
}
.form-title {
@apply text-(--accent-green) dark:text-(--accent-green-dark) font-medium;
}
.blue-button {
@apply bg-info! text-white!
}

View File

@@ -1,32 +1,32 @@
<template>
<header
class="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
<!-- px-4 sm:px-6 lg:px-8 -->
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-14">
<!-- Logo -->
<div class="flex items-center justify-between h-16">
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center">
<UIcon name="i-heroicons-clock" class="w-5 h-5" />
</div>
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
<span class="font-semibold text-gray-900 dark:text-white">{{ settings.app.name }}</span>
</RouterLink>
<UNavigationMenu :type="'trigger'" :ui="{
root: 'justify-center',
list: 'gap-4'
}" :items="menuItems" class="w-full"></UNavigationMenu>
<div class="flex items-center gap-2">
<CountryCurrencySwitch />
<!-- Language Switcher -->
<LangSwitch />
<!-- Theme Switcher -->
<ThemeSwitch />
<!-- Logout Button (only when authenticated) -->
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)">
{{ $t('general.logout') }}
</button>
list: 'gap-4 text-(--black)',
linkLabel: 'text-(--black) text-sm!'
}" :items="menuItems" class="" />
<div class="flex items-center gap-12">
<div class="flex items-center gap-2">
<CountryCurrencySwitch />
<LangSwitch />
</div>
<div class="flex items-center gap-2">
<ThemeSwitch />
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
{{ $t('general.logout') }}
</button>
</div>
</div>
</div>
</div>
@@ -44,6 +44,7 @@ import type { LabelTrans, TopMenuItem } from '@/types'
import type { NavigationMenuItem } from '@nuxt/ui'
import { useRoute, useRouter } from 'vue-router'
import CountryCurrencySwitch from './inner/CountryCurrencySwitch.vue'
import { settings } from '@/router/settings'
const authStore = useAuthStore()
let menu = ref()

View File

@@ -7,7 +7,7 @@
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
</div>
</div> -->
</div>
</component>
</suspense>
</template>
@@ -173,6 +173,7 @@ const UInput = resolveComponent('UInput')
const UButton = resolveComponent('UButton')
const UIcon = resolveComponent('UIcon')
import errorImg from '@/assets/error.svg'
const columns: TableColumn<Product>[] = [
{
accessorKey: 'product_id',
@@ -209,9 +210,13 @@ const columns: TableColumn<Product>[] = [
cell: ({ row }) => {
return h('img', {
src: row.getValue('image_link') as string,
style: 'width:40px;height:40px;object-fit:cover;'
style: 'width:40px;height:40px;object-fit:cover;',
onError: (e: Event) => {
const target = e.target as HTMLImageElement
target.src = errorImg
}
})
}
},
},
{
accessorKey: 'name',
@@ -272,8 +277,9 @@ const columns: TableColumn<Product>[] = [
onClick: () => {
goToProduct(row.original.product_id)
},
color: 'primary',
variant: 'solid'
class: 'cursor-pointer',
color: 'info',
variant: 'soft'
}, () => 'Show product')
},
}

View File

@@ -1,22 +1,19 @@
<template>
<component :is="Default || 'div'">
<p @click="backToProducts()">Back to products</p>
<div class="container my-10 mx-auto">
<div class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
<div
class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
<div class="flex items-center gap-3">
<template v-if="!isViewingExistingTranslation">
<USelectMenu
v-model="selectedLangId"
:items="langOptions"
value-key="id"
class="w-40"
:search-input="false"
>
<USelectMenu v-model="selectedLangId" :items="langOptions" value-key="id" class="w-40"
:search-input="false">
<template #default="{ modelValue }">
<span class="dark:text-white text-black">
{{ langOptions.find(l => l.id === modelValue)?.name }}
{{langOptions.find(l => l.id === modelValue)?.name}}
</span>
</template>
@@ -27,24 +24,15 @@
</template>
</USelectMenu>
<UButton
@click="translateOrSave"
:disabled="selectedLangId === defaultLangId"
:loading="translating"
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!"
>
<UButton @click="translateOrSave" :disabled="selectedLangId === defaultLangId" :loading="translating"
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
Translate
</UButton>
</template>
<template v-else>
<UButton
@click="goBackToDefault"
color="neutral"
variant="outline"
class="cursor-pointer"
>
<UButton @click="goBackToDefault" color="neutral" variant="outline" class="cursor-pointer">
<UIcon name="i-lucide-arrow-left" class="mr-1" />
<p class="dark:text-white">Back to Polish</p>
</UButton>
@@ -116,25 +104,34 @@
<!-- USAGE -->
<div v-if="activeTab === 'usage'" class="box">
<div class="flex justify-end mb-4 gap-2">
<UButton v-if="!isEditing" @click="enableUsageEdit" class="bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)! text-white px-3 py-1 rounded">
<UButton v-if="!isEditing" @click="enableUsageEdit"
class="bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)! text-white px-3 py-1 rounded">
Edit Usage
</UButton>
<UButton v-if="isEditing" @click="saveUsage" class="px-3 py-1 border rounded">Save</UButton>
<UButton v-if="isEditing" @click="cancelUsageEdit" class="px-3 py-1 border rounded">Cancel</UButton>
</div>
<p ref="usageRef" v-html="displayedUsage || 'Usage information not available'" class="text-black dark:text-white"></p>
<p ref="usageRef" v-html="displayedUsage || 'Usage information not available'"
class="text-black dark:text-white">
</p>
</div>
<!-- DESCRIPTION -->
<div v-if="activeTab === 'description'" class="box">
<div class="flex justify-end mb-4 gap-2">
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit" class="bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)! text-white px-3 py-1 rounded">
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
class="bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)! text-white px-3 py-1 rounded">
Edit Description
</UButton>
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" class="px-3 py-1 border rounded">Save</UButton>
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" class="px-3 py-1 border rounded">Cancel</UButton>
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" class="px-3 py-1 border rounded">
Save
</UButton>
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit"
class="px-3 py-1 border rounded">
Cancel</UButton>
</div>
<div ref="descriptionRef" v-html="displayedDescription || 'Description not available'" class="text-black dark:text-white"></div>
<div ref="descriptionRef" v-html="displayedDescription || 'Description not available'"
class="text-black dark:text-white"></div>
</div>
</div>

View File

@@ -1,29 +1,29 @@
<template>
<p>Country: {{ currentCountry?.name }}</p>
<p>Currency: {{ currentCountry?.ps_currency.iso_code }}</p>
<USelectMenu v-model="country" :items="countries"
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="id"
:searchInput="false">
<template #default="{ modelValue }">
<div class="flex items-center gap-1">
<span class="font-medium dark:text-white text-black">{{ modelValue.name }}</span>
</div>
</template>
<template #item-leading="{ item }">
<div class="flex items-center rounded-md cursor-pointer transition-colors">
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
</div>
</template>
</USelectMenu>
<div class="flex flex-col">
<p class="text-sm">Country/Currency:</p>
<USelectMenu v-model="country" :items="countries"
class="w-44 bg-(--main-light) dark:bg-(--black) rounded-md hover:none! text-sm!" valueKey="id"
:searchInput="false">
<template #default="{ modelValue }">
<div class="flex items-center gap-1">
<span class="font-medium dark:text-white text-black whitespace-nowrap">{{ modelValue.name }} / {{
currentCountry?.ps_currency.iso_code }}</span>
</div>
</template>
<template #item-leading="{ item }">
<div class="flex items-center rounded-md cursor-pointer transition-colors">
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
</div>
</template>
</USelectMenu>
</div>
</template>
<script setup lang="ts">
import { countries, currentCountry } from '@/router/langs'
import { countries, currentCountry, switchLocalization } from '@/router/langs'
import { useRouter, useRoute } from 'vue-router'
import { useCookie } from '@/composable/useCookie'
import { computed, watch } from 'vue'
import { i18n } from '@/plugins/02_i18n'
import { useFetchJson } from '@/composable/useFetchJson'
const router = useRouter()
const route = useRoute()
@@ -34,32 +34,17 @@ const country = computed({
return currentCountry.value
},
set(value: string) {
// i18n.locale.value = value
currentCountry.value = countries.find((x) => x.id == Number(value))
cookie.setCookie('country_id', `${countries.find((x) => x.id == Number(value))?.id}`, { days: 60, secure: true, sameSite: 'Lax' })
// changeLang()
switchLocalization()
},
})
// async function changeLang() {
// try {
// const { items } = await useFetchJson('/api/v1/public/auth/update-choice', {
// method: 'POST'
// })
// } catch (error) {
// console.log(error)
// }
// }
watch(
() => country,
(newCountry) => {
if (newCountry) {
console.log(newCountry.value);
// i18n.locale.value = newLocale
currentCountry.value = countries.find((x) => x.id == Number(newCountry.value?.id))
}
},

View File

@@ -1,34 +1,37 @@
<template>
<USelectMenu v-model="locale" :items="langs"
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="iso_code"
:searchInput="false">
<template #default="{ modelValue }">
<div class="flex items-center gap-1">
<!-- <span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.flag}}</span> -->
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.name}}</span>
</div>
</template>
<template #item-leading="{ item }">
<div class="flex items-center rounded-md cursor-pointer transition-colors">
<!-- <span class="text-md ">{{ item.flag }}</span> -->
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
</div>
</template>
</USelectMenu>
<div class="flex flex-col">
<p class="text-sm">Language:</p>
<USelectMenu v-model="locale" :items="langs"
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="iso_code"
:searchInput="false">
<template #default="{ modelValue }">
<div class="flex items-center gap-1">
<!-- <span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.flag}}</span> -->
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code ==
modelValue)?.name}}</span>
</div>
</template>
<template #item-leading="{ item }">
<div class="flex items-center rounded-md cursor-pointer transition-colors">
<!-- <span class="text-md ">{{ item.flag }}</span> -->
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
</div>
</template>
</USelectMenu>
</div>
</template>
<script setup lang="ts">
import { langs, currentLang } from '@/router/langs'
import { langs, currentLang, switchLocalization } from '@/router/langs'
import { useRouter, useRoute } from 'vue-router'
import { useCookie } from '@/composable/useCookie'
import { computed, watch } from 'vue'
import { i18n } from '@/plugins/02_i18n'
import { useFetchJson } from '@/composable/useFetchJson'
const router = useRouter()
const route = useRoute()
const cookie = useCookie()
const locale = computed({
get() {
return currentLang.value?.iso_code || i18n.locale.value
@@ -52,21 +55,10 @@ const locale = computed({
}
}
changeLang()
switchLocalization()
},
})
async function changeLang() {
try {
const { items } = await useFetchJson('/api/v1/public/auth/update-choice', {
method: 'POST'
})
} catch (error) {
console.log(error)
}
}
watch(
() => route.params.locale,
(newLocale) => {

View File

@@ -1,9 +1,34 @@
<template>
<UButton variant="ghost" size="sm" @click="themeStorage.setTheme()">
<UButton variant="outline" size="sm" color="info" @click="themeStorage.setTheme()">
<span class="hidden sm:inline">
<UIcon class="size-5" :name="themeStorage.themeIcon" />
</span>
</UButton>
<!-- <UButton variant="solid" size="sm" color="info" @click="themeStorage.setTheme()">
<span class="hidden sm:inline">
<UIcon class="size-5" :name="themeStorage.themeIcon" />
</span>
</UButton>
<UButton variant="soft" size="sm" color="info" @click="themeStorage.setTheme()">
<span class="hidden sm:inline">
<UIcon class="size-5" :name="themeStorage.themeIcon" />
</span>
</UButton>
<UButton variant="subtle" size="sm" color="info" @click="themeStorage.setTheme()">
<span class="hidden sm:inline">
<UIcon class="size-5" :name="themeStorage.themeIcon" />
</span>
</UButton>
<UButton variant="ghost" size="sm" color="info" @click="themeStorage.setTheme()">
<span class="hidden sm:inline">
<UIcon class="size-5" :name="themeStorage.themeIcon" />
</span>
</UButton>
<UButton variant="link" size="sm" color="info" @click="themeStorage.setTheme()">
<span class="hidden sm:inline">
<UIcon class="size-5" :name="themeStorage.themeIcon" />
</span>
</UButton> -->
</template>
<script setup lang="ts">

View File

@@ -59,7 +59,7 @@ async function setRoutes() {
const componentName = item.component
const [, folder] = componentName.split('/')
const componentPath = `/src${componentName}`
let modules =
folder === 'views' ? viewModules : componentModules
@@ -80,6 +80,8 @@ async function setRoutes() {
meta: item.meta ? JSON.parse(item.meta) : {}
})
}
// await router.replace(router.currentRoute.value.fullPath)
}
await setRoutes()

View File

@@ -51,3 +51,14 @@ export async function initCountryCurrency() {
console.error('Failed to fetch languages:', error)
}
}
export async function switchLocalization() {
try {
await useFetchJson('/api/v1/public/auth/update-choice', {
method: 'POST'
})
} catch (error) {
console.log(error)
}
}