Commit 628e5bf2 authored by abergavenny's avatar abergavenny
Browse files

Version 1.0.0

parent b463e8cc
/*
* DEVINFO In dieser Datei können neue Formularoptionen hinzugefügt oder entfernt werden
*/
export const BUILDING_MATERIAL_OPTIONS = [
{ name: 'Mauer', value: 'BRICKWORK' },
{ name: 'Beton', value: 'CONCRETE' },
{ name: 'Holz', value: 'WOOD' },
{ name: 'Sonstige', value: 'OTHER' }
]
export const INSULATING_MATERIAL_OPTIONS = [
{ name: 'Keine', value: null },
{ name: 'EPS', short: 'EPS', value: 'EPS' },
{ name: 'XPS', short: 'XPS', value: 'XPS' },
{ name: 'PU/PIR', short: 'PU/PIR', value: 'PU_PIR' },
{ name: 'Steinwolle', value: 'MINERAL_WOOL' },
{ name: 'Glaswolle', value: 'GLAS_WOOL' },
{ name: 'Holzfaser', value: 'WOOD_FIBRE' },
{ name: 'Holzspäne', value: 'CHIPPED_WOOD' },
{ name: 'Stroh', value: 'STRAW' },
{ name: 'Sonstiges', value: 'OTHER' }
]
export const HEATING_INSTALLATION_OPTIONS = [
{ name: 'Fernwärme', value: 'DISTRICT_HEATING' },
{ name: 'Öl', value: 'OIL_HEATING' },
{ name: 'Gas', value: 'GAS_HEATING' },
{ name: 'Wärmepumpe Strommix', value: 'HEAT_PUMP' },
{ name: 'Wärmepumpe Ökostrom', value: 'HEAT_PUMP_CLEAN' },
{ name: 'Pellets', value: 'PELLETS' }
]
export const HEATING_TYPE_OPTIONS = [
{ name: 'Fernwärme', value: 'DISTRICT_HEATING' },
{ name: 'Öl', value: 'OIL_HEATING' },
{ name: 'Gas', value: 'GAS_HEATING' },
{ name: 'Stromheizung', value: 'ELECTRIC_HEATING' }
]
export const ROLE_OPTIONS = [
{ name: 'Verwalter', value: 'administrator' },
{ name: 'Eigentümer', value: 'user' },
]
export const ROOF_CLOUDING_OPTIONS = [
{ name: 'nicht verschattet', value: 4 },
{ name: 'wenig verschattet', value: 3 },
{ name: 'mittel verschattet', value: 2 },
{ name: 'viel verschattet', value: 1 }
]
export const WINDOW_CONDITION_OPTIONS = [
{ name: 'Sehr gut', value: 'VERY_GOOD' },
{ name: 'Gut', value: 'GOOD' },
{ name: 'Akzeptabel', value: 'ACCEPTABLE' },
{ name: 'Schlecht', value: 'BAD' }
]
export const WINDOW_FRAME_OPTIONS = [
{ name: 'Aluminium', value: 'ALUMINIUM' },
{ name: 'Holz', value: 'WOOD' },
{ name: 'Kunststoff', value: 'PLASTIC' },
{ name: 'Holz-Alu', value: 'WOOD_ALUMINIUM' },
{ name: 'Kunststoff-Alu', value: 'PLASTIC_ALUMINIUM' },
{ name: 'Stahl', value: 'STEEL' }
]
export const WINDOW_GLAZING_OPTIONS = [
{ name: '1-fach', value: 'SIMPLE_GLAZING' },
{ name: '2-fach', value: 'DOUBLE_GLAZING' },
{ name: '3-fach', value: 'TRIPLE_GLAZING' }
]
\ No newline at end of file
/*
* DEVINFO In dieser Datei können die Parameter für die Berechnungen verändert werden
*/
export const CONVERSION_FACTOR_GAS = 9.77
export const CONVERSION_FACTOR_HEAT_PUMP = 3.5
export const CONVERSION_FACTOR_OIL = 9.94
export const CONVERSION_FACTOR_PELLETS = 5
export const EQUIVALENT_CO2 = {
DISTRICT_HEATING: 311,
ELECTRIC_HEATING: 420,
GAS_HEATING: 247,
HEAT_PUMP: 420,
HEAT_PUMP_CLEAN: 0,
OIL_HEATING: 318,
PELLETS: 23
}
export const IMPACT_WINDOW_CONDITION = {
VERY_GOOD: 4,
GOOD: 3,
ACCEPTABLE: 2,
BAD: 1
}
export const IMPACT_WINDOW_GLAZING = {
SIMPLE_GLAZING: 1,
DOUBLE_GLAZING: 2.5,
TRIPLE_GLAZING: 4
}
export const LAMBDA_INSULATION = {
'EPS': 0.04,
'XPS': 0.04,
'PU_PIR': 0.023,
'MINERAL_WOOL': 0.035,
'GLAS_WOOL': 0.035,
'WOOD_FIBRE': 0.05,
'CHIPPED_WOOD': 0.05,
'STRAW': 0.06,
'OTHER': 0.05,
}
export const LAMBDA_ROOF = {
'FLAT': 80,
'NORMAL': 85
}
export const LAMBDA_ROOF_THICKNESS = {
'FLAT': 10,
'NORMAL': 3
}
export const LAMBDA_STRUCTURE = {
'BRICKWORK': 35,
'CONCRETE': 80,
'WOOD': 10,
'OTHER': 50,
}
export const REFERENCE_CO2_EMISSION = 0.142
export const REFERENCE_ENERGY_PERFORMANCE = 130
export const REFERENCE_INSULATION_FACADE = 0.28
export const REFERENCE_INSULATION_ROOF = 0.24
export const REPORT_FIELDS = {
apartmentComment: 'MISSING',
area: 'MISSING',
averageWindowAge: 'Alter',
averageWindowCondition: 'Zustand',
ceilingHeight: 'MISSING',
centralHeating: 'MISSING',
heatDemand: 'MISSING',
heatingType: 'MISSING',
largeHeatingElements: 'MISSING',
largeWindows: 'Fenster (groß)',
mediumHeatingElements: 'MISSING',
mediumWindows: 'Fenster (normal)',
panelHeating: 'MISSING',
powerDemand: 'MISSING',
roofWindows: 'MISSING',
smallHeatingElements: 'MISSING',
smallWindows: 'Fenster (klein)',
windowComment: 'MISSING',
windowFrame: 'Rahmen',
windowGlazing: 'Verglasung',
workingHeatingInstallations: 'MISSING',
}
\ No newline at end of file
import jwtDecode from 'jwt-decode'
import {
CONVERSION_FACTOR_GAS,
CONVERSION_FACTOR_HEAT_PUMP,
CONVERSION_FACTOR_OIL,
CONVERSION_FACTOR_PELLETS,
EQUIVALENT_CO2,
IMPACT_WINDOW_CONDITION,
IMPACT_WINDOW_GLAZING,
LAMBDA_INSULATION,
LAMBDA_ROOF,
LAMBDA_ROOF_THICKNESS,
LAMBDA_STRUCTURE,
REFERENCE_CO2_EMISSION,
REFERENCE_ENERGY_PERFORMANCE
} from '@/data/parameters'
const status = {
Success: 'SUCCESS',
Warning: 'WARNING',
Error: 'ERROR'
}
Object.freeze(status)
export const ResponseStatus = status
export const getBearerToken = () => {
const storage = window.localStorage
const token = storage.getItem('token')
return `Bearer ${token}`
}
export const setToken = (token) => {
const storage = window.localStorage
storage.setItem('token', token)
}
export const getTokenPayload = (token) => {
setToken(token)
const payload = jwtDecode(token)
return payload
}
// DEVINFO: Hier wird eine Kennung für ein Gebäude erzeugt
export function randomPrefix(length = 4) {
const chars = 'abcdefghijkmnopqrstuxyz'
let prefix = ''
for (let i = 0; i < length; i++) {
prefix += chars.charAt(Math.floor(Math.random() * chars.length))
}
return prefix
}
// DEVINFO: Dauer des Timeouts bis das Formular nach absenden erneut benutzt werden kann - default: 2000
export function resetAlert(alert) {
return setTimeout(() => {
alert.value = null
}, 2000)
}
export function numberOrText(value, ignore = false) {
return ignore || isNaN(value) ? value : value.toLocaleString('de-DE')
}
export function convertHeatConsumption(value, unit) {
if (!unit) return 0
let conversion
if (unit === 'DISTRICT_HEATING') {
conversion = value
} else if (unit === 'OIL_HEATING') {
conversion = value * CONVERSION_FACTOR_OIL
} else if (unit === 'GAS_HEATING') {
conversion = value * CONVERSION_FACTOR_GAS
} else if (unit === 'HEAT_PUMP') {
conversion = value / CONVERSION_FACTOR_HEAT_PUMP
} else if (unit === 'HEAT_PUMP_CLEAN') {
conversion = value / CONVERSION_FACTOR_HEAT_PUMP
} else if (unit === 'PELLETS') {
conversion = value * CONVERSION_FACTOR_PELLETS
} else {
conversion = value
}
return Math.round(conversion)
}
// DEVINFO Hier kann der Bewertungstext (Heizung) angepasst werden
export function convertHeaterEfficiency(value) {
if (value < 0.75) return `Schlecht`
else if (value < 0.80) return `Akzeptabel`
else if (value < 0.95) return `Gut`
else if (value <= 1) return `Sehr gut`
else return null
}
// DEVINFO Hier kann der Bewertungstext (Fenster) angepasst werden
export function convertWindowEfficiency(value) {
if (value < 1.5) return `Schlecht`
else if (value >= 1.5 && value < 2.5) return `Akzeptabel`
else if (value >= 2.5 && value < 3.5) return `Gut`
else if (value >= 3.5) return `Sehr gut`
else return null
}
export function calculateCO2Efficiency(type, consumption) {
const refValue = REFERENCE_CO2_EMISSION
let performance, potential, proportion
const pellets = EQUIVALENT_CO2['PELLETS']
const heating = EQUIVALENT_CO2[type]
performance = (heating / 1000) * consumption
potential = performance - ((pellets / 1000) * consumption)
proportion = performance / refValue
return [Math.round(performance), Math.round(potential), Math.round(proportion)]
}
export function calculateFacadeEfficiency(material, materialThickness, structure, structureThickness) {
const refValue = 0.213
let performance, potential
const insulationLambda = LAMBDA_INSULATION[material]
const structureLambda = LAMBDA_STRUCTURE[structure]
const insulationThickness = materialThickness / 100
const thickness = structureThickness / 100
const insulationDelta = insulationThickness / insulationLambda
const structureDelta = (thickness - insulationThickness) / structureLambda
performance = 1 / (insulationDelta + structureDelta + refValue)
potential = ((1 / refValue * performance) - 1) * 100
return [performance.toPrecision(3), Math.round(potential)]
}
export function calculateHeaterEfficiency(total, working) {
let performance
performance = working / total
return [performance]
}
export function calculateHeatingEfficiency(epc = null, consumption = null, buildingArea = null, demand = null, apartmentArea = null) {
const refValue = REFERENCE_ENERGY_PERFORMANCE
let distict, performance, potential
if (consumption && buildingArea) {
distict = false
performance = consumption / buildingArea
} else if (demand) {
distict = true
performance = demand / apartmentArea
} else {
distict = false
performance = epc
}
potential = ((1 / refValue * performance) - 1) * 100
return [distict, Math.round(performance), Math.round(potential)]
}
export function calculateRoofEfficiency(flat, material, materialThickness) {
const refValue = 0.17
let performance, potential
const insulationLambda = LAMBDA_INSULATION[material]
const roofLambda = LAMBDA_ROOF[flat ? 'FLAT' : 'NORMAL']
const roofThickness = LAMBDA_ROOF_THICKNESS[flat ? 'FLAT' : 'NORMAL']
const insulationThickness = materialThickness / 100
const thickness = roofThickness / 100
const insulationDelta = insulationThickness / insulationLambda
const roofDelta = thickness / roofLambda
performance = 1 / (insulationDelta + roofDelta + refValue)
potential = ((1 / refValue * performance) - 1) * 100
return [performance.toPrecision(3), Math.round(potential)]
}
export function calculateWindowEfficiency(avgAge, avgCondition, glazing) {
let refValue, performance
if (avgAge < 5) {
refValue = 4
} else if (avgAge < 10) {
refValue = 3
} else if (avgAge < 20) {
refValue = 2
} else if (avgAge >= 20) {
refValue = 1
}
const conditionImpact = IMPACT_WINDOW_CONDITION[avgCondition]
const glazingImpact = IMPACT_WINDOW_GLAZING[glazing]
performance = ((refValue * 2) + conditionImpact + (glazingImpact * 2)) / 5
return [performance]
}
\ No newline at end of file
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faBell } from '@fortawesome/free-regular-svg-icons'
import { faArrowRightFromBracket, faBars, faChevronDown, faChevronUp, faPen, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import App from './App.vue'
import router from './router'
import './assets/main.css'
library.add(faArrowRightFromBracket, faBars, faBell, faChevronDown, faChevronUp, faPen, faPlus, faXmark)
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.component('font-awesome-icon', FontAwesomeIcon)
app.mount('#app')
import { createRouter, createWebHistory } from 'vue-router'
import LandingView from '@/views/LandingView.vue'
import NotFoundView from '@/views/NotFoundView.vue'
import ResetPasswordView from '@/views/ResetPasswordView.vue'
import TermsView from '@/views/TermsView.vue'
import { useSessionStore } from '@/stores/session'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'landing',
component: LandingView
},
{
path: '/terms',
name: 'terms',
component: TermsView
},
{
path: '/reset-password',
name: 'reset',
component: ResetPasswordView
},
{
path: '/apartments',
name: 'apartments',
redirect: {
name: 'apartments.dashboard'
},
component: () => import('../views/ApartmentView.vue'),
beforeEnter: (to) => {
try {
const store = useSessionStore()
if (to.meta.requiresAuth && store.isAuthenticated && store.currentUser?.role === 'user') return true
return { name: 'landing' }
} catch (error) {
console.error('ERROR:', error.name)
return { name: 'landing' }
}
},
children: [
{
path: '',
name: 'apartments.dashboard',
component: () => import('../components/ApartmentDashboard.vue'),
},
],
meta: {
requiresAuth: true
}
},
{
path: '/buildings',
name: 'buildings',
redirect: {
name: 'buildings.dashboard'
},
component: () => import('../views/BuildingView.vue'),
beforeEnter: (to) => {
try {
const store = useSessionStore()
if (to.meta.requiresAuth && store.isAuthenticated && store.currentUser?.role === 'administrator') return true
return { name: 'landing' }
} catch (error) {
console.error('ERROR:', error.name)
return { name: 'landing' }
}
},
children: [
{
path: '',
name: 'buildings.dashboard',
redirect: {
name: 'buildings.dashboard.home'
},
component: () => import('../components/BuildingDashboard.vue'),
children: [
{
path: '',
name: 'buildings.dashboard.home',
component: () => import('../components/BuildingApartments.vue'),
},
{
path: 'apartment',
name: 'buildings.dashboard.apartment',
component: () => import('../components/BuildingApartmentDetails.vue'),
},
]
},
],
meta: {
requiresAuth: true
}
},
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView }
]
})
export default router
import axios from 'axios'
import { getBearerToken } from '@/helpers'
const BASE_URI = import.meta.env.VITE_API_URI
const publicApi = axios.create()
const privateApi = axios.create()
const multipartOptions = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
privateApi.interceptors.request.use((config) => {
config.headers.common['Authorization'] = getBearerToken()
return config
}, (err) => Promise.reject(err))
export const auth = {
async login(data) {
return publicApi.post(`${BASE_URI}/auth/login`, data).then(response => response.data)
},
async register(data) {
return publicApi.post(`${BASE_URI}/auth/register`, data).then(response => response.data)
},
async resetPassword(data) {
return publicApi.post(`${BASE_URI}/auth/reset-password`, data).then(response => response.data)
},
async updatePassword(token, data) {
return publicApi.post(`${BASE_URI}/auth/update-password/${token}`, data).then(response => response.data)
}
}
export const apartments = {
async create(data) {
return privateApi.post(`${BASE_URI}/api/v1/apartments`, data).then(response => response.data)
},
async findAll() {
return privateApi.get(`${BASE_URI}/api/v1/apartments`).then(response => response.data)
},
async findFile(id) {
return privateApi.get(`${BASE_URI}/api/v1/apartments/${id}/files`, { responseType: 'blob' }).then(response => response.data)
},
async findOne(id) {
return privateApi.get(`${BASE_URI}/api/v1/apartments/${id}`).then(response => response.data)
},
async update(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/apartments/${id}`, data).then(response => response.data)
},
async updateHeating(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/apartments/${id}/heating`, data).then(response => response.data)
},
async updateWindows(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/apartments/${id}/windows`, data).then(response => response.data)
},
async uploadFile(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/apartments/${id}/files`, data, multipartOptions).then(response => response.data)
}
}
export const buildings = {
async create(data) {
return privateApi.post(`${BASE_URI}/api/v1/buildings`, data).then(response => response.data)
},
async findAll() {
return privateApi.get(`${BASE_URI}/api/v1/buildings`).then(response => response.data)
},
async findOne(id) {
return privateApi.get(`${BASE_URI}/api/v1/buildings/${id}`).then(response => response.data)
},
async update(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/buildings/${id}`, data).then(response => response.data)
},
async updateBasement(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/buildings/${id}/basement`, data).then(response => response.data)
},
async updateCharacteristics(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/buildings/${id}/characteristics`, data).then(response => response.data)
},
async updateFacade(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/buildings/${id}/facade`, data).then(response => response.data)
},
async updateHeating(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/buildings/${id}/heating`, data).then(response => response.data)
},
async updateRoof(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/buildings/${id}/roof`, data).then(response => response.data)
}
}
export const self = {
async findOne() {
return privateApi.get(`${BASE_URI}/api/v1/self`).then(response => response.data)
}
}
export const users = {
async findAll() {
return privateApi.get(`${BASE_URI}/api/v1/users`).then(response => response.data)
},
async findOne(id) {
return privateApi.get(`${BASE_URI}/api/v1/users/${id}`).then(response => response.data)
},
async update(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/users/${id}`, data).then(response => response.data)
},
async updatePassword(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/users/${id}/change-password`, data).then(response => response.data)
},
async updateSharing(id, data) {
return privateApi.put(`${BASE_URI}/api/v1/users/${id}/sharing`, data).then(response => response.data)
}
}
import { defineStore } from 'pinia'
import {
WINDOW_CONDITION_OPTIONS,
WINDOW_FRAME_OPTIONS,
WINDOW_GLAZING_OPTIONS
} from '@/data/options'
import {
calculateCO2Efficiency,
calculateHeaterEfficiency,
calculateHeatingEfficiency,
calculateWindowEfficiency,
ResponseStatus
} from '@/helpers'
import { apartments as api } from '@/services/api'
import { useBuildingStore } from '@/stores/buildings'
import { useUserStore } from '@/stores/users'
export const useApartmentStore = defineStore({
id: 'apartments',
state: () => ({
active: null,
data: [],
files: {}
}),
getters: {
current: (state) => state.data.find(element => element._id === state.active),
count: (state) => state.data.length,
ofBuilding: (state) => {
const buildingStore = useBuildingStore()
return state.data.filter(element => element.linkedTo === buildingStore.active)
},
owner: (state) => {
const userStore = useUserStore()
const apartments = state.data.find(element => element._id === state.active)
if (apartments) {
return userStore.data.find(element => element._id === apartments.owner)
}
},
windowFrameString: (state) => {
const apartments = state.data.find(element => element._id === state.active)
if (apartments) {
const { windowFrame } = apartments.data
const material = WINDOW_FRAME_OPTIONS.find(element => element.value === windowFrame)
return material ? material.name : null
}
return null
},
windowConditionString: (state) => {
const apartments = state.data.find(element => element._id === state.active)
if (apartments) {
const { averageWindowCondition } = apartments.data
const material = WINDOW_CONDITION_OPTIONS.find(element => element.value === averageWindowCondition)
return material ? material.name : null
}
return null
},
windowGlazingString: (state) => {
const apartments = state.data.find(element => element._id === state.active)
if (apartments) {
const { windowGlazing } = apartments.data
const material = WINDOW_GLAZING_OPTIONS.find(element => element.value === windowGlazing)
return material ? material.name : null
}
return null
},
summary: (state) => {
const buildingStore = useBuildingStore()
const apartments = state.data.filter(element => element.linkedTo === buildingStore.active)
if (apartments) {
let totalArea = null
let totalHeatingElements = null
let totalPanelheating = null
let totalWindows = null
apartments.forEach((element) => {
const {
area,
largeHeatingElements,
largeWindows,
mediumHeatingElements,
mediumWindows,
panelHeating,
roofWindows,
smallHeatingElements,
smallWindows,
} = element.data
totalArea += area
totalHeatingElements += largeHeatingElements + mediumHeatingElements + smallHeatingElements
totalPanelheating += panelHeating
totalWindows += largeWindows + mediumWindows + roofWindows + smallWindows
})
return {
area: totalArea,
apartments: apartments.length,
heatingElements: totalHeatingElements,
panelHeating: totalPanelheating,
windows: totalWindows
}
}
return {}
},
co2Efficiency: (state) => {
const apartment = state.data.find(element => element._id === state.active)
if (apartment) {
const { heatDemand, heatingType } = apartment.data
if (!heatingType) {
return null
}
const [performance, potential, proportion] = calculateCO2Efficiency(heatingType, heatDemand)
return {
delta: performance,
km: proportion,
potential
}
}
return null
},
heaterEfficiency: (state) => {
const buildingStore = useBuildingStore()
const building = state.data.filter(element => element.linkedTo === buildingStore.active)
const apartment = state.data.find(element => element._id === state.active)
if (building && apartment) {
const { largeHeatingElements, mediumHeatingElements, smallHeatingElements, workingHeatingInstallations } = apartment.data
let sum = largeHeatingElements + mediumHeatingElements + smallHeatingElements
if (sum === 0) return null
let [performance] = calculateHeaterEfficiency(sum, workingHeatingInstallations)
return {
delta: performance
}
}
return null
},
heatingEfficiency: (state) => {
const buildingStore = useBuildingStore()
const building = buildingStore.single
const apartment = state.data.find(element => element._id === state.active)
if (building && apartment) {
const [distict, performance, potential] = calculateHeatingEfficiency(building.data.energyPerformanceCertificate, buildingStore.consumption, building.data.livingSpace, apartment.data.heatDemand, apartment.data.area)
if (!performance) return null
return {
distict,
performance: Math.round(performance),
potential: Math.round(potential)
}
}
return null
},
windowEfficiency: (state) => {
const buildingStore = useBuildingStore()
const building = state.data.filter(element => element.linkedTo === buildingStore.active)
const apartment = state.data.find(element => element._id === state.active)
if (building && apartment) {
const { averageWindowAge, averageWindowCondition, windowGlazing } = apartment.data
if (!averageWindowAge && !averageWindowCondition && !windowGlazing) {
return null
}
const [performance] = calculateWindowEfficiency(averageWindowAge, averageWindowCondition, windowGlazing)
return {
performance
}
}
return null
}
},
actions: {
async createApartment(data) {
try {
const response = await api.create(data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async fetchApartments() {
try {
const response = await api.findAll()
if (response.status === ResponseStatus.Success) {
this.data = response.data
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async fetchFile(id) {
try {
const blob = await api.findFile(id)
if (blob && blob.size > 0) {
const url = URL.createObjectURL(blob)
this.files[id] = url
return { success: true }
}
return { success: false }
} catch (error) {
return { success: false }
}
},
setActiveApartment(id) {
this.active = id
},
setApartments(data) {
this.data = data
},
async updateApartmentHeating(id, data) {
try {
const response = await api.updateHeating(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async updateApartmentWindows(id, data) {
try {
const response = await api.updateWindows(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async uploadFile(id, data) {
try {
const response = await api.uploadFile(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true }
}
return { success: false }
} catch (error) {
return { success: false }
}
}
}
})
import { defineStore } from 'pinia'
import {
BUILDING_MATERIAL_OPTIONS,
INSULATING_MATERIAL_OPTIONS,
HEATING_INSTALLATION_OPTIONS
} from '@/data/options'
import {
calculateCO2Efficiency,
calculateFacadeEfficiency,
calculateHeatingEfficiency,
calculateRoofEfficiency,
convertHeatConsumption,
ResponseStatus
} from '@/helpers'
import { buildings as api } from '@/services/api'
export const useBuildingStore = defineStore({
id: 'buildings',
state: () => ({
active: null,
data: [],
single: null
}),
getters: {
apartments: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building && building.apartments) return building.apartments
return []
},
consumption: (state) => {
const building = state.single || state.data.find(element => element._id === state.active)
if (building) {
const { heatingConsumption, heatingInstallation } = building.data
return convertHeatConsumption(heatingConsumption, heatingInstallation)
}
return null
},
current: (state) => state.data.find(element => element._id === state.active) || null,
firstBuilding: (state) => state.data.length > 0 ? state.data[0] : null,
insulatedSides: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { facadeEast, facadeNorth, facadeSouth, facadeWest } = building.data
// DEVINFO: Text für die Anzeige der aktuellen Dämmung der einzelnen Gebäudeseiten
if (facadeEast && facadeNorth && facadeSouth && facadeWest) {
return 'alle Seiten gedämmt'
} else if (!facadeEast && !facadeNorth && !facadeSouth && !facadeWest) {
return 'nicht gedämmt'
} else {
return 'teilweise gedämmt'
}
}
return null
},
buildingStructureString: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { buildingStructure } = building.data
const material = BUILDING_MATERIAL_OPTIONS.find(element => element.value === buildingStructure)
return material ? `${material?.name}` : null
}
return null
},
heatingInstallationString: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { heatingInstallation } = building.data
const material = HEATING_INSTALLATION_OPTIONS.find(element => element.value === heatingInstallation)
return material ? `${material?.name}` : null
}
return null
},
basementInsulationString: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { basementInsulatingMaterial, basementInsulatingMaterialThickness } = building.data
const material = INSULATING_MATERIAL_OPTIONS.find(element => element.value === basementInsulatingMaterial)
if (basementInsulatingMaterial && basementInsulatingMaterialThickness) {
return `${basementInsulatingMaterialThickness} cm ${material.short || material.name}`
}
}
return null
},
facadeInsulationString: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { facadeInsulatingMaterial, facadeInsulatingMaterialThickness } = building.data
const material = INSULATING_MATERIAL_OPTIONS.find(element => element.value === facadeInsulatingMaterial)
if (facadeInsulatingMaterial && facadeInsulatingMaterialThickness) {
return `${facadeInsulatingMaterialThickness} cm ${material.short || material.name}`
}
}
return null
},
roofInsulationString: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { roofInsulatingMaterial, roofInsulatingMaterialThickness } = building.data
const material = INSULATING_MATERIAL_OPTIONS.find(element => element.value === roofInsulatingMaterial)
if (roofInsulatingMaterial && roofInsulatingMaterialThickness) {
return `${roofInsulatingMaterialThickness} cm ${material.short || material.name}`
}
}
return null
},
co2Efficiency: (state) => {
const building = state.single || state.data.find(element => element._id === state.active)
if (building) {
const {
heatingConsumption,
heatingInstallation
} = building.data
if (!heatingInstallation) return null
const [performance, potential, proportion] = calculateCO2Efficiency(heatingInstallation, heatingConsumption)
return {
performance,
potential: potential > 0 ? potential : null,
proportion
}
}
return {}
},
facadeInsulationEfficiency: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const {
buildingStructure,
buildingStructureThickness,
facadeInsulatingMaterial,
facadeInsulatingMaterialThickness
} = building.data
if (!buildingStructure || !buildingStructureThickness || !facadeInsulatingMaterial || !facadeInsulatingMaterialThickness) return null
const [performance, potential] = calculateFacadeEfficiency(facadeInsulatingMaterial, facadeInsulatingMaterialThickness, buildingStructure, buildingStructureThickness)
return {
performance,
potential
}
}
return null
},
heatingEfficiency: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const { energyPerformanceCertificate, heatingConsumption, heatingInstallation, livingSpace } = building.data
const consumption = convertHeatConsumption(heatingConsumption, heatingInstallation)
// eslint-disable-next-line no-unused-vars
const [distinct, performance, potential] = calculateHeatingEfficiency(energyPerformanceCertificate, consumption, livingSpace)
if (!performance) return null
return {
performance,
potential
}
}
return null
},
roofInsulationEfficiency: (state) => {
const building = state.data.find(element => element._id === state.active)
if (building) {
const {
flatRoof,
roofInsulatingMaterial,
roofInsulatingMaterialThickness,
} = building.data
if (!roofInsulatingMaterial || !roofInsulatingMaterialThickness) return null
const [performance, potential] = calculateRoofEfficiency(flatRoof, roofInsulatingMaterial, roofInsulatingMaterialThickness)
return {
performance,
potential,
}
}
return null
},
},
actions: {
async createBuilding(data) {
try {
const response = await api.create(data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async fetchBuildingById(id) {
try {
const response = await api.findOne(id)
if (response.status === ResponseStatus.Success) {
this.single = response.data
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async fetchBuildings() {
try {
const response = await api.findAll()
if (response.status === ResponseStatus.Success) {
this.data = response.data
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
setActiveBuilding(id) {
this.active = id
},
setBuildings(data) {
this.data = data
},
async updateBuilding(id, data) {
try {
const response = await api.update(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async updateBuildingCharacteristics(id, data) {
try {
const response = await api.updateCharacteristics(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async updateBuildingHeating(id, data) {
try {
const response = await api.updateHeating(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async updateBuildingFacade(id, data) {
try {
const response = await api.updateFacade(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async updateBuildingRoof(id, data) {
try {
const response = await api.updateRoof(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
async updateBuildingBasement(id, data) {
try {
const response = await api.updateBasement(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
}
}
})
import { defineStore } from 'pinia'
import { ResponseStatus } from '@/helpers'
import { self as api, users as userApi } from '@/services/api'
export const useSessionStore = defineStore({
id: 'session',
state: () => ({
connectionState: false,
currentUser: null,
isAuthenticated: false,
self: null,
}),
getters: {
getUser: (state) => state.currentUser
},
actions: {
async fetchSelf() {
try {
const response = await api.findOne()
if (response.status === ResponseStatus.Success) {
this.self = response.data
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
setConnectionState(connectionState) {
this.connectionState = connectionState
},
setUser(user, success) {
this.currentUser = user
this.isAuthenticated = success
},
async updateSharing(id, data) {
try {
const response = await userApi.updateSharing(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
}
}
})
import { defineStore } from 'pinia'
import { ResponseStatus } from '@/helpers'
import { users as api } from '@/services/api'
export const useUserStore = defineStore({
id: 'users',
state: () => ({
data: []
}),
getters: {},
actions: {
async fetchUsers() {
try {
const response = await api.findAll()
if (response.status === ResponseStatus.Success) {
this.data = response.data
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
},
setUsers(data) {
this.data = data
},
async updateUserPassword(id, data) {
try {
const response = await api.updatePassword(id, data)
if (response.status === ResponseStatus.Success) {
return { success: true, message: response.code }
}
return { success: false, message: response.code }
} catch (error) {
return { success: false }
}
}
}
})
<script setup>
import { onMounted, ref } from 'vue'
import AppBar from '@/components/AppBar.vue'
import ContentLoading from '@/components/ContentLoading.vue'
import ModalContainer from '@/components/ModalContainer.vue'
import PageContainer from '@/components/PageContainer.vue'
import PageContent from '@/components/PageContent.vue'
import TermsRequest from '@/components/TermsRequest.vue'
import { useApartmentStore } from '@/stores/apartments'
import { useBuildingStore } from '@/stores/buildings'
import { useSessionStore } from '@/stores/session'
const isLoading = ref(true)
const showModal = ref(false)
const apartments = useApartmentStore()
const buildings = useBuildingStore()
const session = useSessionStore()
function handleModal() {
showModal.value = false
}
onMounted(() => {
Promise.all([
session.fetchSelf(),
apartments.fetchApartments()
])
.then(async () => {
let buildingId, apartmentId
if (apartments.data.length > 0) {
buildingId = apartments.data[0].linkedTo
apartmentId = apartments.data[0]._id
apartments.setActiveApartment(apartmentId)
}
await buildings.fetchBuildingById(buildingId)
})
.finally(() => {
isLoading.value = false
})
})
</script>
<template>
<PageContainer v-if="!isLoading">
<AppBar />
<PageContent>
<router-view></router-view>
</PageContent>
</PageContainer>
<ContentLoading v-else />
<ModalContainer v-if="!session.self?.sharingAllowed" :permanent="true">
<TermsRequest @on-close-modal="handleModal" />
</ModalContainer>
</template>
\ No newline at end of file
<script setup>
import { onMounted, ref } from 'vue'
import AppBar from '@/components/AppBar.vue'
import BuildingPicker from '@/components/BuildingPicker.vue'
import ContentLoading from '@/components/ContentLoading.vue'
import ModalContainer from '@/components/ModalContainer.vue'
import PageContainer from '@/components/PageContainer.vue'
import PageContent from '@/components/PageContent.vue'
import BuildingForm from '@/components/forms/BuildingForm.vue'
import { useApartmentStore } from '@/stores/apartments'
import { useBuildingStore } from '@/stores/buildings'
import { useUserStore } from '@/stores/users'
const isLoading = ref(true)
const showModal = ref(false)
const apartments = useApartmentStore()
const buildings = useBuildingStore()
const users = useUserStore()
function handleModal() {
showModal.value = !showModal.value
}
onMounted(() => {
Promise.all([
apartments.fetchApartments(),
buildings.fetchBuildings(),
users.fetchUsers()
])
.then(() => {
isLoading.value = false
})
})
</script>
<template>
<PageContainer v-if="!isLoading">
<AppBar>
<BuildingPicker />
<button v-if="buildings.firstBuilding?.setupCompleted" class="button secondary" type="button" @click="handleModal">
<div class="button-content">
<span>Neues Gebäude</span>
</div>
<div class="button-icon">
<font-awesome-icon icon="fa-solid fa-plus" />
</div>
</button>
</AppBar>
<PageContent>
<router-view></router-view>
</PageContent>
</PageContainer>
<ContentLoading v-else />
<ModalContainer v-if="showModal" label="Gebäude anlegen" @close-modal="handleModal">
<BuildingForm @close-modal="handleModal" />
</ModalContainer>
</template>
\ No newline at end of file
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import BrandBanner from '@/components/BrandBanner.vue'
import LoginForm from '@/components/forms/LoginForm.vue'
import MessageBox from '@/components/MessageBox.vue'
import RegistrationForm from '@/components/forms/RegistrationForm.vue'
import { MESSAGES } from '@/data/messages'
import { useSessionStore } from '@/stores/session'
const gmlId = ref(null)
const message = ref(null)
const messageType = ref(null)
const showLogin = ref(true)
const route = useRoute()
const session = useSessionStore()
function toggleTab() {
message.value = null
showLogin.value = !showLogin.value
}
function onSuccess(isRequired) {
if (isRequired) message.value = MESSAGES['CONFIRMATION_REQUIRED']
showLogin.value = !showLogin.value
}
onMounted(() => {
gmlId.value = route.query?.gmlId
if (route.query?.tab) showLogin.value = route.query?.tab !== 'register'
if (route.query?.verified) {
message.value = route.query.verified === 'true' ? MESSAGES['CONFIRMATION_COMPLETED'] : MESSAGES['TOKEN_INVALID']
messageType.value = route.query.verified === 'true' ? 'success' : 'danger'
}
})
</script>
<template>
<div class="landing-view">
<BrandBanner />
<MessageBox v-if="!session.connectionState || message" :msg="message || MESSAGES['NO_INTERNET_CONNECTION']"
:type="messageType || 'danger'" />
<div class="landing-view__tabs shadow" @click="toggleTab">
<span class="landing-view__tab" :class="{ active: showLogin }">Anmeldung</span>
<span class="landing-view__tab" :class="{ active: !showLogin }">Registrierung</span>
</div>
<LoginForm v-if="showLogin" />
<RegistrationForm v-else :gmlId="gmlId" @on-success="onSuccess" />
</div>
</template>
<style scoped>
.landing-view {
overflow-y: auto;
margin: 0 auto;
padding: 1em;
width: min(100%, 35em);
display: flex;
flex-direction: column;
gap: var(--spacing);
border-radius: var(--radius);
}
.landing-view__tabs {
background-color: hsl(var(--clr-content));
padding: var(--spacing-s);
display: flex;
gap: var(--spacing-s);
justify-content: center;
border-radius: var(--radius);
}
.landing-view__tab {
padding: var(--spacing-s);
flex: 1;
text-align: center;
border-radius: var(--radius);
color: var(--clr-text);
cursor: pointer;
}
.landing-view__tab.active {
background-color: hsl(var(--clr-primary));
color: var(--clr-white);
transition: var(--t-background-color);
}
.landing-view__tab:hover:not(.active) {
background-color: hsl(var(--clr-background));
transition: var(--t-background-color);
}
</style>
<template>
<div class="not-found">
<div class="nf404">
<span>404</span>
</div>
<a class="button" href="/">Zurück</a>
</div>
</template>
<style scoped>
@keyframes flash {
0% {
background-color: hsl(var(--clr-primary));
}
50% {
background-color: hsl(var(--clr-primary-shade));
}
100% {
background-color: hsl(var(--clr-primary));
}
}
.not-found {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.nf404 {
background-color: hsl(var(--clr-primary));
padding: var(--spacing);
width: 10em;
height: 10em;
animation: flash 1500ms infinite;
}
.nf404>span {
color: var(--clr-white);
font-size: 3em;
}
</style>
\ No newline at end of file
<script setup>
import { useRouter } from 'vue-router'
import SectionTitle from '@/components/SectionTitle.vue'
const router = useRouter()
function handleReturn() {
router.push({ name: 'landing' })
}
</script>
<template>
<div class="legal shadow">
<SectionTitle value="Datenschutzerklärung" />
<div>
<!-- DEVINFO - Privacy: Hier den Text für die Datenschutzerklärung eintragen -->
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna
sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra
imperdiet enim. Fusce est. Vivamus a tellus.
<!-- END - Privacy -->
</div>
<div class="legal-button">
<button class="button primary" type="button" @click="handleReturn">Zurück</button>
</div>
</div>
</template>
\ No newline at end of file
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import BrandBanner from '@/components/BrandBanner.vue'
import ResetPasswordForm from '@/components/forms/ResetPasswordForm.vue'
import UpdatePasswordForm from '@/components/forms/UpdatePasswordForm.vue'
const requested = ref(false)
const token = ref(null)
const route = useRoute()
onMounted(() => {
requested.value = !!route.query?.token
token.value = route.query?.token
})
</script>
<template>
<div class="reset-view">
<BrandBanner />
<UpdatePasswordForm v-if="requested" :token="token" />
<ResetPasswordForm v-else />
</div>
</template>
<style scoped>
.reset-view {
overflow-y: auto;
margin: 0 auto;
padding: 1em;
width: min(100%, 35em);
display: flex;
flex-direction: column;
gap: var(--spacing);
border-radius: var(--radius);
}
</style>
<script setup>
import { useRouter } from 'vue-router'
import SectionTitle from '@/components/SectionTitle.vue'
const router = useRouter()
function handleReturn() {
router.push({ name: 'landing' })
}
</script>
<template>
<div class="legal shadow">
<SectionTitle value="Nutzungsbedingungen" />
<div>
<!-- DEVINFO - ToS: Hier den Text für die Nutzungsbedingungen eintragen -->
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna
sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra
imperdiet enim. Fusce est. Vivamus a tellus.
<!-- END - ToS -->
</div>
<div class="legal-button">
<button class="button primary" type="button" @click="handleReturn">Zurück</button>
</div>
</div>
</template>
\ No newline at end of file
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
db.createUser({
user: 'your_user_here',
pwd: 'your_pass_here',
roles: [{ role: 'readWrite', db: 'dreiprozentplus' }]
})
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment