Merge branch 'devCorrected' into 'beta'
fixed archived filtering See merge request fedeo/software!9
This commit is contained in:
74
components/ArchiveButton.vue
Normal file
74
components/ArchiveButton.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
const emit = defineEmits(['confirmed'])
|
||||
|
||||
const props = defineProps({
|
||||
color: {
|
||||
type: String,
|
||||
required:false
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
required:false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required:false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const {color,variant, type} = props
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
|
||||
const showModal = ref(false)
|
||||
|
||||
const emitConfirm = () => {
|
||||
showModal.value = false
|
||||
emit('confirmed')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton
|
||||
:color="color"
|
||||
:variant="variant"
|
||||
@click="showModal = true"
|
||||
>
|
||||
Archivieren
|
||||
</UButton>
|
||||
<UModal v-model="showModal">
|
||||
<UCard>
|
||||
<template #header>
|
||||
<span class="text-md font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie diese/-s/-n {{dataType.labelSingle}} wirklich archivieren?
|
||||
|
||||
<template #footer>
|
||||
<div class="text-right">
|
||||
<UButtonGroup>
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="showModal = false"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="emitConfirm"
|
||||
class="ml-2"
|
||||
color="rose"
|
||||
>
|
||||
Archivieren
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -171,7 +171,7 @@ const moveFile = async () => {
|
||||
|
||||
</template>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-1/3">
|
||||
<div :class="useCapacitor().getIsNative() ? ['w-full'] : ['w-1/3']">
|
||||
<object
|
||||
class="bigPreview"
|
||||
:data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
||||
@@ -186,21 +186,14 @@ const moveFile = async () => {
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
<div class="w-2/3 p-5">
|
||||
<div class="w-2/3 p-5" v-if="!useCapacitor().getIsNative()">
|
||||
<UButtonGroup>
|
||||
<ButtonWithConfirm
|
||||
<ArchiveButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
type="files"
|
||||
@confirmed="archiveDocument"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie die Datei wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
/>
|
||||
|
||||
<UButton
|
||||
:to="props.documentData.url"
|
||||
|
||||
@@ -254,20 +254,13 @@ const updateItem = async () => {
|
||||
>{{item.id ? `${dataType.labelSingle} bearbeiten` : `${dataType.labelSingle} erstellen` }}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
v-if="platform !== 'mobile'"
|
||||
<ArchiveButton
|
||||
color="rose"
|
||||
v-if="platform !== 'mobile'"
|
||||
variant="outline"
|
||||
@confirmed="dataStore.updateItem(type,{...item,archived: true}, oldItem)"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black dark:text-white font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie das {{dataType.labelSingle}} {{item[dataType.templateColumns.find(i => i.title).key]}} wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
:type="type"
|
||||
@confirmed="useEntities(type).archive(item.id)"
|
||||
/>
|
||||
<UButton
|
||||
v-if="item.id"
|
||||
@click="updateItem"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {useTempStore} from "~/stores/temp.js";
|
||||
import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
|
||||
import EntityTable from "~/components/EntityTable.vue";
|
||||
import EntityListMobile from "~/components/EntityListMobile.vue";
|
||||
import EntityTableMobile from "~/components/EntityTableMobile.vue";
|
||||
|
||||
const { has } = usePermission()
|
||||
|
||||
@@ -145,6 +145,7 @@ const filteredRows = computed(() => {
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #left v-if="$slots['left-toolbar']">
|
||||
<slot name="left-toolbar"/>
|
||||
@@ -181,7 +182,7 @@ const filteredRows = computed(() => {
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<EntityListMobile
|
||||
<EntityTableMobile
|
||||
v-if="platform === 'mobile'"
|
||||
:type="props.type"
|
||||
:columns="columns"
|
||||
|
||||
@@ -220,7 +220,6 @@ const changePinned = async () => {
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UTabs
|
||||
:items="dataType.showTabs"
|
||||
v-if="props.item.id && platform !== 'mobile'"
|
||||
@@ -326,7 +325,7 @@ const changePinned = async () => {
|
||||
@updateNeeded="emit('updateNeeded')"
|
||||
:platform="platform"
|
||||
/>
|
||||
<EntityShowSubPhases
|
||||
<!--<EntityShowSubPhases
|
||||
:item="props.item"
|
||||
:top-level-type="type"
|
||||
v-else-if="sub.label === 'Phasen'"
|
||||
@@ -355,7 +354,7 @@ const changePinned = async () => {
|
||||
:top-level-type="type"
|
||||
v-else
|
||||
:platform="platform"
|
||||
/>
|
||||
/>-->
|
||||
</div>
|
||||
|
||||
</UDashboardPanelContent>
|
||||
|
||||
190
components/PDFViewer.vue
Normal file
190
components/PDFViewer.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue"
|
||||
import { VPdfViewer } from "@vue-pdf-viewer/viewer"
|
||||
|
||||
const props = defineProps({
|
||||
// Beispiel: "FEDEO/26/filesbyid/11990345-8711-4e23-8851-c50f028fc915/RE25-1081.pdf"
|
||||
fileId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1.2,
|
||||
},
|
||||
})
|
||||
|
||||
const pdfSrc = ref(null) // ObjectURL fürs Viewer
|
||||
const { $api } = useNuxtApp()
|
||||
|
||||
async function loadPdf(id) {
|
||||
try {
|
||||
const arrayBuffer = await $api(`/api/files/download/${id}`, {
|
||||
method: "POST",
|
||||
responseType: "arrayBuffer", // wichtig für pdf.js
|
||||
})
|
||||
const blob = new Blob([arrayBuffer], { type: "application/pdf" })
|
||||
pdfSrc.value = URL.createObjectURL(blob)
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden der PDF:", err)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadPdf(props.fileId))
|
||||
watch(() => props.fileId, (newPath) => {
|
||||
if (newPath) loadPdf(newPath)
|
||||
})
|
||||
|
||||
const vpvRef = ref(null);
|
||||
|
||||
//Zoom Control
|
||||
const zoomControl = computed(() => vpvRef.value?.zoomControl)
|
||||
|
||||
const currentScale = computed(() => {
|
||||
return zoomControl.value?.scale
|
||||
})
|
||||
|
||||
const handleZoomTool = (type) => {
|
||||
const zoomCtrl = unref(zoomControl)
|
||||
if (!zoomCtrl) return
|
||||
|
||||
const scale = unref(currentScale)
|
||||
if (type === "in") {
|
||||
scale && zoomCtrl.zoom(scale + 0.25)
|
||||
} else if (type === "out") {
|
||||
scale && zoomCtrl.zoom(scale - 0.25)
|
||||
} else {
|
||||
zoomCtrl.zoom(type)
|
||||
}
|
||||
}
|
||||
|
||||
//Page Control
|
||||
const pageControl = computed(() => vpvRef.value?.pageControl)
|
||||
const currentPageInput = computed(() => pageControl.value?.currentPage)
|
||||
const searchControl = computed(() => vpvRef.value?.searchControl)
|
||||
const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)
|
||||
|
||||
const isNextPageButtonDisable = computed(() =>
|
||||
pageControl.value?.currentPage === pageControl.value?.totalPages
|
||||
)
|
||||
|
||||
const isPreviousPageButtonDisable = computed(() =>
|
||||
pageControl.value?.currentPage === 1
|
||||
)
|
||||
|
||||
const prevPage = () => {
|
||||
const isFirstPage = pageControl.value?.currentPage === 1
|
||||
if (isFirstPage) return
|
||||
pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
|
||||
if (isLastPage) return
|
||||
pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
|
||||
}
|
||||
|
||||
const handleKeyPress = (event) => {
|
||||
if (event.key === "Enter") {
|
||||
handlePageInput(event)
|
||||
}
|
||||
}
|
||||
|
||||
//Handle Download
|
||||
const downloadControl = computed(() => vpvRef.value?.downloadControl)
|
||||
|
||||
const handleDownloadFile = () => {
|
||||
const downloadCtrl = unref(downloadControl)
|
||||
if (!downloadCtrl) return
|
||||
downloadCtrl.download()
|
||||
}
|
||||
|
||||
watch(downloadControl, (downloadCtrl) => {
|
||||
if (!downloadCtrl) return
|
||||
|
||||
downloadCtrl.onError = (error) => {
|
||||
console.log("Download error", error)
|
||||
}
|
||||
|
||||
downloadCtrl.onComplete = () => {
|
||||
console.log("Download completed")
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 justify-self-center">
|
||||
<div class="flex items-center gap-4 text-[#7862FF] bg-pale-blue border-[#D7D1FB] rounded-lg p-2 justify-center">
|
||||
|
||||
<!-- Zoom out button -->
|
||||
<UButton
|
||||
@click="() => handleZoomTool('pageWidth')"
|
||||
icon="i-heroicons-document-text"
|
||||
variant="outline"
|
||||
></UButton>
|
||||
|
||||
<UButton
|
||||
@click="() => handleZoomTool('out')"
|
||||
icon="i-heroicons-magnifying-glass-minus"
|
||||
variant="outline"
|
||||
></UButton>
|
||||
|
||||
<!-- Zoom in button -->
|
||||
<UButton
|
||||
@click="() => handleZoomTool('in')"
|
||||
icon="i-heroicons-magnifying-glass-plus"
|
||||
variant="outline"
|
||||
></UButton>
|
||||
<UButton
|
||||
@click="prevPage"
|
||||
:disabled="isPreviousPageButtonDisable"
|
||||
icon="i-heroicons-chevron-up"
|
||||
variant="outline"
|
||||
></UButton>
|
||||
|
||||
<UButton
|
||||
@click="handleDownloadFile"
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-down-on-square"
|
||||
/>
|
||||
|
||||
<!-- Page number input and total pages display -->
|
||||
<div class="flex items-center text-sm font-normal">
|
||||
<UInput
|
||||
v-model="currentPageInput"
|
||||
class="w-24 h-8 rounded-sm focus:outline-none"
|
||||
@change="handleKeyPress"
|
||||
>
|
||||
<template #trailing>
|
||||
/ {{ pageControl?.totalPages }}
|
||||
</template>
|
||||
</UInput>
|
||||
</div>
|
||||
|
||||
<!-- Next page button -->
|
||||
<UButton
|
||||
@click="nextPage"
|
||||
:disabled="isNextPageButtonDisable"
|
||||
icon="i-heroicons-chevron-down"
|
||||
variant="outline"
|
||||
></UButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pdf-container">
|
||||
<VPdfViewer
|
||||
v-if="pdfSrc"
|
||||
:src="pdfSrc"
|
||||
style="height: 78vh; width: 100%;"
|
||||
:toolbar-options="false"
|
||||
ref="vpvRef"
|
||||
/>
|
||||
<div v-else>Lade PDF…</div>
|
||||
</div></template>
|
||||
|
||||
<style scoped>
|
||||
.pdf-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,14 @@
|
||||
<script setup>
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const profileStore = useProfileStore()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const tenant = ref({})
|
||||
|
||||
const setupPage = async () => {
|
||||
tenant.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="font-bold text-xl">Willkommen zurück {{profileStore.activeProfile.fullName}}</h1>
|
||||
<span v-if="tenant.id">bei {{tenant.name}}</span>
|
||||
<h1 class="font-bold text-xl">Willkommen zurück {{auth.profile.full_name}}</h1>
|
||||
<span v-if="auth.activeTenant">bei {{auth.activeTenantData.name}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -17,10 +17,14 @@ export const useCapacitor = () => {
|
||||
return deviceInfo.model.toLowerCase().includes('iphone')
|
||||
}
|
||||
|
||||
const getIsNative = () => {
|
||||
return Capacitor.isNativePlatform()
|
||||
}
|
||||
|
||||
const getNetworkStatus = async () => {
|
||||
return await Network.getStatus()
|
||||
}
|
||||
|
||||
|
||||
return {getPlatform, getDeviceInfo, getNetworkStatus, getIsPhone}
|
||||
return {getPlatform, getDeviceInfo, getNetworkStatus, getIsPhone, getIsNative}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
export const useError = (resourceType) => {
|
||||
export const useErrorLogging = (resourceType) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
const profileStore = useProfileStore()
|
||||
@@ -6,7 +6,6 @@ export const useSum = () => {
|
||||
const getIncomingInvoiceSum = (invoice) => {
|
||||
let sum = 0
|
||||
invoice.accounts.forEach(account => {
|
||||
console.log(account)
|
||||
|
||||
|
||||
sum += account.amountTax
|
||||
@@ -23,21 +22,9 @@ export const useSum = () => {
|
||||
let total19 = 0
|
||||
let total7 = 0
|
||||
|
||||
/*let usedadvanceinvoices = []
|
||||
if(createddocument.usedAdvanceInvoices.length > 0) {
|
||||
console.log(createddocument)
|
||||
console.log(createddocument.usedAdvanceInvoices)
|
||||
|
||||
|
||||
|
||||
console.log((await supabase.from("createddocuments").select().in("id", createddocument.usedAdvanceInvoices)))
|
||||
|
||||
usedadvanceinvoices = (await supabase.from("createddocuments").select().in("id", createddocument.usedAdvanceInvoices)).data
|
||||
console.log(usedadvanceinvoices)
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
createddocument.rows.forEach(row => {
|
||||
if(!['pagebreak','title','text'].includes(row.mode)){
|
||||
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
|
||||
|
||||
@@ -485,7 +485,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GMCGQ8KK2P;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
@@ -493,7 +493,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.8;
|
||||
MARKETING_VERSION = 2.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = software.federspiel.fedeo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -510,7 +510,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GMCGQ8KK2P;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
@@ -518,7 +518,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.8;
|
||||
MARKETING_VERSION = 2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = software.federspiel.fedeo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
|
||||
@@ -13,6 +13,7 @@ def capacitor_pods
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
|
||||
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
||||
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||
pod 'CapacitorPluginSafeArea', :path => '../../node_modules/capacitor-plugin-safe-area'
|
||||
pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'
|
||||
end
|
||||
|
||||
@@ -8,6 +8,8 @@ PODS:
|
||||
- Capacitor
|
||||
- CapacitorPluginSafeArea (4.0.0):
|
||||
- Capacitor
|
||||
- CapacitorPreferences (6.0.3):
|
||||
- Capacitor
|
||||
- CordovaPluginsStatic (7.1.0):
|
||||
- CapacitorCordova
|
||||
- OneSignalXCFramework (= 5.2.10)
|
||||
@@ -64,6 +66,7 @@ DEPENDENCIES:
|
||||
- "CapacitorDevice (from `../../node_modules/@capacitor/device`)"
|
||||
- "CapacitorNetwork (from `../../node_modules/@capacitor/network`)"
|
||||
- CapacitorPluginSafeArea (from `../../node_modules/capacitor-plugin-safe-area`)
|
||||
- "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
|
||||
- CordovaPluginsStatic (from `../capacitor-cordova-ios-plugins`)
|
||||
- OneSignalXCFramework (< 6.0, >= 5.0)
|
||||
|
||||
@@ -82,6 +85,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/@capacitor/network"
|
||||
CapacitorPluginSafeArea:
|
||||
:path: "../../node_modules/capacitor-plugin-safe-area"
|
||||
CapacitorPreferences:
|
||||
:path: "../../node_modules/@capacitor/preferences"
|
||||
CordovaPluginsStatic:
|
||||
:path: "../capacitor-cordova-ios-plugins"
|
||||
|
||||
@@ -91,9 +96,10 @@ SPEC CHECKSUMS:
|
||||
CapacitorDevice: 069faf433b3a99c3d5f0e500fbe634f60a8c6a84
|
||||
CapacitorNetwork: 30c2e78a0ed32530656cb426c8ee6c2caec10dbf
|
||||
CapacitorPluginSafeArea: 22031c3436269ca80fac90ec2c94bc7c1e59a81d
|
||||
CapacitorPreferences: f3eadae2369ac3ab8e21743a2959145b0d1286a3
|
||||
CordovaPluginsStatic: f722d4ff434f50099581e690d579b7c108f490e6
|
||||
OneSignalXCFramework: 1a3b28dfbff23aabce585796d23c1bef37772774
|
||||
|
||||
PODFILE CHECKSUM: ccfbce7f13cfefd953204fe26b280d6431731aa5
|
||||
PODFILE CHECKSUM: d76fcd3d35c3f8c3708303de70ef45a76cc6e2b5
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -15,7 +15,6 @@ const route = useRoute()
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
//profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
|
||||
const month = dayjs().format("MM")
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ const { isHelpSlideoverOpen } = useDashboard()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const auth = useAuthStore()
|
||||
|
||||
profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
//profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
|
||||
const month = dayjs().format("MM")
|
||||
|
||||
@@ -64,40 +65,6 @@ const actions = [
|
||||
]
|
||||
|
||||
|
||||
const groups = computed(() =>
|
||||
[{
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
},{
|
||||
key: "customers",
|
||||
label: "Kunden",
|
||||
commands: dataStore.customers.map(item => { return {id: item.id, label: item.name, to: `/customers/show/${item.id}`}})
|
||||
},{
|
||||
key: "vendors",
|
||||
label: "Lieferanten",
|
||||
commands: dataStore.vendors.map(item => { return {id: item.id, label: item.name, to: `/vendors/show/${item.id}`}})
|
||||
},{
|
||||
key: "contacts",
|
||||
label: "Ansprechpartner",
|
||||
commands: dataStore.contacts.map(item => { return {id: item.id, label: item.fullName, to: `/contacts/show/${item.id}`}})
|
||||
},{
|
||||
key: "products",
|
||||
label: "Artikel",
|
||||
commands: dataStore.products.map(item => { return {id: item.id, label: item.name, to: `/products/show/${item.id}`}})
|
||||
},{
|
||||
key: "tasks",
|
||||
label: "Aufgaben",
|
||||
commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}})
|
||||
},{
|
||||
key: "plants",
|
||||
label: "Objekte",
|
||||
commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}})
|
||||
},{
|
||||
key: "projects",
|
||||
label: "Projekte",
|
||||
commands: dataStore.projects.map(item => { return {id: item.id, label: item.name, to: `/projects/show/${item.id}`}})
|
||||
}
|
||||
].filter(Boolean))
|
||||
const footerLinks = [/*{
|
||||
label: 'Invite people',
|
||||
icon: 'i-heroicons-plus',
|
||||
@@ -111,36 +78,112 @@ const footerLinks = [/*{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardLayout class="safearea" v-if="profileStore.loaded">
|
||||
|
||||
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
|
||||
<UDashboardNavbar style="margin-top: env(safe-area-inset-top, 10px) !important;" :class="['!border-transparent']" :ui="{ left: 'flex-1' }">
|
||||
<template #left>
|
||||
<ProfileDropdown class="w-full" />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardSidebar id="sidebar">
|
||||
<template #header>
|
||||
<UDashboardSearchButton v-if="!useCapacitor().getIsPhone()" label="Suche..."/>
|
||||
</template>
|
||||
|
||||
<MainNav/>
|
||||
|
||||
<div class="flex-1" />
|
||||
|
||||
|
||||
<template #footer>
|
||||
<div class="flex flex-col w-full">
|
||||
|
||||
<UDashboardSidebarLinks :links="footerLinks" />
|
||||
<UDivider class="sticky bottom-0" />
|
||||
<UserDropdown style="margin-bottom: env(safe-area-inset-bottom, 10px) !important;"/>
|
||||
<div v-if="!auth.loading">
|
||||
<div v-if="auth.activeTenantData?.locked === 'maintenance_tenant'">
|
||||
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||
<UCard class="max-w-lg text-center p-10">
|
||||
<UColorModeImage
|
||||
light="/Logo_Hell_Weihnachten.png"
|
||||
dark="/Logo_Dunkel_Weihnachten.png"
|
||||
class=" mx-auto my-10"
|
||||
v-if="month === '12'"
|
||||
/>
|
||||
<UColorModeImage
|
||||
light="/Logo.png"
|
||||
dark="/Logo_Dark.png"
|
||||
class="mx-auto my-10"
|
||||
v-else
|
||||
/>
|
||||
<div class="flex justify-center mb-6">
|
||||
<UIcon name="i-heroicons-exclamation-triangle-solid" class="w-16 h-16 text-yellow-500" />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</UDashboardSidebar>
|
||||
</UDashboardPanel>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Wartungsarbeiten
|
||||
</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||
Dieser FEDEO Mandant wird derzeit gewartet. Bitte versuche es in einigen Minuten erneut oder verwende einen anderen Mandanten.
|
||||
</p>
|
||||
<div class="mx-auto text-left flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||
{{tenant.name}}
|
||||
<UButton
|
||||
:disabled="tenant.locked"
|
||||
@click="auth.switchTenant(tenant.id)"
|
||||
>Wählen</UButton>
|
||||
</div>
|
||||
|
||||
|
||||
</UCard>
|
||||
</UContainer>
|
||||
</div>
|
||||
<div v-else-if="auth.activeTenantData?.locked === 'maintenance'">
|
||||
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||
<UCard class="max-w-lg text-center p-10">
|
||||
<UColorModeImage
|
||||
light="/Logo_Hell_Weihnachten.png"
|
||||
dark="/Logo_Dunkel_Weihnachten.png"
|
||||
class=" mx-auto my-10"
|
||||
v-if="month === '12'"
|
||||
/>
|
||||
<UColorModeImage
|
||||
light="/Logo.png"
|
||||
dark="/Logo_Dark.png"
|
||||
class="mx-auto my-10"
|
||||
v-else
|
||||
/>
|
||||
<div class="flex justify-center mb-6">
|
||||
<UIcon name="i-heroicons-exclamation-triangle-solid" class="w-16 h-16 text-yellow-500" />
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Wartungsarbeiten
|
||||
</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||
FEDEO wird derzeit gewartet. Bitte versuche es in einigen Minuten erneut.
|
||||
</p>
|
||||
|
||||
|
||||
</UCard>
|
||||
</UContainer>
|
||||
</div>
|
||||
<div v-else-if="auth.activeTenantData?.locked === 'no_subscription'">
|
||||
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||
<UCard class="max-w-lg text-center p-10">
|
||||
<UColorModeImage
|
||||
light="/Logo_Hell_Weihnachten.png"
|
||||
dark="/Logo_Dunkel_Weihnachten.png"
|
||||
class=" mx-auto my-10"
|
||||
v-if="month === '12'"
|
||||
/>
|
||||
<UColorModeImage
|
||||
light="/Logo.png"
|
||||
dark="/Logo_Dark.png"
|
||||
class="mx-auto my-10"
|
||||
v-else
|
||||
/>
|
||||
<div class="flex justify-center mb-6">
|
||||
<UIcon name="i-heroicons-credit-card" class="w-16 h-16 text-red-600" />
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Kein Aktives Abonnement für diesen Mandant.
|
||||
</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||
Bitte wenden Sie sich an den FEDEO Support um ein Abonnement zu erhalten oder verwenden Sie einen anderen Mandanten.
|
||||
</p>
|
||||
<div class="mx-auto text-left flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||
{{tenant.name}}
|
||||
<UButton
|
||||
:disabled="tenant.locked"
|
||||
@click="auth.switchTenant(tenant.id)"
|
||||
>Wählen</UButton>
|
||||
</div>
|
||||
|
||||
|
||||
</UCard>
|
||||
</UContainer>
|
||||
</div>
|
||||
<UDashboardLayout class="safearea" v-else>
|
||||
|
||||
<UDashboardPage style="height: 90vh">
|
||||
<UDashboardPanel grow>
|
||||
@@ -188,10 +231,10 @@ const footerLinks = [/*{
|
||||
<!-- ~/components/NotificationsSlideover.vue -->
|
||||
<NotificationsSlideover />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
|
||||
</ClientOnly>
|
||||
|
||||
</UDashboardLayout>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col"
|
||||
@@ -208,15 +251,26 @@ const footerLinks = [/*{
|
||||
class="w-1/3 mx-auto my-10"
|
||||
v-else
|
||||
/>
|
||||
<div v-if="dataStore.showProfileSelection">
|
||||
<ProfileSelection/>
|
||||
<div v-if="!auth.activeTenant" class="w-full mx-auto text-center">
|
||||
<!-- Tenant Selection -->
|
||||
<h3 class="text-center font-bold text-2xl mb-5">Kein Aktiver Mandant. Bitte wählen Sie ein Mandant.</h3>
|
||||
<div class="mx-auto w-5/6 flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||
<span class="text-left">{{tenant.name}}</span>
|
||||
<UButton
|
||||
@click="auth.switchTenant(tenant.id)"
|
||||
>Wählen</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
variant="outline"
|
||||
color="rose"
|
||||
@click="auth.logout()"
|
||||
>Abmelden</UButton>
|
||||
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
|
||||
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
9
middleware/redirectToMobileIndex.ts
Normal file
9
middleware/redirectToMobileIndex.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
const router = useRouter()
|
||||
|
||||
console.log(await useCapacitor().getIsPhone())
|
||||
|
||||
if(await useCapacitor().getIsPhone() && _from.path !== '/mobile') {
|
||||
return router.push('/mobile')
|
||||
}
|
||||
})
|
||||
@@ -25,7 +25,7 @@ export default defineNuxtConfig({
|
||||
transpile: ['@vuepic/vue-datepicker']
|
||||
},
|
||||
|
||||
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxt/content', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet'],
|
||||
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet'],
|
||||
|
||||
routeRules: {
|
||||
'/printing': {ssr: false}
|
||||
|
||||
0
package-lock.json
generated
0
package-lock.json
generated
12
package.json
12
package.json
@@ -10,21 +10,21 @@
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^7.0.0",
|
||||
"@nuxtjs/leaflet": "^1.2.3",
|
||||
"@nuxtjs/supabase": "^1.1.4",
|
||||
"nuxt": "^3.14.1592",
|
||||
"nuxt-tiptap-editor": "^1.2.0",
|
||||
"vite-plugin-pwa": "^0.17.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^7.1.0",
|
||||
"@capacitor/cli": "^7.1.0",
|
||||
"@capacitor/core": "^7.1.0",
|
||||
"@capacitor/android": "^7.0.0",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/device": "^7.0.0",
|
||||
"@capacitor/ios": "^7.1.0",
|
||||
"@capacitor/ios": "^7.0.0",
|
||||
"@capacitor/network": "^7.0.0",
|
||||
"@capacitor/preferences": "^7.0.0",
|
||||
"@fullcalendar/core": "^6.1.10",
|
||||
"@fullcalendar/daygrid": "^6.1.10",
|
||||
"@fullcalendar/interaction": "^6.1.10",
|
||||
@@ -34,7 +34,6 @@
|
||||
"@fullcalendar/timegrid": "^6.1.10",
|
||||
"@fullcalendar/vue3": "^6.1.10",
|
||||
"@iconify/json": "^2.2.171",
|
||||
"@nuxt/content": "^2.9.0",
|
||||
"@nuxt/ui-pro": "^1.6.0",
|
||||
"@nuxtjs/fontaine": "^0.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.1.0",
|
||||
@@ -49,6 +48,7 @@
|
||||
"@tiptap/vue-3": "^2.1.15",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vue-pdf-viewer/viewer": "^3.0.1",
|
||||
"@vuepic/vue-datepicker": "^7.4.0",
|
||||
"@zip.js/zip.js": "^2.7.32",
|
||||
"array-sort": "^1.0.0",
|
||||
|
||||
@@ -16,7 +16,6 @@ const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const itemInfo = ref({statementallocations:[]})
|
||||
const oldItemInfo = ref({})
|
||||
@@ -202,15 +201,7 @@ const archiveStatement = async () => {
|
||||
let temp = itemInfo.value
|
||||
delete temp.statementallocations
|
||||
|
||||
await dataStore.updateItem("bankstatements", {...temp,archived:true})
|
||||
|
||||
|
||||
const {data,error} = await supabase.from("historyitems").insert({
|
||||
createdBy: useProfileStore().activeProfile.id,
|
||||
tenant: useProfileStore().currentTenant,
|
||||
text: "Bankbuchung archiviert",
|
||||
bankStatement: itemInfo.value.id
|
||||
})
|
||||
await useEntities("bankstatements").archive(temp.id)
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -254,19 +245,12 @@ const archiveStatement = async () => {
|
||||
</UBadge>
|
||||
</template>
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
<ArchiveButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
type="bankstatements"
|
||||
@confirmed="archiveStatement"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie die Kontobewegung wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
/>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
const modal = useModal()
|
||||
const auth = useAuthStore()
|
||||
|
||||
@@ -134,7 +133,8 @@ const setupPage = async () => {
|
||||
console.log(route.query.loadMode)
|
||||
|
||||
if (route.query.loadMode === "deliveryNotes") {
|
||||
let linkedDocuments = (await supabase.from("createddocuments").select().in("id", JSON.parse(route.query.linkedDocuments))).data
|
||||
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||
//let linkedDocuments = (await supabase.from("createddocuments").select().in("id", JSON.parse(route.query.linkedDocuments))).data
|
||||
|
||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||
|
||||
@@ -1523,21 +1523,14 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
||||
</UButton>
|
||||
</template>
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
v-if="itemInfo.state === 'Entwurf' || itemInfo.type === 'serialInvoices'"
|
||||
<ArchiveButton
|
||||
color="rose"
|
||||
type="createddocuments"
|
||||
v-if="itemInfo.state === 'Entwurf' || itemInfo.type === 'serialInvoices'"
|
||||
variant="outline"
|
||||
@confirmed="useEntities('createddocuments').update(itemInfo.id,{archived: true}),
|
||||
router.push('/')"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md dark:text-whitetext-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie diesen Ausgangsbeleg wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
/>
|
||||
<UButton
|
||||
icon="i-mdi-content-save"
|
||||
@click="saveDocument('Entwurf',true)"
|
||||
|
||||
@@ -47,10 +47,11 @@
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu
|
||||
v-if="selectableFilters.length > 0"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
multiple
|
||||
v-model="selectedFilters"
|
||||
:options="['Nur offene anzeigen']"
|
||||
:options="selectableFilters"
|
||||
:color="selectedFilters.length > 0 ? 'primary' : 'white'"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
>
|
||||
@@ -61,6 +62,15 @@
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UTabs :items="selectedTypes" class="m-3">
|
||||
<template #default="{item}">
|
||||
{{item.label}}
|
||||
<UBadge
|
||||
variant="outline"
|
||||
class="ml-2"
|
||||
>
|
||||
{{filteredRows.filter(i => item.key === 'invoices' ? ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type) : item.key === i.type).length}}
|
||||
</UBadge>
|
||||
</template>
|
||||
<template #item="{item}">
|
||||
<div style="height: 80vh; overflow-y: scroll">
|
||||
<UTable
|
||||
@@ -182,12 +192,15 @@ const dataStore = useDataStore()
|
||||
const tempStore = useTempStore()
|
||||
const router = useRouter()
|
||||
|
||||
const type = "createddocuments"
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = (await useEntities("createddocuments").select("*, customer(id,name), statementallocations(id,amount),linkedDocument(*)","documentNumber",true)).filter(i => !i.archived)
|
||||
items.value = (await useEntities("createddocuments").select("*, customer(id,name), statementallocations(id,amount),linkedDocument(*)","documentNumber",true, true))
|
||||
}
|
||||
|
||||
setupPage()
|
||||
@@ -279,24 +292,34 @@ const clearSearchString = () => {
|
||||
tempStore.clearSearchString('createddocuments')
|
||||
searchString.value = ''
|
||||
}
|
||||
const selectedFilters = ref([])
|
||||
const selectableFilters = ref(dataType.filters.map(i => i.name))
|
||||
const selectedFilters = ref(dataType.filters.filter(i => i.default).map(i => i.name) || [])
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
|
||||
let temp = items.value.filter(i => types.value.find(x => x.key === 'invoices' ? ['invoices', 'advanceInvoices', 'cancellationInvoices'].includes(i.type) : x.key === i.type))
|
||||
temp = temp.filter(i => i.type !== "serialInvoices")
|
||||
let tempItems = items.value.filter(i => types.value.find(x => x.key === 'invoices' ? ['invoices', 'advanceInvoices', 'cancellationInvoices'].includes(i.type) : x.key === i.type))
|
||||
tempItems = tempItems.filter(i => i.type !== "serialInvoices")
|
||||
|
||||
/*if(showDrafts.value === true) {
|
||||
temp = temp.filter(i => i.state === "Entwurf")
|
||||
} else {
|
||||
temp = temp.filter(i => i.state !== "Entwurf")
|
||||
}*/
|
||||
tempItems = tempItems.map(i => {
|
||||
return {
|
||||
...i,
|
||||
class: i.archived ? 'bg-red-500/50 dark:bg-red-400/50' : null
|
||||
}
|
||||
})
|
||||
|
||||
if (selectedFilters.value.includes("Nur offene anzeigen")) {
|
||||
temp = temp.filter(i => !isPaid(i))
|
||||
if(selectedFilters.value.length > 0) {
|
||||
selectedFilters.value.forEach(filterName => {
|
||||
let filter = dataType.filters.find(i => i.name === filterName)
|
||||
tempItems = tempItems.filter(filter.filterFunction)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return useSearch(searchString.value, temp.slice().reverse())
|
||||
|
||||
tempItems = useSearch(searchString.value, tempItems)
|
||||
|
||||
|
||||
return useSearch(searchString.value, tempItems.slice().reverse())
|
||||
|
||||
})
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@ const modal = useModal()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const itemInfo = ref({})
|
||||
const linkedDocument =ref({})
|
||||
const setupPage = async () => {
|
||||
if(route.params) {
|
||||
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*, files(*)")
|
||||
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*, files(*), linkedDocument(*)")
|
||||
|
||||
console.log(itemInfo.value)
|
||||
|
||||
@@ -95,14 +96,27 @@ const openEmail = () => {
|
||||
>
|
||||
Kunde
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="itemInfo.linkedDocument"
|
||||
@click="router.push(`/standardEntity/createDocument/show/${itemInfo.linkedDocument}`)"
|
||||
icon="i-heroicons-link"
|
||||
variant="outline"
|
||||
>
|
||||
{{dataStore.documentTypesForCreation[itemInfo.linkedDocument.type].labelSingle}} - {{itemInfo.linkedDocument.documentNumber}}
|
||||
</UButton>
|
||||
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UDashboardPanelContent>
|
||||
<object
|
||||
<!-- <object
|
||||
:data="linkedDocument.url"
|
||||
class="w-full previewDocumentMobile"
|
||||
/>
|
||||
/>-->
|
||||
<PDFViewer v-if="linkedDocument.id" :file-id="linkedDocument.id" />
|
||||
|
||||
|
||||
|
||||
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -170,19 +170,12 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
<template>
|
||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
<ArchiveButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
type="incominginvoices"
|
||||
@confirmed="useEntities('incominginvoices').archive(route.params.id)"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie den Eingangsbeleg wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
/>
|
||||
<UButton
|
||||
@click="updateIncomingInvoice(false)"
|
||||
>
|
||||
|
||||
@@ -47,69 +47,49 @@ const sort = ref({
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
const type = "incominginvoices"
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
|
||||
const setupPage = async () => {
|
||||
//items.value = await useSupabaseSelect("incominginvoices","*, vendor(id,name), statementallocations(id,amount)","created_at",false)
|
||||
items.value = await useEntities("incominginvoices").select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
||||
items.value = await useEntities(type).select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: 'reference',
|
||||
label: "Referenz:",
|
||||
sortable: true,
|
||||
}, {
|
||||
key: 'state',
|
||||
label: "Status:"
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
label: "Datum",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "vendor",
|
||||
label: "Lieferant",
|
||||
},
|
||||
{
|
||||
key: "amount",
|
||||
label: "Betrag",
|
||||
},
|
||||
{
|
||||
key: "dueDate",
|
||||
label: "Fälligkeitsdatum",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "paid",
|
||||
label: "Bezahlt"
|
||||
},
|
||||
{
|
||||
key: "paymentType",
|
||||
label: "Zahlart",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "description",
|
||||
label: "Beschreibung"
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
const selectedColumns = ref(tempStore.columns[type] ? tempStore.columns[type] : dataType.templateColumns.filter(i => !i.disabledInTable))
|
||||
const columns = computed(() => dataType.templateColumns.filter((column) => !column.disabledInTable && selectedColumns.value.find(i => i.key === column.key)))
|
||||
|
||||
const selectableFilters = ref(dataType.filters.map(i => i.name))
|
||||
const selectedFilters = ref(dataType.filters.filter(i => i.default).map(i => i.name) || [])
|
||||
|
||||
|
||||
const searchString = ref(tempStore.searchStrings['incominginvoices'] ||'')
|
||||
const searchString = ref(tempStore.searchStrings[type] ||'')
|
||||
|
||||
const clearSearchString = () => {
|
||||
tempStore.clearSearchString('incominginvoices')
|
||||
tempStore.clearSearchString(type)
|
||||
searchString.value = ''
|
||||
}
|
||||
const filteredRows = computed(() => {
|
||||
let filteredItems = useSearch(searchString.value, items.value)
|
||||
let tempItems = items.value.map(i => {
|
||||
return {
|
||||
...i,
|
||||
class: i.archived ? 'bg-red-500/50 dark:bg-red-400/50' : null
|
||||
}
|
||||
})
|
||||
|
||||
return [...filteredItems.filter(i => i.state === "Vorbereitet"), ...filteredItems.filter(i => i.state !== "Vorbereitet")]
|
||||
if(selectedFilters.value.length > 0) {
|
||||
selectedFilters.value.forEach(filterName => {
|
||||
let filter = dataType.filters.find(i => i.name === filterName)
|
||||
tempItems = tempItems.filter(filter.filterFunction)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
tempItems = useSearch(searchString.value, tempItems)
|
||||
|
||||
|
||||
return [...tempItems.filter(i => i.state === "Vorbereitet"), ...tempItems.filter(i => i.state !== "Vorbereitet")]
|
||||
|
||||
|
||||
})
|
||||
@@ -157,7 +137,7 @@ const selectIncomingInvoice = (invoice) => {
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
@change="tempStore.modifySearchString('incominginvoices',searchString)"
|
||||
@change="tempStore.modifySearchString(type,searchString)"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
@@ -179,24 +159,54 @@ const selectIncomingInvoice = (invoice) => {
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
:options="dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
:color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
@change="tempStore.modifyColumns(type,selectedColumns)"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu
|
||||
v-if="selectableFilters.length > 0"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
multiple
|
||||
v-model="selectedFilters"
|
||||
:options="selectableFilters"
|
||||
:color="selectedFilters.length > 0 ? 'primary' : 'white'"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
>
|
||||
<template #label>
|
||||
Filter
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<UTabs
|
||||
class="m-3"
|
||||
:items="[{label: 'In Bearbeitung'},{label: 'Gebucht'}]"
|
||||
>
|
||||
<template #default="{item}">
|
||||
{{item.label}}
|
||||
<UBadge
|
||||
variant="outline"
|
||||
class="ml-2"
|
||||
>
|
||||
{{filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' ).length}}
|
||||
</UBadge>
|
||||
</template>
|
||||
<template #item="{item}">
|
||||
<div style="height: 80dvh; overflow-y: scroll">
|
||||
<UTable
|
||||
v-model:sort="sort"
|
||||
sort-mode="manual"
|
||||
@update:sort="setupPage"
|
||||
:rows="filteredRows"
|
||||
:rows="filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' )"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@@ -229,7 +239,10 @@ const selectIncomingInvoice = (invoice) => {
|
||||
<span v-else class="text-rose-600">Offen</span>
|
||||
</template>
|
||||
</UTable>
|
||||
</UDashboardPanelContent>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</UTabs>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'redirect-to-mobile-index'
|
||||
})
|
||||
|
||||
|
||||
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,11 @@ const doLogin = async (data:any) => {
|
||||
await auth.login(data.email, data.password)
|
||||
// Weiterleiten nach erfolgreichem Login
|
||||
toast.add({title:"Einloggen erfolgreich"})
|
||||
if(useCapacitor().getIsNative()) {
|
||||
return navigateTo("/mobile")
|
||||
} else {
|
||||
return navigateTo("/")
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.add({title:"Zugangsdaten falsch. Bitte überprüfen Sie Ihre Eingaben",color:"rose"})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ definePageMeta({
|
||||
layout: 'mobile'
|
||||
})
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
//const profileStore = useProfileStore()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -21,7 +21,7 @@ const profileStore = useProfileStore()
|
||||
>
|
||||
<display-open-tasks/>
|
||||
</UDashboardCard>
|
||||
<UDashboardCard
|
||||
<!--<UDashboardCard
|
||||
title="Anwesenheit"
|
||||
>
|
||||
<display-running-working-time/>
|
||||
@@ -36,7 +36,7 @@ const profileStore = useProfileStore()
|
||||
v-if="profileStore.ownTenant.features.accounting"
|
||||
>
|
||||
<display-open-balances/>
|
||||
</UDashboardCard>
|
||||
</UDashboardCard>-->
|
||||
<UDashboardCard
|
||||
title="Projekte"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ definePageMeta({
|
||||
layout: 'mobile',
|
||||
})
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const auth = useAuthStore()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -65,17 +65,23 @@ const profileStore = useProfileStore()
|
||||
>
|
||||
Objekte
|
||||
</UButton>
|
||||
<UButton
|
||||
class="w-full my-1"
|
||||
@click="auth.logout()"
|
||||
color="rose"
|
||||
variant="outline"
|
||||
>
|
||||
Abmelden
|
||||
</UButton>
|
||||
|
||||
<UDivider class="my-5">Unternehmen wechseln</UDivider>
|
||||
|
||||
<div class="w-full flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||
<span class="text-left">{{tenant.name}}</span>
|
||||
<UButton
|
||||
v-for="option in profileStore.ownProfiles"
|
||||
class="my-1"
|
||||
variant="outline"
|
||||
@click="profileStore.changeProfile(option.id)"
|
||||
>
|
||||
{{profileStore.tenants.find(i => i.id === option.tenant).name}}
|
||||
</UButton>
|
||||
@click="auth.switchTenant(tenant.id)"
|
||||
>Wechseln</UButton>
|
||||
</div>
|
||||
|
||||
|
||||
</UDashboardPanelContent>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import {setPageLayout} from "#app";
|
||||
import {useCapacitor} from "~/composables/useCapacitor.js";
|
||||
|
||||
definePageMeta({
|
||||
layout: "default",
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const dataStore = useDataStore()
|
||||
const api = useNuxtApp().$api
|
||||
@@ -12,7 +10,6 @@ const api = useNuxtApp().$api
|
||||
|
||||
const type = route.params.type
|
||||
const platform = await useCapacitor().getIsPhone() ? "mobile" : "default"
|
||||
console.log(platform)
|
||||
|
||||
|
||||
const dataType = dataStore.dataTypes[route.params.type]
|
||||
@@ -25,10 +22,9 @@ const item = ref({})
|
||||
|
||||
const setupPage = async (sort_column = null, sort_direction = null) => {
|
||||
loaded.value = false
|
||||
setPageLayout(platform)
|
||||
|
||||
|
||||
if (await useCapacitor().getIsPhone()) {
|
||||
setPageLayout("mobile")
|
||||
}
|
||||
|
||||
if (route.params.mode) mode.value = route.params.mode
|
||||
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import {Preferences} from "@capacitor/preferences";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const api = $fetch.create({
|
||||
baseURL: /*"http://localhost:3100"*/ "https://backend.fedeo.io",
|
||||
baseURL: /*"http://192.168.1.227:3100"*/ "https://backend.fedeo.io",
|
||||
credentials: "include",
|
||||
onRequest({ options }) {
|
||||
async onRequest({options}) {
|
||||
// Token aus Cookie holen
|
||||
let token = useCookie("token").value
|
||||
let token: string | null | undefined = ""
|
||||
if (await useCapacitor().getIsNative()) {
|
||||
const {value} = await Preferences.get({key: 'token'});
|
||||
token = value
|
||||
} else {
|
||||
token = useCookie("token").value
|
||||
}
|
||||
|
||||
|
||||
// Falls im Request explizit ein anderer JWT übergeben wird → diesen verwenden
|
||||
if (options.context && (options.context as any).jwt) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from "pinia"
|
||||
import router from "#app/plugins/router";
|
||||
import {Preferences} from "@capacitor/preferences";
|
||||
|
||||
export const useAuthStore = defineStore("auth", {
|
||||
state: () => ({
|
||||
@@ -15,7 +16,12 @@ export const useAuthStore = defineStore("auth", {
|
||||
actions: {
|
||||
async init(token) {
|
||||
await this.fetchMe(token)
|
||||
|
||||
if(useCapacitor().getIsNative()) {
|
||||
navigateTo("/mobile")
|
||||
} else {
|
||||
navigateTo("/")
|
||||
}
|
||||
},
|
||||
|
||||
async login(email: string, password: string) {
|
||||
@@ -23,7 +29,15 @@ export const useAuthStore = defineStore("auth", {
|
||||
method: "POST",
|
||||
body: { email, password }
|
||||
})
|
||||
if(useCapacitor().getIsNative()) {
|
||||
await Preferences.set({
|
||||
key:"token",
|
||||
value: token,
|
||||
})
|
||||
} else {
|
||||
useCookie("token").value = token // persistieren
|
||||
|
||||
}
|
||||
await this.fetchMe(token)
|
||||
},
|
||||
|
||||
@@ -38,7 +52,13 @@ export const useAuthStore = defineStore("auth", {
|
||||
this.profile = null
|
||||
this.activeTenant = null
|
||||
this.tenants = []
|
||||
if(useCapacitor().getIsNative()) {
|
||||
await Preferences.remove({ key: 'token' });
|
||||
useCookie("token").value = null
|
||||
} else {
|
||||
useCookie("token").value = null
|
||||
}
|
||||
|
||||
navigateTo("/login")
|
||||
},
|
||||
|
||||
@@ -62,7 +82,7 @@ export const useAuthStore = defineStore("auth", {
|
||||
|
||||
this.profile = me.profile
|
||||
|
||||
if(me.activeTenant) {
|
||||
if(me.activeTenant > 0) {
|
||||
this.activeTenant = me.activeTenant
|
||||
this.activeTenantData = me.tenants.find(i => i.id === me.activeTenant)
|
||||
this.loading = false
|
||||
@@ -79,13 +99,29 @@ export const useAuthStore = defineStore("auth", {
|
||||
|
||||
async switchTenant(tenant_id: string) {
|
||||
this.loading = true
|
||||
const { token } = await useNuxtApp().$api("/api/tenant/switch", {
|
||||
const res = await useNuxtApp().$api("/api/tenant/switch", {
|
||||
method: "POST",
|
||||
body: { tenant_id }
|
||||
})
|
||||
useCookie("token").value = token
|
||||
console.log(res)
|
||||
|
||||
const {token} = res
|
||||
|
||||
if(await useCapacitor().getIsNative()) {
|
||||
await Preferences.set({
|
||||
key:"token",
|
||||
value: token,
|
||||
})
|
||||
} else {
|
||||
useCookie("token").value = token // persistieren
|
||||
|
||||
}
|
||||
await this.init(token)
|
||||
if(useCapacitor().getIsNative()) {
|
||||
navigateTo("/mobile")
|
||||
} else {
|
||||
navigateTo("/")
|
||||
}
|
||||
},
|
||||
|
||||
hasPermission(key: string) {
|
||||
|
||||
@@ -1552,6 +1552,19 @@ export const useDataStore = defineStore('data', () => {
|
||||
label: "Dokumente",
|
||||
labelSingle: "Dokument",
|
||||
supabaseSelectWithInformation: "*, files(*), statementallocations(*)",
|
||||
filters: [
|
||||
{
|
||||
name: "Archivierte ausblenden",
|
||||
default: true,
|
||||
"filterFunction": function (row) {
|
||||
if(!row.archived) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
files: {
|
||||
isArchivable: true,
|
||||
@@ -1568,7 +1581,61 @@ export const useDataStore = defineStore('data', () => {
|
||||
incominginvoices: {
|
||||
label: "Eingangsrechnungen",
|
||||
labelSingle: "Eingangsrechnung",
|
||||
redirect:true
|
||||
redirect:true,
|
||||
filters: [
|
||||
{
|
||||
name: "Archivierte ausblenden",
|
||||
default: true,
|
||||
"filterFunction": function (row) {
|
||||
if(!row.archived) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
templateColumns: [
|
||||
{
|
||||
key: 'reference',
|
||||
label: "Referenz:",
|
||||
sortable: true,
|
||||
}, {
|
||||
key: 'state',
|
||||
label: "Status:"
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
label: "Datum",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "vendor",
|
||||
label: "Lieferant",
|
||||
},
|
||||
{
|
||||
key: "amount",
|
||||
label: "Betrag",
|
||||
},
|
||||
{
|
||||
key: "dueDate",
|
||||
label: "Fälligkeitsdatum",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "paid",
|
||||
label: "Bezahlt"
|
||||
},
|
||||
{
|
||||
key: "paymentType",
|
||||
label: "Zahlart",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "description",
|
||||
label: "Beschreibung"
|
||||
}
|
||||
]
|
||||
},
|
||||
inventoryitems: {
|
||||
isArchivable: true,
|
||||
|
||||
Reference in New Issue
Block a user