Merge branch 'beta' into 'main'

2025.20.1

See merge request fedeo/software!36
This commit is contained in:
2025-11-14 19:00:37 +00:00
10 changed files with 114 additions and 40 deletions

View File

@@ -75,7 +75,7 @@ const changeActivePhase = async (key) => {
return p
})
const res = await useEntities("projects").update(item.id, {phases:item.phases})
const res = await useEntities("projects").update(item.id, {phases:item.phases,active_phase: item.phases.find(i => i.active).label})
//const {error:updateError} = await supabase.from("projects").update({phases: item.phases}).eq("id",item.id)

View File

@@ -38,6 +38,20 @@ export function useStaffTime() {
})
}
async function submit(id: string) {
return await $api<StaffTimeEntry>(`/api/staff/time/${id}`, {
method: 'PUT',
body: { state: 'submitted' },
})
}
async function approve(id: string) {
return await $api<StaffTimeEntry>(`/api/staff/time/${id}`, {
method: 'PUT',
body: { state: 'approved' },
})
}
async function get(id: string) {
return await $api<StaffTimeEntry>(`/api/staff/time/${id}`, { method: 'GET' })
}
@@ -50,5 +64,5 @@ export function useStaffTime() {
return await $api(`/api/staff/time/${id}`, { method: 'PUT', body: data })
}
return { list, start, stop, get, create, update }
return { list, start, stop,submit,approve, get, create, update }
}

View File

@@ -76,7 +76,7 @@ const calculateOpenSum = (statement) => {
return (statement.amount - startingAmount).toFixed(2)
}
const selectedFilters = ref(tempStore.filters["banking"] ? tempStore.filters["banking"] : ['Nur offene anzeigen'])
const selectedFilters = ref(tempStore.filters?.["banking"]?.["main"] ? tempStore.filters["banking"]["main"] : ['Nur offene anzeigen'])
const filteredRows = computed(() => {
let temp = bankstatements.value
@@ -170,7 +170,7 @@ setupPage()
:options="['Nur offene anzeigen','Nur positive anzeigen','Nur negative anzeigen']"
:color="selectedFilters.length > 0 ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }"
@change="tempStore.modifyFilter('banking',selectedFilters)"
@change="tempStore.modifyFilter('banking','main',selectedFilters)"
>
<template #label>
Filter

View File

@@ -152,6 +152,7 @@ const findIncomingInvoiceErrors = computed(() => {
itemInfo.value.accounts.forEach(account => {
if(account.account === null) errors.push({message: "Es ist keine Kategorie ausgewählt", type: "breaking"})
if(!accounts.value.find(i => i.id === account.account)) errors.push({message: "Es ist keine Kategorie ausgewählt", type: "breaking"})
if(account.amountNet === null) errors.push({message: "Es ist kein Nettobetrag angegeben", type: "breaking"})
if(account.taxType === null) errors.push({message: "Es ist kein Steuertyp ausgewählt", type: "breaking"})
if(account.costCentre === null) errors.push({message: "Es ist keine Kostenstelle ausgewählt", type: "info"})
@@ -379,7 +380,7 @@ const findIncomingInvoiceErrors = computed(() => {
:search-attributes="['label']"
searchable-placeholder="Suche..."
v-model="item.account"
:color="!item.account ? 'rose' : 'primary'"
:color="(item.account && accounts.find(i => i.id === item.account)) ? 'primary' : 'rose'"
>
<template #label>
{{accounts.find(account => account.id === item.account) ? accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}

View File

@@ -208,11 +208,11 @@ changeRange()
</template>
<template #start-data="{ row }">
{{ $dayjs(row.startDate).format('HH:mm DD.MM.YY') }} Uhr
{{ $dayjs(row.started_at).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #end-data="{ row }">
{{ $dayjs(row.endDate).format('HH:mm DD.MM.YY') }} Uhr
{{ $dayjs(row.stopped_at).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #duration-data="{ row }">

View File

@@ -2,7 +2,7 @@
import { useStaffTime } from '~/composables/useStaffTime'
import { useAuthStore } from '~/stores/auth'
const { list, start, stop } = useStaffTime()
const { list, start, stop, submit,approve } = useStaffTime()
const auth = useAuthStore()
const router = useRouter()
@@ -51,6 +51,16 @@ function handleEdit(entry: any) {
showModal.value = true
}
async function handleSubmit(entry: any) {
await submit(entry.id)
await load()
}
async function handleApprove(entry: any) {
await approve(entry.id)
await load()
}
onMounted(async () => {
await loadUsers()
await load()
@@ -115,14 +125,19 @@ onMounted(async () => {
/>
<!-- 🔹 Button zur Auswertung -->
<UButton
v-if="selectedUser"
color="gray"
icon="i-heroicons-chart-bar"
label="Auswertung"
variant="soft"
@click="router.push(`/staff/time/${selectedUser}/evaluate`)"
/>
<UTooltip
:text="selectedUser ? 'Anwesenheiten des Mitarbeiters auswerten' : 'Mitarbeiter für die Auswertung auswählen'"
>
<UButton
:disabled="!selectedUser"
color="gray"
icon="i-heroicons-chart-bar"
label="Auswertung"
variant="soft"
@click="router.push(`/staff/time/${selectedUser}/evaluate`)"
/>
</UTooltip>
</div>
</template>
</UDashboardToolbar>
@@ -131,15 +146,15 @@ onMounted(async () => {
<UDashboardPanelContent>
<UTable
:rows="entries"
@select="(row) => handleEdit(row)"
:columns="[
{ key: 'actions', label: '' },
{ key: 'state', label: 'Status' },
{ key: 'started_at', label: 'Start' },
{ key: 'stopped_at', label: 'Ende' },
{ key: 'duration_minutes', label: 'Dauer' },
{ key: 'description', label: 'Beschreibung' },
...(canViewAll ? [{ key: 'user_name', label: 'Benutzer' }] : []),
{ key: 'actions', label: '' },
]"
>
<template #state-data="{row}">
@@ -163,7 +178,40 @@ onMounted(async () => {
{{ row.user_id ? users.find(i => i.user_id === row.user_id).full_name : '-' }}
</template>
<template #actions-data="{ row }">
<UButton variant="ghost" icon="i-heroicons-pencil-square" @click="handleEdit(row)" />
<UTooltip
text="Zeit genehmigen"
v-if="row.state === 'submitted'"
>
<UButton
variant="ghost"
icon="i-heroicons-check-circle"
@click="handleApprove(row)"
/>
</UTooltip>
<UTooltip
text="Zeit einreichen"
v-if="row.state === 'draft'"
>
<UButton
variant="ghost"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="handleSubmit(row)"
/>
</UTooltip>
<UTooltip
text="Zeit bearbeiten"
v-if="row.state === 'draft'"
>
<UButton
variant="ghost"
icon="i-heroicons-pencil-square"
@click="handleEdit(row)"
/>
</UTooltip>
</template>
</UTable>
</UDashboardPanelContent>

View File

@@ -86,9 +86,7 @@ const changePage = (number) => {
setupPage()
}
const resetColum = (column) => {
columnsToFilter.value[column] = itemsMeta.value.distinctValues[column]
}
const changeSort = (column) => {
if(sort.value.column === column) {
@@ -131,7 +129,11 @@ const setupPage = async () => {
items.value = data
itemsMeta.value = meta
if(!initialSetupDone.value){
Object.keys(itemsMeta.value.distinctValues).forEach(distinctValue => {
Object.keys(tempStore.filters[type] || {}).forEach((column) => {
columnsToFilter.value[column] = tempStore.filters[type][column]
})
Object.keys(itemsMeta.value.distinctValues).filter(i => !Object.keys(tempStore.filters[type] || {}).includes(i)).forEach(distinctValue => {
columnsToFilter.value[distinctValue] = itemsMeta.value.distinctValues[distinctValue]
})
}
@@ -144,8 +146,14 @@ const setupPage = async () => {
setupPage()
const handleFilterChange = async (action,column) => {
if(action === 'reset') {
columnsToFilter.value[column] = itemsMeta.value.distinctValues[column]
} else if(action === 'change') {
tempStore.modifyFilter(type,column,columnsToFilter.value[column])
}
setupPage()
}
@@ -297,7 +305,7 @@ setupPage()
sort-mode="manual"
v-model:sort="sort"
@update:sort="setupPage"
v-if="dataType && columns && items.length > 0"
v-if="dataType && columns && items.length > 0 && !loading"
:rows="items"
:columns="columns"
class="w-full"
@@ -328,7 +336,7 @@ setupPage()
:options="itemsMeta?.distinctValues?.[column.key]"
v-model="columnsToFilter[column.key]"
multiple
@change="setupPage"
@change="handleFilterChange('change', column.key)"
searchable
searchable-placeholder="Suche..."
:search-attributes="[column.key]"
@@ -367,7 +375,7 @@ setupPage()
v-if="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length && column.distinct"
>
<UButton
@click="resetColum(column.key)"
@click="handleFilterChange('reset',column.key)"
variant="outline"
color="rose"
>
@@ -428,7 +436,7 @@ setupPage()
</UTable>
<UCard
class="w-1/3 mx-auto mt-10"
v-else
v-else-if="!loading"
>
<div
class="flex flex-col text-center"
@@ -442,6 +450,7 @@ setupPage()
</UCard>
<UProgress v-else animation="carousel" class="w-3/4 mx-auto mt-5"></UProgress>
</template>
<style scoped>

View File

@@ -35,9 +35,6 @@ export const useAuthStore = defineStore("auth", {
console.log("Auth initStore")
await this.fetchMe()
const tempStore = useTempStore()
if(this.profile.temp_config) tempStore.setStoredTempConfig(this.profile.temp_config)
if(this.activeTenant > 0) {
this.loading = false
if(useCapacitor().getIsNative()) {
@@ -99,6 +96,8 @@ export const useAuthStore = defineStore("auth", {
async fetchMe(jwt= null) {
console.log("Auth fetchMe")
const tempStore = useTempStore()
try {
const me = await useNuxtApp().$api("/api/me", {
headers: { Authorization: `Bearer ${jwt}`,
@@ -117,6 +116,8 @@ export const useAuthStore = defineStore("auth", {
this.profile = me.profile
if(this.profile.temp_config) tempStore.setStoredTempConfig(this.profile.temp_config)
if(me.activeTenant > 0) {
this.activeTenant = me.activeTenant
this.activeTenantData = me.tenants.find(i => i.id === me.activeTenant)

View File

@@ -47,10 +47,7 @@ import sepaDate from "~/components/columnRenderings/sepaDate.vue";
// @ts-ignore
export const useDataStore = defineStore('data', () => {
const profileStore = useProfileStore()
const toast = useToast()
const router = useRouter()
const modal = useModal()
const dataTypes = {
tasks: {
@@ -1088,9 +1085,9 @@ export const useDataStore = defineStore('data', () => {
},
sortable: true
},{
key: "phase",
key: "active_phase",
label: "Phase",
component: phase
distinct:true
},{
key: "name",
label: "Name",

View File

@@ -17,6 +17,7 @@ export const useTempStore = defineStore('temp', () => {
columns: columns.value,
pages: pages.value,
settings: settings.value,
filters: filters.value
}
await useNuxtApp().$api(`/api/profiles/${auth.profile.id}`,{
@@ -30,6 +31,7 @@ export const useTempStore = defineStore('temp', () => {
columns.value = config.columns
pages.value = config.pages
settings.value = config.settings
filters.value = config.filters || {}
}
function modifySearchString(type,input) {
@@ -42,8 +44,10 @@ export const useTempStore = defineStore('temp', () => {
storeTempConfig()
}
function modifyFilter(type,input) {
filters.value[type] = input
function modifyFilter(domain,type,input) {
if(!filters.value[domain]) filters.value[domain] = {}
filters.value[domain][type] = input
storeTempConfig()
}