Introduced New Projecttypes and Phase Display

This commit is contained in:
2024-11-20 13:48:13 +01:00
parent 988c33d07d
commit 869f381a1b
5 changed files with 586 additions and 131 deletions

View File

@@ -1,8 +1,6 @@
<script setup>
import dayjs from "dayjs";
import HistoryDisplay from "~/components/HistoryDisplay.vue";
import DocumentUpload from "~/components/DocumentUpload.vue";
import DocumentList from "~/components/DocumentList.vue";
definePageMeta({
middleware: "auth"
@@ -97,17 +95,18 @@ const itemInfo = ref({
const oldItemInfo = ref({})
const tags = dataStore.getDocumentTags
const phasesTemplates = ref([])
const phasesTemplateSelected = ref(null)
const plants = ref([])
const contracts = ref([])
const projecttypes = ref([])
const disableCustomerSelection =ref(false)
//Functions
const setupPage = async() => {
plants.value = await useSupabaseSelect("plants")
contracts.value = await useSupabaseSelect("contracts")
projecttypes.value = await useSupabaseSelect("projecttypes", "*", "id")
if(mode.value === "show" ){
itemInfo.value = await useSupabaseSelectSingle("projects",route.params.id,"*, customer(*), plant(*)")
@@ -127,12 +126,10 @@ const setupPage = async() => {
}
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
phasesTemplates.value = await useSupabaseSelect("phasesTemplates","*","created_at")
if(phasesTemplates.value.length > 0) {
phasesTemplateSelected.value = phasesTemplates.value[0].id
if(!oldItemInfo.value.projecttype) {
itemInfo.value.projecttype = projecttypes.value[0].id
}
}
setupPage()
@@ -155,53 +152,65 @@ const projectHours = () => {
}
/*const phasesTemplate = ref([{
label: 'Erstkontakt',
icon: 'i-heroicons-clipboard-document',
active: true
}, {
label: 'Überprüfung Vor Ort',
icon: 'i-heroicons-magnifying-glass'
}, {
label: 'Angebotserstellung',
icon: 'i-heroicons-document-text'
}, {
label: 'Auftragsvergabe',
icon: 'i-heroicons-document-check'
}, {
label: 'Umsetzung',
icon: 'i-heroicons-wrench-screwdriver'
},{
label: 'Rechnungsstellung',
icon: 'i-heroicons-document-text'
}, {
label: 'Abgeschlossen',
icon: 'i-heroicons-check'
}])*/
const changeActivePhase = async (phase) => {
itemInfo.value = await useSupabaseSelectSingle("projects",route.params.id,'*')
itemInfo.value.phases = itemInfo.value.phases.map(p => {
if(p.active) delete p.active
const createProject = async () => {
let initialPhases = projecttypes.value.find(i => i.id === itemInfo.value.projecttype).initialPhases
await dataStore.createNewItem('projects',{...itemInfo.value, phases: initialPhases })
}
const changeActivePhase = async (key) => {
console.log(key)
let item = await useSupabaseSelectSingle("projects",itemInfo.value.id,'*')
item.phases = item.phases.map(p => {
if(p.active) p.active = false
if(p.key === key) {
p.active = true
p.activated_at = dayjs().format()
p.activated_by = dataStore.activeProfile.id
}
if(p.label === phase.label) p.active = true
return p
})
await savePhases()
console.log(item.phases)
await dataStore.updateItem("projects", item)
setupPage()
}
const savePhases = () => {
dataStore.updateItem("projects", itemInfo.value)
}
const loadPhases = async () => {
itemInfo.value.phases = dataStore.phasesTemplates.find(i => i.id === phasesTemplateSelected.value).initialPhases
await dataStore.updateItem("projects", {...itemInfo.value, customer: itemInfo.value.customer ? itemInfo.value.customer.id : null, plant: itemInfo.value.plant ? itemInfo.value.plant.id : null})
}
const renderedPhases = computed(() => {
if(itemInfo.value.phases) {
return itemInfo.value.phases.map((phase,index,array) => {
let isAvailable = false
if(phase.active) {
isAvailable = true
} else if(index > 0 && array[index-1].active ){
isAvailable = true
} else if(index > 1 && array[index-1].optional && array[index-2].active){
isAvailable = true
} else if(array.findIndex(i => i.active) > index) {
isAvailable = true
}
return {
...phase,
label: phase.optional ? `${phase.label}(optional)`: phase.label,
disabled: !isAvailable,
defaultOpen: phase.active ? true : false
}
})
} else {
return []
}
})
</script>
@@ -231,7 +240,7 @@ const loadPhases = async () => {
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('projects',itemInfo)"
@click="createProject"
>
Erstellen
</UButton>
@@ -294,37 +303,15 @@ const loadPhases = async () => {
</div>
<div v-if="item.key === 'phases'" class="space-y-3">
<UCard class="mt-5">
<UFormGroup
label="Vorlage laden"
v-if="itemInfo.phases.length === 0"
>
<InputGroup>
<USelectMenu
:options="phasesTemplates"
option-attribute="name"
value-attribute="id"
v-model="phasesTemplateSelected"
class="flex-auto"
>
</USelectMenu>
<UButton
@click="loadPhases"
>
Vorlage laden
</UButton>
</InputGroup>
</UFormGroup>
<UAccordion
:items="itemInfo.phases"
:items="renderedPhases"
>
<template #default="{item,index,open}">
<UButton
variant="ghost"
:color="item.active ? 'primary' : 'white'"
class="mb-1"
:disabled="true"
>
<template #leading>
<div class="w-6 h-6 flex items-center justify-center -my-1">
@@ -344,15 +331,35 @@ const loadPhases = async () => {
</UButton>
</template>
<template #item="{item}">
<template #item="{item, index}">
<UCard class="mx-5">
<template #header>
<span class="text-black">{{item.label}}</span>
</template>
<InputGroup>
<UButton
v-if="!item.active"
@click="changeActivePhase(item)"
v-if="!item.activated_at && index !== 0 "
@click="changeActivePhase(item.key)"
>
Phase aktivieren
</UButton>
<UButton
v-if="item.active"
v-for="button in item.quickactions"
@click="router.push(`${button.link}&customer=${itemInfo.customer.id}&project=${itemInfo.id}`)"
>
{{button.label}}
</UButton>
</InputGroup>
<div>
<p v-if="item.activated_at" class="text-black">Aktiviert am: {{dayjs(item.activated_at).format("DD.MM.YY HH:mm")}} Uhr</p>
<p v-if="item.activated_by" class="text-black">Aktiviert durch: {{dataStore.getProfileById(item.activated_by).fullName}}</p>
<p v-if="item.description" class="text-black">Beschreibung: {{item.description}}</p>
</div>
</UCard>
</template>
</UAccordion>
</UCard>
@@ -490,23 +497,7 @@ const loadPhases = async () => {
</UCard>
</div>
<div v-else-if="item.key === 'material'" class="space-y-3">
<UCard class="mt-5">
Auf das Projekt gebuchte Artikel:
<!-- <UTable
:rows="dataStore.getStocksByProjectId(itemInfo.id)"
:columns="[{key:'productId',label:'Artikel'},{key:'stock',label:'Anzahl'}]"
@select="(i) => router.push(`/products/show/${i.productId}`)"
>
<template #productId-data="{row}">
{{dataStore.getProductById(row.productId).name}}
</template>
<template #stock-data="{row}">
{{dataStore.getProductById(row.productId)}}
{{row.stock}} {{dataStore.units.find(i => i.id === dataStore.getProductById(row.productId).unit).short}}
</template>
</UTable>-->
</UCard>
</div>
</template>
@@ -532,6 +523,43 @@ const loadPhases = async () => {
/>
</UFormGroup>
<UFormGroup
label="Objekt:"
>
<USelectMenu
v-model="itemInfo.plant"
:options="itemInfo.customer ? plants.filter(i => i.customer === itemInfo.customer) : plants"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
@change="itemInfo.customer = plants.find(i => i.id === itemInfo.plant).customer,
disableCustomerSelection = true"
>
<template #label>
{{plants.find(i => i.id === itemInfo.plant) ? plants.find(i => i.id === itemInfo.plant).name : "Objekt auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Vertrag:"
>
<USelectMenu
v-model="itemInfo.contract"
:options="itemInfo.customer ? contracts.filter(i => i.customer === itemInfo.customer) : contracts"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
@change="itemInfo.customer = contracts.find(i => i.id === itemInfo.contract).customer,
disableCustomerSelection = true"
>
<template #label>
{{contracts.find(i => i.id === itemInfo.contract) ? contracts.find(i => i.id === itemInfo.contract).name : "Vertrag auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kundennummer:"
>
@@ -542,6 +570,7 @@ const loadPhases = async () => {
value-attribute="id"
searchable
:search-attributes="['name']"
:disabled="disableCustomerSelection"
>
<template #label>
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kunde auswählen"}}
@@ -549,6 +578,21 @@ const loadPhases = async () => {
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<USelectMenu
v-model="itemInfo.projecttype"
:options="projecttypes"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['label']"
:disabled="oldItemInfo.projecttype"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Gewerk:"
>
@@ -564,38 +608,7 @@ const loadPhases = async () => {
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Objekt:"
>
<USelectMenu
v-model="itemInfo.plant"
:options="plants"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
>
<template #label>
{{plants.find(i => i.id === itemInfo.plant) ? plants.find(i => i.id === itemInfo.plant).name : "Objekt auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Vertrag:"
>
<USelectMenu
v-model="itemInfo.contract"
:options="contracts"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
>
<template #label>
{{contracts.find(i => i.id === itemInfo.contract) ? contracts.find(i => i.id === itemInfo.contract).name : "Vertrag auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Beteiligte Benutzer:"
>
@@ -622,6 +635,7 @@ const loadPhases = async () => {
/>
</UFormGroup>
</UForm>
</template>
<style scoped>

View File

@@ -26,7 +26,20 @@
<UCheckbox
label="Abgeschlossene anzeigen"
v-model="showFinished"
class="mt-1"
/>
<USelectMenu
class="ml-3 w-36"
v-model="selectedTypes"
:options="projecttypes"
value-attribute="id"
option-attribute="name"
multiple
>
<template #label>
Typen
</template>
</USelectMenu>
</template>
<template #right>
@@ -60,6 +73,9 @@
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{row.name}}</span>
<span v-else>{{row.name}}</span>
</template>
<template #projecttype-data="{row}">
{{row.projecttype.name}}
</template>
<template #phase-data="{row}">
{{getActivePhaseLabel(row)}}
</template>
@@ -117,9 +133,13 @@ const router = useRouter()
const items = ref([])
const selectedItem = ref(0)
const projecttypes = ref([])
const selectedTypes = ref([])
const setupPage = async () => {
items.value = await useSupabaseSelect("projects","*, customer (name), plant(name)","projectNumber")
items.value = await useSupabaseSelect("projects","*, customer (name), plant(name), projecttype(name, id)","projectNumber")
projecttypes.value = await useSupabaseSelect("projecttypes","*")
selectedTypes.value = projecttypes.value.map(i => i.id)
}
setupPage()
@@ -129,6 +149,10 @@ const templateColumns = [
key: "projectNumber",
label: "Projektnummer"
},
{
key: "projecttype",
label: "Typ"
},
{
key: "phase",
label: "Phase"
@@ -198,10 +222,11 @@ const filteredRows = computed(() => {
temp = temp.filter(i => i.phaseLabel !== "Abgeschlossen")
}
temp = temp.filter(i => selectedTypes.value.includes(i.projecttype.id))
if(!searchString.value) {
return temp
}
return useSearch(searchString.value, temp)
})
</script>

View File

@@ -0,0 +1,292 @@
<script setup>
import { v4 as uuidv4 } from 'uuid';
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/projecttypes")
},
'arrowleft': () => {
if(openTab.value > 0){
openTab.value -= 1
}
},
'arrowright': () => {
if(openTab.value < 3) {
openTab.value += 1
}
},
})
const openTab = ref(0)
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
name: "",
initialPhases: []
})
const oldItemInfo = ref({})
const openQuickActionModal = ref(false)
const selectedKeyForQuickAction = ref("")
const setupPage = async() => {
if(mode.value === "show" ){
itemInfo.value = await useSupabaseSelectSingle("projecttypes",route.params.id,"*")
} else if (mode.value === "edit") {
itemInfo.value = await useSupabaseSelectSingle("projecttypes",route.params.id,"*")
}
if(mode.value === "create") {
let query = route.query
}
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
setKeys()
}
setupPage()
const setKeys = () => {
itemInfo.value.initialPhases = itemInfo.value.initialPhases.map(i => {
return {
...i,
key: uuidv4(),
quickactions: i.quickactions || []
}
})
}
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Projekt erstellen' : 'Projekt bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/projecttypes`)"
>
Projekttypen
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Projekttyp: ${itemInfo.name}` : (mode === 'create' ? 'Projekttyp erstellen' : 'Projekttyp bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('projecttypes',itemInfo,oldItemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('projecttyes', itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="router.push(itemInfo.id ? `/projecttypes/show/${itemInfo.id}` : `/projecttypes`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/projecttypes/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs
:items="[{label: 'Informationen'}]"
v-if="itemInfo.id && mode == 'show'"
v-model="openTab"
>
<template #item="{ item }">
<div v-if="item.label === 'Informationen'" class="flex flex-row">
<div class="w-1/2 mr-3">
<UCard class="mt-5">
{{itemInfo}}
</UCard>
</div>
<div class="w-1/2">
<UCard class="mt-5">
<HistoryDisplay
type="project"
v-if="itemInfo"
:element-id="itemInfo.id"
render-headline
/>
</UCard>
</div>
</div>
</template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create'">
<UAlert
color="rose"
variant="outline"
class="mb-5"
description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert."
/>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UDivider class="mt-5">
Initiale Phasen
</UDivider>
<UButton
class="mt-3"
@click="itemInfo.initialPhases.push({label: '', icon: ''}),
setKeys"
>
+ Phase
</UButton>
<table class="mt-3">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Icon</th>
<th>Optional</th>
<th>Beschreibung</th>
<th>Schnellaktionen</th>
<th></th>
</tr>
</thead>
<draggable
v-model="itemInfo.initialPhases"
handle=".handle"
tag="tbody"
itemKey="pos"
@end="setKeys"
>
<template #item="{element: phase}">
<tr>
<td>
<UIcon
class="handle"
name="i-mdi-menu"
/>
</td>
<td>
<UInput
class="my-2 ml-2"
v-model="phase.label"
/>
</td>
<td>
<UInput
class="my-2 ml-2"
v-model="phase.icon"
/>
</td>
<td>
<UCheckbox
class="my-2 ml-2"
v-model="phase.optional"
/>
</td>
<td>
<UInput
class="my-2 ml-2"
v-model="phase.description"
/>
</td>
<td>
<UButton
class="my-2 ml-2"
variant="outline"
@click="openQuickActionModal = true,
selectedKeyForQuickAction = phase.key"
>+ Schnellaktion</UButton>
<UButton
@click="phase.quickactions = phase.quickactions.filter(i => i.label !== button.label)"
v-for="button in phase.quickactions"
class="ml-1"
>
{{ button.label }}
</UButton>
<UModal v-model="openQuickActionModal">
<UCard>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Schnellaktion hinzufügen
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="openQuickActionModal = false" />
</div>
<div class="flex flex-col">
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Angebot',link:'/createDocument/edit/?type=quotes'})">Angebot Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Lieferschein',link:'/createDocument/edit/?type=deliveryNotes'})">Lieferschein Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Rechnung',link:'/createDocument/edit/?type=invoices'})">Rechnung Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Aufgabe',link:'/tasks/create'})">Aufgabe Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Termin',link:'/events/edit'})">Termin Erstellen</UButton>
</div>
</UCard>
</UModal>
</td>
<td>
<UButton
class="my-2 ml-2"
variant="outline"
color="rose"
@click="itemInfo.initialPhases = itemInfo.initialPhases.filter(i => i !== phase)"
>X</UButton>
</td>
</tr>
</template>
</draggable>
</table>
</UForm>
</UDashboardPanelContent>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,108 @@
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/projects/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/projecttypes/show/${filteredRows.value[selectedItem.value].id}`)
}
},
'arrowdown': () => {
if(selectedItem.value < filteredRows.value.length - 1) {
selectedItem.value += 1
} else {
selectedItem.value = 0
}
},
'arrowup': () => {
if(selectedItem.value === 0) {
selectedItem.value = filteredRows.value.length - 1
} else {
selectedItem.value -= 1
}
}
})
const router = useRouter()
const items = ref([])
const selectedItem = ref(0)
const setup = async () => {
items.value = await useSupabaseSelect("projecttypes","*")
}
setup()
const templateColumns = [
{
key: "name",
label: "Name"
},
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const searchString = ref("")
const filteredRows = computed(() => {
return useListFilter(searchString.value, items.value)
})
</script>
<template>
<UDashboardNavbar title="Projekttypen" :badge="filteredRows.length">
<template #right>
<UInput
id="searchinput"
v-model="searchString"
icon="i-heroicons-funnel"
autocomplete="off"
placeholder="Suche..."
class="hidden lg:block"
@keydown.esc="$event.target.blur()"
>
<template #trailing>
<UKbd value="/" />
</template>
</UInput>
<UButton @click="router.push(`/projecttypes/create`)">+ Projekttyp</UButton>
</template>
</UDashboardNavbar>
<UTable
:rows="filteredRows"
:columns="columns"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/projecttypes/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Projekttypen anzuzeigen' }"
>
<template #name-data="{row}">
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{row.name}}</span>
<span v-else>{{row.name}}</span>
</template>
</UTable>
</template>
<style scoped>
</style>

View File

@@ -155,6 +155,12 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Fahrt",
redirect: true,
historyItemHolder: "trackingtrip",
},
projecttypes: {
label: "Projekttypen",
labelSingle: "Projekttyp",
redirect: true,
historyItemHolder: "projecttype"
}
}
@@ -687,6 +693,12 @@ export const useDataStore = defineStore('data', () => {
name = "Wöchentliche Arbeitszeit"
} else if(key === "licensePlate") {
name = "Kennzeichen"
}else if(key === "towingCapacity") {
name = "Anhängelast"
}else if(key === "color") {
name = "Farbe"
}else if(key === "powerInKW") {
name = "Leistung"
} else if(key === "driver") {
name = "Fahrer"
if(prop.data.o) oldVal = profiles.value.find(i => i.id === prop.data.o).fullName
@@ -1193,6 +1205,10 @@ export const useDataStore = defineStore('data', () => {
return documents.value.filter(item => item.project === projectId && !item.tags.includes("Archiviert"))
})
const getDocumentsByProfileId = computed(() => (profileId) => {
return documents.value.filter(item => item.profile === profileId && !item.tags.includes("Archiviert"))
})
const getDocumentsByPlantId = computed(() => (itemId) => {
return documents.value.filter(item => item.plant === itemId && !item.tags.includes("Archiviert"))
})