Compare commits

..

10 Commits

Author SHA1 Message Date
f793d4cce6 Supabase Removals Frontend
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 3m2s
Build and Push Docker Images / build-frontend (push) Successful in 3m38s
2026-02-14 13:35:21 +01:00
c3f46cd184 Supabase Removals Frontend 2026-02-14 13:15:01 +01:00
6bf336356d Supabase Removals Frontend 2026-02-14 13:14:22 +01:00
55699da42c Supabase Removals Frontend 2026-02-14 12:48:59 +01:00
053f184a33 Supabase Removals Backend 2026-02-14 12:29:59 +01:00
6541cb2adf Supabase Removals Backend 2026-02-14 12:27:44 +01:00
7dca84947e Supabase Removals 2026-02-14 12:16:50 +01:00
45fd6fda08 Remove Supa 2026-02-14 12:03:12 +01:00
31e80fb386 Fix #96 2026-02-14 11:59:07 +01:00
7ea28cc6c0 Neue E-Mail Sending Seite 2026-02-14 11:50:58 +01:00
67 changed files with 937 additions and 1429 deletions

View File

@@ -0,0 +1,3 @@
{
"rules": []
}

View File

@@ -29,7 +29,6 @@
"@infisical/sdk": "^4.0.6", "@infisical/sdk": "^4.0.6",
"@mmote/niimbluelib": "^0.0.1-alpha.29", "@mmote/niimbluelib": "^0.0.1-alpha.29",
"@prisma/client": "^6.15.0", "@prisma/client": "^6.15.0",
"@supabase/supabase-js": "^2.56.1",
"@zip.js/zip.js": "^2.7.73", "@zip.js/zip.js": "^2.7.73",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"axios": "^1.12.1", "axios": "^1.12.1",

View File

@@ -1,6 +1,5 @@
import Fastify from "fastify"; import Fastify from "fastify";
import swaggerPlugin from "./plugins/swagger" import swaggerPlugin from "./plugins/swagger"
import supabasePlugin from "./plugins/supabase";
import dayjsPlugin from "./plugins/dayjs"; import dayjsPlugin from "./plugins/dayjs";
import healthRoutes from "./routes/health"; import healthRoutes from "./routes/health";
import meRoutes from "./routes/auth/me"; import meRoutes from "./routes/auth/me";
@@ -73,7 +72,6 @@ async function main() {
// Plugins Global verfügbar // Plugins Global verfügbar
await app.register(swaggerPlugin); await app.register(swaggerPlugin);
await app.register(supabasePlugin);
await app.register(tenantPlugin); await app.register(tenantPlugin);
await app.register(dayjsPlugin); await app.register(dayjsPlugin);
await app.register(dbPlugin); await app.register(dbPlugin);

View File

@@ -1,5 +1,7 @@
// modules/helpdesk/helpdesk.contact.service.ts // modules/helpdesk/helpdesk.contact.service.ts
import { FastifyInstance } from 'fastify' import { FastifyInstance } from 'fastify'
import { and, eq, or } from "drizzle-orm";
import { helpdesk_contacts } from "../../../db/schema";
export async function getOrCreateContact( export async function getOrCreateContact(
server: FastifyInstance, server: FastifyInstance,
@@ -9,30 +11,35 @@ export async function getOrCreateContact(
if (!email && !phone) throw new Error('Contact must have at least an email or phone') if (!email && !phone) throw new Error('Contact must have at least an email or phone')
// Bestehenden Kontakt prüfen // Bestehenden Kontakt prüfen
const { data: existing, error: findError } = await server.supabase const matchConditions = []
.from('helpdesk_contacts') if (email) matchConditions.push(eq(helpdesk_contacts.email, email))
.select('*') if (phone) matchConditions.push(eq(helpdesk_contacts.phone, phone))
.eq('tenant_id', tenant_id)
.or(`email.eq.${email || ''},phone.eq.${phone || ''}`)
.maybeSingle()
if (findError) throw findError const existing = await server.db
if (existing) return existing .select()
.from(helpdesk_contacts)
.where(
and(
eq(helpdesk_contacts.tenantId, tenant_id),
or(...matchConditions)
)
)
.limit(1)
if (existing[0]) return existing[0]
// Anlegen // Anlegen
const { data: created, error: insertError } = await server.supabase const created = await server.db
.from('helpdesk_contacts') .insert(helpdesk_contacts)
.insert({ .values({
tenant_id, tenantId: tenant_id,
email, email,
phone, phone,
display_name, displayName: display_name,
customer_id, customerId: customer_id,
contact_id contactId: contact_id
}) })
.select() .returning()
.single()
if (insertError) throw insertError return created[0]
return created
} }

View File

@@ -2,6 +2,8 @@
import { FastifyInstance } from 'fastify' import { FastifyInstance } from 'fastify'
import { getOrCreateContact } from './helpdesk.contact.service.js' import { getOrCreateContact } from './helpdesk.contact.service.js'
import {useNextNumberRangeNumber} from "../../utils/functions"; import {useNextNumberRangeNumber} from "../../utils/functions";
import { and, desc, eq } from "drizzle-orm";
import { customers, helpdesk_contacts, helpdesk_conversations } from "../../../db/schema";
export async function createConversation( export async function createConversation(
server: FastifyInstance, server: FastifyInstance,
@@ -25,24 +27,34 @@ export async function createConversation(
const {usedNumber } = await useNextNumberRangeNumber(server, tenant_id, "tickets") const {usedNumber } = await useNextNumberRangeNumber(server, tenant_id, "tickets")
const { data, error } = await server.supabase const inserted = await server.db
.from('helpdesk_conversations') .insert(helpdesk_conversations)
.insert({ .values({
tenant_id, tenantId: tenant_id,
contact_id: contactRecord.id, contactId: contactRecord.id,
channel_instance_id, channelInstanceId: channel_instance_id,
subject: subject || null, subject: subject || null,
status: 'open', status: 'open',
created_at: new Date().toISOString(), createdAt: new Date(),
customer_id, customerId: customer_id,
contact_person_id, contactPersonId: contact_person_id,
ticket_number: usedNumber ticketNumber: usedNumber
}) })
.select() .returning()
.single()
if (error) throw error const data = inserted[0]
return data
return {
...data,
channel_instance_id: data.channelInstanceId,
contact_id: data.contactId,
contact_person_id: data.contactPersonId,
created_at: data.createdAt,
customer_id: data.customerId,
last_message_at: data.lastMessageAt,
tenant_id: data.tenantId,
ticket_number: data.ticketNumber,
}
} }
export async function getConversations( export async function getConversations(
@@ -52,22 +64,34 @@ export async function getConversations(
) { ) {
const { status, limit = 50 } = opts || {} const { status, limit = 50 } = opts || {}
let query = server.supabase.from('helpdesk_conversations').select('*, customer_id(*)').eq('tenant_id', tenant_id) const filters = [eq(helpdesk_conversations.tenantId, tenant_id)]
if (status) filters.push(eq(helpdesk_conversations.status, status))
if (status) query = query.eq('status', status) const data = await server.db
query = query.order('last_message_at', { ascending: false }).limit(limit) .select({
conversation: helpdesk_conversations,
const { data, error } = await query contact: helpdesk_contacts,
if (error) throw error customer: customers,
const mappedData = data.map(entry => {
return {
...entry,
customer: entry.customer_id
}
}) })
.from(helpdesk_conversations)
.leftJoin(helpdesk_contacts, eq(helpdesk_contacts.id, helpdesk_conversations.contactId))
.leftJoin(customers, eq(customers.id, helpdesk_conversations.customerId))
.where(and(...filters))
.orderBy(desc(helpdesk_conversations.lastMessageAt))
.limit(limit)
return mappedData return data.map((entry) => ({
...entry.conversation,
helpdesk_contacts: entry.contact,
channel_instance_id: entry.conversation.channelInstanceId,
contact_id: entry.conversation.contactId,
contact_person_id: entry.conversation.contactPersonId,
created_at: entry.conversation.createdAt,
customer_id: entry.customer,
last_message_at: entry.conversation.lastMessageAt,
tenant_id: entry.conversation.tenantId,
ticket_number: entry.conversation.ticketNumber,
}))
} }
export async function updateConversationStatus( export async function updateConversationStatus(
@@ -78,13 +102,22 @@ export async function updateConversationStatus(
const valid = ['open', 'in_progress', 'waiting_for_customer', 'answered', 'closed'] const valid = ['open', 'in_progress', 'waiting_for_customer', 'answered', 'closed']
if (!valid.includes(status)) throw new Error('Invalid status') if (!valid.includes(status)) throw new Error('Invalid status')
const { data, error } = await server.supabase const updated = await server.db
.from('helpdesk_conversations') .update(helpdesk_conversations)
.update({ status }) .set({ status })
.eq('id', conversation_id) .where(eq(helpdesk_conversations.id, conversation_id))
.select() .returning()
.single()
if (error) throw error const data = updated[0]
return data return {
...data,
channel_instance_id: data.channelInstanceId,
contact_id: data.contactId,
contact_person_id: data.contactPersonId,
created_at: data.createdAt,
customer_id: data.customerId,
last_message_at: data.lastMessageAt,
tenant_id: data.tenantId,
ticket_number: data.ticketNumber,
}
} }

View File

@@ -1,5 +1,7 @@
// modules/helpdesk/helpdesk.message.service.ts // modules/helpdesk/helpdesk.message.service.ts
import { FastifyInstance } from 'fastify' import { FastifyInstance } from 'fastify'
import { asc, eq } from "drizzle-orm";
import { helpdesk_conversations, helpdesk_messages } from "../../../db/schema";
export async function addMessage( export async function addMessage(
server: FastifyInstance, server: FastifyInstance,
@@ -23,38 +25,53 @@ export async function addMessage(
) { ) {
if (!payload?.text) throw new Error('Message payload requires text content') if (!payload?.text) throw new Error('Message payload requires text content')
const { data: message, error } = await server.supabase const inserted = await server.db
.from('helpdesk_messages') .insert(helpdesk_messages)
.insert({ .values({
tenant_id, tenantId: tenant_id,
conversation_id, conversationId: conversation_id,
author_user_id, authorUserId: author_user_id,
direction, direction,
payload, payload,
raw_meta, rawMeta: raw_meta,
created_at: new Date().toISOString(), externalMessageId: external_message_id,
receivedAt: new Date(),
}) })
.select() .returning()
.single()
if (error) throw error const message = inserted[0]
// Letzte Nachricht aktualisieren // Letzte Nachricht aktualisieren
await server.supabase await server.db
.from('helpdesk_conversations') .update(helpdesk_conversations)
.update({ last_message_at: new Date().toISOString() }) .set({ lastMessageAt: new Date() })
.eq('id', conversation_id) .where(eq(helpdesk_conversations.id, conversation_id))
return message return {
...message,
author_user_id: message.authorUserId,
conversation_id: message.conversationId,
created_at: message.createdAt,
external_message_id: message.externalMessageId,
raw_meta: message.rawMeta,
tenant_id: message.tenantId,
}
} }
export async function getMessages(server: FastifyInstance, conversation_id: string) { export async function getMessages(server: FastifyInstance, conversation_id: string) {
const { data, error } = await server.supabase const data = await server.db
.from('helpdesk_messages') .select()
.select('*') .from(helpdesk_messages)
.eq('conversation_id', conversation_id) .where(eq(helpdesk_messages.conversationId, conversation_id))
.order('created_at', { ascending: true }) .orderBy(asc(helpdesk_messages.createdAt))
if (error) throw error return data.map((message) => ({
return data ...message,
author_user_id: message.authorUserId,
conversation_id: message.conversationId,
created_at: message.createdAt,
external_message_id: message.externalMessageId,
raw_meta: message.rawMeta,
tenant_id: message.tenantId,
}))
} }

View File

@@ -1,6 +1,8 @@
// services/notification.service.ts // services/notification.service.ts
import type { FastifyInstance } from 'fastify'; import type { FastifyInstance } from 'fastify';
import {secrets} from "../utils/secrets"; import {secrets} from "../utils/secrets";
import { eq } from "drizzle-orm";
import { notificationsEventTypes, notificationsItems } from "../../db/schema";
export type NotificationStatus = 'queued' | 'sent' | 'failed'; export type NotificationStatus = 'queued' | 'sent' | 'failed';
@@ -34,16 +36,16 @@ export class NotificationService {
*/ */
async trigger(input: TriggerInput) { async trigger(input: TriggerInput) {
const { tenantId, userId, eventType, title, message, payload } = input; const { tenantId, userId, eventType, title, message, payload } = input;
const supabase = this.server.supabase;
// 1) Event-Typ prüfen (aktiv?) // 1) Event-Typ prüfen (aktiv?)
const { data: eventTypeRow, error: etErr } = await supabase const eventTypeRows = await this.server.db
.from('notifications_event_types') .select()
.select('event_key,is_active') .from(notificationsEventTypes)
.eq('event_key', eventType) .where(eq(notificationsEventTypes.eventKey, eventType))
.maybeSingle(); .limit(1)
const eventTypeRow = eventTypeRows[0]
if (etErr || !eventTypeRow || eventTypeRow.is_active !== true) { if (!eventTypeRow || eventTypeRow.isActive !== true) {
throw new Error(`Unbekannter oder inaktiver Event-Typ: ${eventType}`); throw new Error(`Unbekannter oder inaktiver Event-Typ: ${eventType}`);
} }
@@ -54,40 +56,40 @@ export class NotificationService {
} }
// 3) Notification anlegen (status: queued) // 3) Notification anlegen (status: queued)
const { data: inserted, error: insErr } = await supabase const insertedRows = await this.server.db
.from('notifications_items') .insert(notificationsItems)
.insert({ .values({
tenant_id: tenantId, tenantId,
user_id: userId, userId,
event_type: eventType, eventType,
title, title,
message, message,
payload: payload ?? null, payload: payload ?? null,
channel: 'email', channel: 'email',
status: 'queued' status: 'queued'
}) })
.select('id') .returning({ id: notificationsItems.id })
.single(); const inserted = insertedRows[0]
if (insErr || !inserted) { if (!inserted) {
throw new Error(`Fehler beim Einfügen der Notification: ${insErr?.message}`); throw new Error("Fehler beim Einfügen der Notification");
} }
// 4) E-Mail versenden // 4) E-Mail versenden
try { try {
await this.sendEmail(user.email, title, message); await this.sendEmail(user.email, title, message);
await supabase await this.server.db
.from('notifications_items') .update(notificationsItems)
.update({ status: 'sent', sent_at: new Date().toISOString() }) .set({ status: 'sent', sentAt: new Date() })
.eq('id', inserted.id); .where(eq(notificationsItems.id, inserted.id));
return { success: true, id: inserted.id }; return { success: true, id: inserted.id };
} catch (err: any) { } catch (err: any) {
await supabase await this.server.db
.from('notifications_items') .update(notificationsItems)
.update({ status: 'failed', error: String(err?.message || err) }) .set({ status: 'failed', error: String(err?.message || err) })
.eq('id', inserted.id); .where(eq(notificationsItems.id, inserted.id));
this.server.log.error({ err, notificationId: inserted.id }, 'E-Mail Versand fehlgeschlagen'); this.server.log.error({ err, notificationId: inserted.id }, 'E-Mail Versand fehlgeschlagen');
return { success: false, error: err?.message || 'E-Mail Versand fehlgeschlagen' }; return { success: false, error: err?.message || 'E-Mail Versand fehlgeschlagen' };

View File

@@ -1,19 +0,0 @@
import { FastifyInstance } from "fastify";
import fp from "fastify-plugin";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import {secrets} from "../utils/secrets";
export default fp(async (server: FastifyInstance) => {
const supabaseUrl = secrets.SUPABASE_URL
const supabaseServiceKey = secrets.SUPABASE_SERVICE_ROLE_KEY
const supabase: SupabaseClient = createClient(supabaseUrl, supabaseServiceKey);
// Fastify um supabase erweitern
server.decorate("supabase", supabase);
});
declare module "fastify" {
interface FastifyInstance {
supabase: SupabaseClient;
}
}

View File

@@ -1,5 +1,7 @@
import { FastifyInstance, FastifyRequest } from "fastify"; import { FastifyInstance, FastifyRequest } from "fastify";
import fp from "fastify-plugin"; import fp from "fastify-plugin";
import { eq } from "drizzle-orm";
import { tenants } from "../../db/schema";
export default fp(async (server: FastifyInstance) => { export default fp(async (server: FastifyInstance) => {
server.addHook("preHandler", async (req, reply) => { server.addHook("preHandler", async (req, reply) => {
@@ -9,11 +11,12 @@ export default fp(async (server: FastifyInstance) => {
return; return;
} }
// Tenant aus DB laden // Tenant aus DB laden
const { data: tenant } = await server.supabase const rows = await server.db
.from("tenants") .select()
.select("*") .from(tenants)
.eq("portalDomain", host) .where(eq(tenants.portalDomain, host))
.single(); .limit(1);
const tenant = rows[0];
if(!tenant) { if(!tenant) {

View File

@@ -1,6 +1,4 @@
import { FastifyInstance } from "fastify"; import { FastifyInstance } from "fastify";
import jwt from "jsonwebtoken";
import {insertHistoryItem} from "../utils/history";
import {buildExportZip} from "../utils/export/datev"; import {buildExportZip} from "../utils/export/datev";
import {s3} from "../utils/s3"; import {s3} from "../utils/s3";
import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3" import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3"
@@ -9,6 +7,8 @@ import dayjs from "dayjs";
import {randomUUID} from "node:crypto"; import {randomUUID} from "node:crypto";
import {secrets} from "../utils/secrets"; import {secrets} from "../utils/secrets";
import {createSEPAExport} from "../utils/export/sepa"; import {createSEPAExport} from "../utils/export/sepa";
import {generatedexports} from "../../db/schema";
import {eq} from "drizzle-orm";
const createDatevExport = async (server:FastifyInstance,req:any,startDate,endDate,beraternr,mandantennr) => { const createDatevExport = async (server:FastifyInstance,req:any,startDate,endDate,beraternr,mandantennr) => {
try { try {
@@ -45,25 +45,21 @@ const createDatevExport = async (server:FastifyInstance,req:any,startDate,endDat
console.log(url) console.log(url)
// 5) In Supabase-DB speichern // 5) In Haupt-DB speichern
const { data, error } = await server.supabase const inserted = await server.db
.from("exports") .insert(generatedexports)
.insert([ .values({
{ tenantId: req.user.tenant_id,
tenant_id: req.user.tenant_id, startDate: new Date(startDate),
start_date: startDate, endDate: new Date(endDate),
end_date: endDate, validUntil: dayjs().add(24, "hours").toDate(),
valid_until: dayjs().add(24,"hours").toISOString(), filePath: fileKey,
file_path: fileKey, url,
url: url, type: "datev",
created_at: new Date().toISOString(), })
}, .returning()
])
.select()
.single()
console.log(data) console.log(inserted[0])
console.log(error)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@@ -120,9 +116,22 @@ export default async function exportRoutes(server: FastifyInstance) {
//List Exports Available for Download //List Exports Available for Download
server.get("/exports", async (req,reply) => { server.get("/exports", async (req,reply) => {
const {data,error} = await server.supabase.from("exports").select().eq("tenant_id",req.user.tenant_id) const data = await server.db
.select({
id: generatedexports.id,
created_at: generatedexports.createdAt,
tenant_id: generatedexports.tenantId,
start_date: generatedexports.startDate,
end_date: generatedexports.endDate,
valid_until: generatedexports.validUntil,
type: generatedexports.type,
url: generatedexports.url,
file_path: generatedexports.filePath,
})
.from(generatedexports)
.where(eq(generatedexports.tenantId, req.user.tenant_id))
console.log(data,error) console.log(data)
reply.send(data) reply.send(data)
}) })

View File

@@ -110,17 +110,6 @@ export default async function functionRoutes(server: FastifyInstance) {
const data = await server.db.select().from(citys).where(eq(citys.zip,zip)) const data = await server.db.select().from(citys).where(eq(citys.zip,zip))
/*const { data, error } = await server.supabase
.from('citys')
.select()
.eq('zip', zip)
.maybeSingle()
if (error) {
console.log(error)
return reply.code(500).send({ error: 'Database error' })
}*/
if (!data) { if (!data) {
return reply.code(404).send({ error: 'ZIP not found' }) return reply.code(404).send({ error: 'ZIP not found' })
} }

View File

@@ -3,12 +3,11 @@ import { FastifyInstance } from "fastify";
export default async function routes(server: FastifyInstance) { export default async function routes(server: FastifyInstance) {
server.get("/ping", async () => { server.get("/ping", async () => {
// Testquery gegen DB // Testquery gegen DB
const { data, error } = await server.supabase.from("tenants").select("id").limit(1); const result = await server.db.execute("SELECT NOW()");
return { return {
status: "ok", status: "ok",
db: error ? "not connected" : "connected", db: JSON.stringify(result.rows[0]),
tenant_count: data?.length ?? 0
}; };
}); });
} }

View File

@@ -3,8 +3,9 @@ import { FastifyPluginAsync } from 'fastify'
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js' import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js' import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js' import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
import {extractDomain, findCustomerOrContactByEmailOrDomain} from "../utils/helpers"; import { findCustomerOrContactByEmailOrDomain } from "../utils/helpers";
import {useNextNumberRangeNumber} from "../utils/functions"; import { eq } from "drizzle-orm";
import { helpdesk_conversations, helpdesk_messages } from "../../db/schema";
// ------------------------------------------------------------- // -------------------------------------------------------------
// 📧 Interne M2M-Route für eingehende E-Mails // 📧 Interne M2M-Route für eingehende E-Mails
@@ -52,12 +53,12 @@ const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
// 3⃣ Konversation anhand In-Reply-To suchen // 3⃣ Konversation anhand In-Reply-To suchen
let conversationId: string | null = null let conversationId: string | null = null
if (in_reply_to) { if (in_reply_to) {
const { data: msg } = await server.supabase const msg = await server.db
.from('helpdesk_messages') .select({ conversationId: helpdesk_messages.conversationId })
.select('conversation_id') .from(helpdesk_messages)
.eq('external_message_id', in_reply_to) .where(eq(helpdesk_messages.externalMessageId, in_reply_to))
.maybeSingle() .limit(1)
conversationId = msg?.conversation_id || null conversationId = msg[0]?.conversationId || null
} }
// 4⃣ Neue Konversation anlegen falls keine existiert // 4⃣ Neue Konversation anlegen falls keine existiert
@@ -73,12 +74,12 @@ const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
}) })
conversationId = conversation.id conversationId = conversation.id
} else { } else {
const { data } = await server.supabase const rows = await server.db
.from('helpdesk_conversations') .select()
.select('*') .from(helpdesk_conversations)
.eq('id', conversationId) .where(eq(helpdesk_conversations.id, conversationId))
.single() .limit(1)
conversation = data conversation = rows[0]
} }
// 5⃣ Nachricht speichern // 5⃣ Nachricht speichern
@@ -96,7 +97,7 @@ const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
return res.status(201).send({ return res.status(201).send({
success: true, success: true,
conversation_id: conversationId, conversation_id: conversationId,
ticket_number: conversation.ticket_number, ticket_number: conversation?.ticket_number || conversation?.ticketNumber,
}) })
}) })
} }

View File

@@ -3,70 +3,9 @@ import { FastifyPluginAsync } from 'fastify'
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js' import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js' import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js' import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
import { findCustomerOrContactByEmailOrDomain } from "../utils/helpers";
/** import { eq } from "drizzle-orm";
* Öffentliche Route zum Empfang eingehender Kontaktformular-Nachrichten. import { helpdesk_channel_instances } from "../../db/schema";
* Authentifizierung: über `public_token` aus helpdesk_channel_instances
*/
function extractDomain(email) {
if (!email) return null
const parts = email.split("@")
return parts.length === 2 ? parts[1].toLowerCase() : null
}
async function findCustomerOrContactByEmailOrDomain(server,fromMail, tenantId) {
const sender = fromMail
const senderDomain = extractDomain(sender)
if (!senderDomain) return null
// 1⃣ Direkter Match über contacts
const { data: contactMatch } = await server.supabase
.from("contacts")
.select("id, customer")
.eq("email", sender)
.eq("tenant", tenantId)
.maybeSingle()
if (contactMatch?.customer_id) return {
customer: contactMatch.customer,
contact: contactMatch.id
}
// 2⃣ Kunden laden, bei denen E-Mail oder Rechnungsmail passt
const { data: customers, error } = await server.supabase
.from("customers")
.select("id, infoData")
.eq("tenant", tenantId)
if (error) {
console.error(`[Helpdesk] Fehler beim Laden der Kunden:`, error.message)
return null
}
// 3⃣ Durch Kunden iterieren und prüfen
for (const c of customers || []) {
const info = c.infoData || {}
const email = info.email?.toLowerCase()
const invoiceEmail = info.invoiceEmail?.toLowerCase()
const emailDomain = extractDomain(email)
const invoiceDomain = extractDomain(invoiceEmail)
// exakter Match oder Domain-Match
if (
sender === email ||
sender === invoiceEmail ||
senderDomain === emailDomain ||
senderDomain === invoiceDomain
) {
return {customer: c.id, contact:null}
}
}
return null
}
const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => { const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => {
// Öffentliche POST-Route // Öffentliche POST-Route
@@ -85,17 +24,18 @@ const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => {
} }
// 1⃣ Kanalinstanz anhand des Tokens ermitteln // 1⃣ Kanalinstanz anhand des Tokens ermitteln
const { data: channel, error: channelError } = await server.supabase const channels = await server.db
.from('helpdesk_channel_instances') .select()
.select('*') .from(helpdesk_channel_instances)
.eq('public_token', public_token) .where(eq(helpdesk_channel_instances.publicToken, public_token))
.single() .limit(1)
const channel = channels[0]
if (channelError || !channel) { if (!channel) {
return res.status(404).send({ error: 'Invalid channel token' }) return res.status(404).send({ error: 'Invalid channel token' })
} }
const tenant_id = channel.tenant_id const tenant_id = channel.tenantId
const channel_instance_id = channel.id const channel_instance_id = channel.id
// @ts-ignore // @ts-ignore

View File

@@ -5,6 +5,13 @@ import { addMessage, getMessages } from '../modules/helpdesk/helpdesk.message.se
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js' import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
import {decrypt, encrypt} from "../utils/crypt"; import {decrypt, encrypt} from "../utils/crypt";
import nodemailer from "nodemailer" import nodemailer from "nodemailer"
import { eq } from "drizzle-orm";
import {
helpdesk_channel_instances,
helpdesk_contacts,
helpdesk_conversations,
helpdesk_messages,
} from "../../db/schema";
const helpdeskRoutes: FastifyPluginAsync = async (server) => { const helpdeskRoutes: FastifyPluginAsync = async (server) => {
// 📩 1. Liste aller Konversationen // 📩 1. Liste aller Konversationen
@@ -58,15 +65,30 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
const tenant_id = req.user?.tenant_id const tenant_id = req.user?.tenant_id
const {id: conversation_id} = req.params as {id: string} const {id: conversation_id} = req.params as {id: string}
const { data, error } = await server.supabase const rows = await server.db
.from('helpdesk_conversations') .select({
.select('*, helpdesk_contacts(*)') conversation: helpdesk_conversations,
.eq('tenant_id', tenant_id) contact: helpdesk_contacts
.eq('id', conversation_id) })
.single() .from(helpdesk_conversations)
.leftJoin(helpdesk_contacts, eq(helpdesk_contacts.id, helpdesk_conversations.contactId))
.where(eq(helpdesk_conversations.id, conversation_id))
if (error) return res.status(404).send({ error: 'Conversation not found' }) const data = rows[0]
return res.send(data) if (!data || data.conversation.tenantId !== tenant_id) return res.status(404).send({ error: 'Conversation not found' })
return res.send({
...data.conversation,
channel_instance_id: data.conversation.channelInstanceId,
contact_id: data.conversation.contactId,
contact_person_id: data.conversation.contactPersonId,
created_at: data.conversation.createdAt,
customer_id: data.conversation.customerId,
last_message_at: data.conversation.lastMessageAt,
tenant_id: data.conversation.tenantId,
ticket_number: data.conversation.ticketNumber,
helpdesk_contacts: data.contact,
})
}) })
// 🔄 4. Konversation Status ändern // 🔄 4. Konversation Status ändern
@@ -181,36 +203,39 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
safeConfig.smtp.pass = encrypt(safeConfig.smtp.pass) safeConfig.smtp.pass = encrypt(safeConfig.smtp.pass)
} }
// Speichern in Supabase const inserted = await server.db
const { data, error } = await server.supabase .insert(helpdesk_channel_instances)
.from("helpdesk_channel_instances") .values({
.insert({ tenantId: tenant_id,
tenant_id, typeId: type_id,
type_id,
name, name,
config: safeConfig, config: safeConfig,
is_active, isActive: is_active,
}) })
.select() .returning()
.single()
if (error) throw error const data = inserted[0]
if (!data) throw new Error("Konnte Channel nicht erstellen")
const responseConfig: any = data.config
// sensible Felder aus Response entfernen // sensible Felder aus Response entfernen
if (data.config?.imap) { if (responseConfig?.imap) {
delete data.config.imap.host delete responseConfig.imap.host
delete data.config.imap.user delete responseConfig.imap.user
delete data.config.imap.pass delete responseConfig.imap.pass
} }
if (data.config?.smtp) { if (responseConfig?.smtp) {
delete data.config.smtp.host delete responseConfig.smtp.host
delete data.config.smtp.user delete responseConfig.smtp.user
delete data.config.smtp.pass delete responseConfig.smtp.pass
} }
reply.send({ reply.send({
message: "E-Mail-Channel erfolgreich erstellt", message: "E-Mail-Channel erfolgreich erstellt",
channel: data, channel: {
...data,
config: responseConfig
},
}) })
} catch (err) { } catch (err) {
console.error("Fehler bei Channel-Erstellung:", err) console.error("Fehler bei Channel-Erstellung:", err)
@@ -234,29 +259,29 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
const { text } = req.body as { text: string } const { text } = req.body as { text: string }
// 🔹 Konversation inkl. Channel + Kontakt laden // 🔹 Konversation inkl. Channel + Kontakt laden
const { data: conv, error: convErr } = await server.supabase const rows = await server.db
.from("helpdesk_conversations") .select({
.select(` conversation: helpdesk_conversations,
id, contact: helpdesk_contacts,
tenant_id, channel: helpdesk_channel_instances,
subject, })
channel_instance_id, .from(helpdesk_conversations)
helpdesk_contacts(email), .leftJoin(helpdesk_contacts, eq(helpdesk_contacts.id, helpdesk_conversations.contactId))
helpdesk_channel_instances(config, name), .leftJoin(helpdesk_channel_instances, eq(helpdesk_channel_instances.id, helpdesk_conversations.channelInstanceId))
ticket_number .where(eq(helpdesk_conversations.id, conversationId))
`) .limit(1)
.eq("id", conversationId)
.single() const conv = rows[0]
console.log(conv) console.log(conv)
if (convErr || !conv) { if (!conv) {
reply.status(404).send({ error: "Konversation nicht gefunden" }) reply.status(404).send({ error: "Konversation nicht gefunden" })
return return
} }
const contact = conv.helpdesk_contacts as unknown as {email: string} const contact = conv.contact as unknown as {email: string}
const channel = conv.helpdesk_channel_instances as unknown as {name: string} const channel = conv.channel as unknown as {name: string, config: any}
console.log(contact) console.log(contact)
if (!contact?.email) { if (!contact?.email) {
@@ -288,7 +313,7 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
const mailOptions = { const mailOptions = {
from: `"${channel?.name}" <${user}>`, from: `"${channel?.name}" <${user}>`,
to: contact.email, to: contact.email,
subject: `${conv.ticket_number} | ${conv.subject}` || `${conv.ticket_number} | Antwort vom FEDEO Helpdesk`, subject: `${conv.conversation.ticketNumber} | ${conv.conversation.subject}` || `${conv.conversation.ticketNumber} | Antwort vom FEDEO Helpdesk`,
text, text,
} }
@@ -296,24 +321,22 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
console.log(`[Helpdesk SMTP] Gesendet an ${contact.email}: ${info.messageId}`) console.log(`[Helpdesk SMTP] Gesendet an ${contact.email}: ${info.messageId}`)
// 💾 Nachricht speichern // 💾 Nachricht speichern
const { error: insertErr } = await server.supabase await server.db
.from("helpdesk_messages") .insert(helpdesk_messages)
.insert({ .values({
tenant_id: conv.tenant_id, tenantId: conv.conversation.tenantId,
conversation_id: conversationId, conversationId: conversationId,
direction: "outgoing", direction: "outgoing",
payload: { type: "text", text }, payload: { type: "text", text },
external_message_id: info.messageId, externalMessageId: info.messageId,
received_at: new Date().toISOString(), receivedAt: new Date(),
}) })
if (insertErr) throw insertErr
// 🔁 Konversation aktualisieren // 🔁 Konversation aktualisieren
await server.supabase await server.db
.from("helpdesk_conversations") .update(helpdesk_conversations)
.update({ last_message_at: new Date().toISOString() }) .set({ lastMessageAt: new Date() })
.eq("id", conversationId) .where(eq(helpdesk_conversations.id, conversationId))
reply.send({ reply.send({
message: "E-Mail erfolgreich gesendet", message: "E-Mail erfolgreich gesendet",

View File

@@ -1,12 +1,34 @@
// src/routes/resources/history.ts // src/routes/resources/history.ts
import { FastifyInstance } from "fastify"; import { FastifyInstance } from "fastify";
import { and, asc, eq, inArray } from "drizzle-orm";
import { authProfiles, historyitems } from "../../db/schema";
const columnMap: Record<string, string> = { const columnMap: Record<string, any> = {
customers: historyitems.customer,
vendors: historyitems.vendor,
projects: historyitems.project,
plants: historyitems.plant,
contacts: historyitems.contact,
tasks: historyitems.task,
vehicles: historyitems.vehicle,
events: historyitems.event,
files: historyitems.file,
products: historyitems.product,
inventoryitems: historyitems.inventoryitem,
inventoryitemgroups: historyitems.inventoryitemgroup,
checks: historyitems.check,
costcentres: historyitems.costcentre,
ownaccounts: historyitems.ownaccount,
documentboxes: historyitems.documentbox,
hourrates: historyitems.hourrate,
services: historyitems.service,
};
const insertFieldMap: Record<string, string> = {
customers: "customer", customers: "customer",
vendors: "vendor", vendors: "vendor",
projects: "project", projects: "project",
plants: "plant", plants: "plant",
contracts: "contract",
contacts: "contact", contacts: "contact",
tasks: "task", tasks: "task",
vehicles: "vehicle", vehicles: "vehicle",
@@ -15,15 +37,18 @@ const columnMap: Record<string, string> = {
products: "product", products: "product",
inventoryitems: "inventoryitem", inventoryitems: "inventoryitem",
inventoryitemgroups: "inventoryitemgroup", inventoryitemgroups: "inventoryitemgroup",
absencerequests: "absencerequest",
checks: "check", checks: "check",
costcentres: "costcentre", costcentres: "costcentre",
ownaccounts: "ownaccount", ownaccounts: "ownaccount",
documentboxes: "documentbox", documentboxes: "documentbox",
hourrates: "hourrate", hourrates: "hourrate",
services: "service", services: "service",
roles: "role", }
};
const parseId = (value: string) => {
if (/^\d+$/.test(value)) return Number(value)
return value
}
export default async function resourceHistoryRoutes(server: FastifyInstance) { export default async function resourceHistoryRoutes(server: FastifyInstance) {
server.get<{ server.get<{
@@ -49,29 +74,36 @@ export default async function resourceHistoryRoutes(server: FastifyInstance) {
return reply.code(400).send({ error: `History not supported for resource '${resource}'` }); return reply.code(400).send({ error: `History not supported for resource '${resource}'` });
} }
const { data, error } = await server.supabase const data = await server.db
.from("historyitems") .select()
.select("*") .from(historyitems)
.eq(column, id) .where(eq(column, parseId(id)))
.order("created_at", { ascending: true }); .orderBy(asc(historyitems.createdAt));
if (error) { const userIds = Array.from(
server.log.error(error); new Set(data.map((item) => item.createdBy).filter(Boolean))
return reply.code(500).send({ error: "Failed to fetch history" }); ) as string[]
}
const {data:users, error:usersError} = await server.supabase const profiles = userIds.length > 0
.from("auth_users") ? await server.db
.select("*, auth_profiles(*), tenants!auth_tenant_users(*)") .select()
.from(authProfiles)
.where(and(
eq(authProfiles.tenant_id, req.user?.tenant_id),
inArray(authProfiles.user_id, userIds)
))
: []
const filteredUsers = (users ||[]).filter(i => i.tenants.find((t:any) => t.id === req.user?.tenant_id)) const profileByUserId = new Map(
profiles.map((profile) => [profile.user_id, profile])
)
const dataCombined = data.map(historyitem => { const dataCombined = data.map((historyitem) => ({
return {
...historyitem, ...historyitem,
created_by_profile: filteredUsers.find(i => i.id === historyitem.created_by) ? filteredUsers.find(i => i.id === historyitem.created_by).auth_profiles[0] : null created_at: historyitem.createdAt,
} created_by: historyitem.createdBy,
}) created_by_profile: historyitem.createdBy ? profileByUserId.get(historyitem.createdBy) || null : null,
}))
@@ -128,29 +160,33 @@ export default async function resourceHistoryRoutes(server: FastifyInstance) {
const userId = (req.user as any)?.user_id; const userId = (req.user as any)?.user_id;
const fkField = columnMap[resource]; const fkField = insertFieldMap[resource];
if (!fkField) { if (!fkField) {
return reply.code(400).send({ error: `Unknown resource: ${resource}` }); return reply.code(400).send({ error: `Unknown resource: ${resource}` });
} }
const { data, error } = await server.supabase const inserted = await server.db
.from("historyitems") .insert(historyitems)
.insert({ .values({
text, text,
[fkField]: id, [fkField]: parseId(id),
oldVal: old_val || null, oldVal: old_val || null,
newVal: new_val || null, newVal: new_val || null,
config: config || null, config: config || null,
tenant: (req.user as any)?.tenant_id, tenant: (req.user as any)?.tenant_id,
created_by: userId createdBy: userId
}) })
.select() .returning()
.single();
if (error) { const data = inserted[0]
return reply.code(500).send({ error: error.message }); if (!data) {
return reply.code(500).send({ error: "Failed to create history entry" });
} }
return reply.code(201).send(data); return reply.code(201).send({
...data,
created_at: data.createdAt,
created_by: data.createdBy
});
}); });
} }

View File

@@ -1,21 +1,22 @@
// routes/notifications.routes.ts // routes/notifications.routes.ts
import { FastifyInstance } from 'fastify'; import { FastifyInstance } from 'fastify';
import { NotificationService, UserDirectory } from '../modules/notification.service'; import { NotificationService, UserDirectory } from '../modules/notification.service';
import { eq } from "drizzle-orm";
import { authUsers } from "../../db/schema";
// Beispiel: E-Mail aus eigener User-Tabelle laden // Beispiel: E-Mail aus eigener User-Tabelle laden
const getUserDirectory: UserDirectory = async (server:FastifyInstance, userId, tenantId) => { const getUserDirectory: UserDirectory = async (server:FastifyInstance, userId, tenantId) => {
const { data, error } = await server.supabase const rows = await server.db
.from('auth_users') .select({ email: authUsers.email })
.select('email') .from(authUsers)
.eq('id', userId) .where(eq(authUsers.id, userId))
.maybeSingle(); .limit(1)
if (error || !data) return null; const data = rows[0]
if (!data) return null;
return { email: data.email }; return { email: data.email };
}; };
export default async function notificationsRoutes(server: FastifyInstance) { export default async function notificationsRoutes(server: FastifyInstance) {
// wichtig: server.supabase ist über app verfügbar
const svc = new NotificationService(server, getUserDirectory); const svc = new NotificationService(server, getUserDirectory);
server.post('/notifications/trigger', async (req, reply) => { server.post('/notifications/trigger', async (req, reply) => {

View File

@@ -35,7 +35,7 @@ export default async function resourceRoutesSpecial(server: FastifyInstance) {
} }
// --------------------------------------- // ---------------------------------------
// 📌 SELECT: wir ignorieren select string (wie Supabase) // 📌 SELECT: select-string wird in dieser Route bewusst ignoriert
// Drizzle kann kein dynamisches Select aus String! // Drizzle kann kein dynamisches Select aus String!
// Wir geben IMMER alle Spalten zurück → kompatibel zum Frontend // Wir geben IMMER alle Spalten zurück → kompatibel zum Frontend
// --------------------------------------- // ---------------------------------------

View File

@@ -1,5 +1,7 @@
import { FastifyInstance } from 'fastify' import { FastifyInstance } from 'fastify'
import { StaffTimeEntryConnect } from '../../types/staff' import { StaffTimeEntryConnect } from '../../types/staff'
import { asc, eq } from "drizzle-orm";
import { stafftimenetryconnects } from "../../../db/schema";
export default async function staffTimeConnectRoutes(server: FastifyInstance) { export default async function staffTimeConnectRoutes(server: FastifyInstance) {
@@ -8,16 +10,21 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
'/staff/time/:id/connects', '/staff/time/:id/connects',
async (req, reply) => { async (req, reply) => {
const { id } = req.params const { id } = req.params
const { started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes } = req.body const { started_at, stopped_at, project_id, notes } = req.body
const parsedProjectId = project_id ? Number(project_id) : null
const { data, error } = await server.supabase const data = await server.db
.from('staff_time_entry_connects') .insert(stafftimenetryconnects)
.insert([{ time_entry_id: id, started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes }]) .values({
.select() stafftimeentry: id,
.maybeSingle() started_at: new Date(started_at),
stopped_at: new Date(stopped_at),
project_id: parsedProjectId,
notes
})
.returning()
if (error) return reply.code(400).send({ error: error.message }) return reply.send(data[0])
return reply.send(data)
} }
) )
@@ -26,13 +33,12 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
'/staff/time/:id/connects', '/staff/time/:id/connects',
async (req, reply) => { async (req, reply) => {
const { id } = req.params const { id } = req.params
const { data, error } = await server.supabase const data = await server.db
.from('staff_time_entry_connects') .select()
.select('*') .from(stafftimenetryconnects)
.eq('time_entry_id', id) .where(eq(stafftimenetryconnects.stafftimeentry, id))
.order('started_at', { ascending: true }) .orderBy(asc(stafftimenetryconnects.started_at))
if (error) return reply.code(400).send({ error: error.message })
return reply.send(data) return reply.send(data)
} }
) )
@@ -42,15 +48,20 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
'/staff/time/connects/:connectId', '/staff/time/connects/:connectId',
async (req, reply) => { async (req, reply) => {
const { connectId } = req.params const { connectId } = req.params
const { data, error } = await server.supabase const patchData = { ...req.body } as any
.from('staff_time_entry_connects') if (patchData.started_at) patchData.started_at = new Date(patchData.started_at)
.update({ ...req.body, updated_at: new Date().toISOString() }) if (patchData.stopped_at) patchData.stopped_at = new Date(patchData.stopped_at)
.eq('id', connectId) if (patchData.project_id !== undefined) {
.select() patchData.project_id = patchData.project_id ? Number(patchData.project_id) : null
.maybeSingle() }
if (error) return reply.code(400).send({ error: error.message }) const data = await server.db
return reply.send(data) .update(stafftimenetryconnects)
.set({ ...patchData, updated_at: new Date() })
.where(eq(stafftimenetryconnects.id, connectId))
.returning()
return reply.send(data[0])
} }
) )
@@ -59,12 +70,10 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
'/staff/time/connects/:connectId', '/staff/time/connects/:connectId',
async (req, reply) => { async (req, reply) => {
const { connectId } = req.params const { connectId } = req.params
const { error } = await server.supabase await server.db
.from('staff_time_entry_connects') .delete(stafftimenetryconnects)
.delete() .where(eq(stafftimenetryconnects.id, connectId))
.eq('id', connectId)
if (error) return reply.code(400).send({ error: error.message })
return reply.send({ success: true }) return reply.send({ success: true })
} }
) )

View File

@@ -1,12 +1,25 @@
import xmlbuilder from "xmlbuilder"; import xmlbuilder from "xmlbuilder";
import {randomUUID} from "node:crypto"; import {randomUUID} from "node:crypto";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { and, eq, inArray } from "drizzle-orm";
import { createddocuments, tenants } from "../../../db/schema";
export const createSEPAExport = async (server,idsToExport, tenant_id) => { export const createSEPAExport = async (server,idsToExport, tenant_id) => {
const {data,error} = await server.supabase.from("createddocuments").select().eq("tenant", tenant_id).in("id", idsToExport) const data = await server.db
const {data:tenantData,error:tenantError} = await server.supabase.from("tenants").select().eq("id", tenant_id).single() .select()
.from(createddocuments)
.where(and(
eq(createddocuments.tenant, tenant_id),
inArray(createddocuments.id, idsToExport)
))
const tenantRows = await server.db
.select()
.from(tenants)
.where(eq(tenants.id, tenant_id))
.limit(1)
const tenantData = tenantRows[0]
console.log(tenantData) console.log(tenantData)
console.log(tenantError)
console.log(data) console.log(data)

View File

@@ -86,7 +86,7 @@ const InstructionFormat = z.object({
}); });
// --------------------------------------------------------- // ---------------------------------------------------------
// MAIN FUNCTION REPLACES SUPABASE VERSION // MAIN FUNCTION
// --------------------------------------------------------- // ---------------------------------------------------------
export const getInvoiceDataFromGPT = async function ( export const getInvoiceDataFromGPT = async function (
server: FastifyInstance, server: FastifyInstance,

View File

@@ -1,4 +1,5 @@
import { FastifyInstance } from "fastify" import { FastifyInstance } from "fastify"
import { historyitems } from "../../db/schema";
export async function insertHistoryItem( export async function insertHistoryItem(
server: FastifyInstance, server: FastifyInstance,
@@ -63,8 +64,5 @@ export async function insertHistoryItem(
newVal: params.newVal ? JSON.stringify(params.newVal) : null newVal: params.newVal ? JSON.stringify(params.newVal) : null
} }
const { error } = await server.supabase.from("historyitems").insert([entry]) await server.db.insert(historyitems).values(entry as any)
if (error) { // @ts-ignore
console.log(error)
}
} }

View File

@@ -2,6 +2,9 @@ import {PDFDocument, StandardFonts, rgb} from "pdf-lib"
import dayjs from "dayjs" import dayjs from "dayjs"
import {renderAsCurrency, splitStringBySpace} from "./stringRendering"; import {renderAsCurrency, splitStringBySpace} from "./stringRendering";
import {FastifyInstance} from "fastify"; import {FastifyInstance} from "fastify";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { s3 } from "./s3";
import { secrets } from "./secrets";
const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => { const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
/* /*
@@ -25,9 +28,21 @@ const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
const getBackgroundSourceBuffer = async (server:FastifyInstance, path:string) => { const getBackgroundSourceBuffer = async (server:FastifyInstance, path:string) => {
const {data:backgroundPDFData,error:backgroundPDFError} = await server.supabase.storage.from("files").download(path) console.log(path)
return backgroundPDFData.arrayBuffer() const { Body } = await s3.send(
new GetObjectCommand({
Bucket: secrets.S3_BUCKET,
Key: path
})
)
const chunks: Buffer[] = []
for await (const chunk of Body as any) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
}
return Buffer.concat(chunks)
} }
const getDuration = (time) => { const getDuration = (time) => {

View File

@@ -14,8 +14,6 @@ export let secrets = {
PORT: number PORT: number
HOST: string HOST: string
DATABASE_URL: string DATABASE_URL: string
SUPABASE_URL: string
SUPABASE_SERVICE_ROLE_KEY: string
S3_BUCKET: string S3_BUCKET: string
ENCRYPTION_KEY: string ENCRYPTION_KEY: string
MAILER_SMTP_HOST: string MAILER_SMTP_HOST: string

View File

@@ -60,7 +60,6 @@ const router = useRouter()
const createddocuments = ref([]) const createddocuments = ref([])
const setup = async () => { const setup = async () => {
//createddocuments.value = (await useSupabaseSelect("createddocuments")).filter(i => !i.archived)
createddocuments.value = (await useEntities("createddocuments").select()).filter(i => !i.archived) createddocuments.value = (await useEntities("createddocuments").select()).filter(i => !i.archived)
} }
setup() setup()

View File

@@ -1,9 +1,6 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter() const router = useRouter()
const profileStore = useProfileStore()
const props = defineProps({ const props = defineProps({
@@ -28,8 +25,6 @@ const statementallocations = ref([])
const incominginvoices = ref([]) const incominginvoices = ref([])
const setup = async () => { const setup = async () => {
//statementallocations.value = (await supabase.from("statementallocations").select("*, bs_id(*)").eq("account", route.params.id).eq("tenant",profileStore.currentTenant).order("created_at",{ascending: true})).data
//incominginvoices.value = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account == route.params.id))
} }
setup() setup()

View File

@@ -24,7 +24,6 @@ const emit = defineEmits(["updateNeeded"]);
const router = useRouter() const router = useRouter()
const profileStore = useProfileStore() const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const renderedPhases = computed(() => { const renderedPhases = computed(() => {
if(props.topLevelType === "projects" && props.item.phases) { if(props.topLevelType === "projects" && props.item.phases) {
@@ -77,17 +76,6 @@ const changeActivePhase = async (key) => {
const res = await useEntities("projects").update(item.id, {phases:item.phases,active_phase: item.phases.find(i => i.active).label}) const res = await useEntities("projects").update(item.id, {phases:item.phases,active_phase: item.phases.find(i => i.active).label})
//const {error:updateError} = await supabase.from("projects").update({phases: item.phases}).eq("id",item.id)
/*const {error} = await supabase.from("historyitems").insert({
createdBy: profileStore.activeProfile.id,
tenant: profileStore.currentTenant,
text: `Aktive Phase zu "${phaseLabel}" gewechselt`,
project: item.id
})*/
emit("updateNeeded") emit("updateNeeded")
} }

View File

@@ -1,10 +1,5 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const profileStore = useProfileStore()
const props = defineProps({ const props = defineProps({
queryStringData: { queryStringData: {

View File

@@ -57,7 +57,7 @@
const selectedItem = ref(0) const selectedItem = ref(0)
const sort = ref({ const sort = ref({
column: dataType.supabaseSortColumn || "date", column: dataType.sortColumn || "date",
direction: 'desc' direction: 'desc'
}) })

View File

@@ -1,14 +1,17 @@
<script setup> <script setup>
const supabase = useSupabaseClient()
const profileStore = useProfileStore() const profileStore = useProfileStore()
const globalMessages = ref([]) const globalMessages = ref([])
const setup = async () => { const setup = async () => {
let {data} = await supabase.from("globalmessages").select("*, profiles(id)") let data = []
try {
data = await useNuxtApp().$api("/api/resource/globalmessages")
} catch (e) {
data = []
}
data = data.filter((message) => message.profiles.length === 0) data = (data || []).filter((message) => !message.profiles || message.profiles.length === 0)
globalMessages.value = data globalMessages.value = data
@@ -29,10 +32,17 @@ const showMessage = (message) => {
showMessageModal.value = true showMessageModal.value = true
} }
const markMessageAsRead = async () => { const markMessageAsRead = async () => {
await supabase.from("globalmessagesseen").insert({ try {
await useNuxtApp().$api("/api/resource/globalmessagesseen", {
method: "POST",
body: {
profile: profileStore.activeProfile.id, profile: profileStore.activeProfile.id,
message: messageToShow.value.id, message: messageToShow.value.id,
}
}) })
} catch (e) {
// noop: endpoint optional in newer backend versions
}
showMessageModal.value = false showMessageModal.value = false
setup() setup()

View File

@@ -3,10 +3,7 @@ const { isHelpSlideoverOpen } = useDashboard()
const { metaSymbol } = useShortcuts() const { metaSymbol } = useShortcuts()
const shortcuts = ref(false) const shortcuts = ref(false)
const dataStore = useDataStore()
const profileStore = useProfileStore()
const query = ref('') const query = ref('')
const supabase = useSupabaseClient()
const toast = useToast() const toast = useToast()
const router = useRouter() const router = useRouter()

View File

@@ -1,9 +1,6 @@
<script setup> <script setup>
import {formatTimeAgo} from '@vueuse/core' import {formatTimeAgo} from '@vueuse/core'
const supabase = useSupabaseClient()
const { isNotificationsSlideoverOpen } = useDashboard() const { isNotificationsSlideoverOpen } = useDashboard()
watch(isNotificationsSlideoverOpen, async (newVal,oldVal) => { watch(isNotificationsSlideoverOpen, async (newVal,oldVal) => {
@@ -15,18 +12,24 @@ watch(isNotificationsSlideoverOpen, async (newVal,oldVal) => {
const notifications = ref([]) const notifications = ref([])
const setup = async () => { const setup = async () => {
notifications.value = (await supabase.from("notifications").select()).data try {
notifications.value = await useNuxtApp().$api("/api/resource/notifications_items")
} catch (e) {
notifications.value = []
}
} }
setup() setup()
const setNotificationAsRead = async (notification) => { const setNotificationAsRead = async (notification) => {
console.log(notification) try {
await useNuxtApp().$api(`/api/resource/notifications_items/${notification.id}`, {
const {data,error} = await supabase.from("notifications").update({read: true}).eq("id", notification.id) method: "PUT",
body: { readAt: new Date() }
console.log(error) })
} catch (e) {
// noop: endpoint optional in older/newer backend variants
}
setup() setup()
} }
@@ -41,7 +44,7 @@ const setNotificationAsRead = async (notification) => {
class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative" class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative"
@click="setNotificationAsRead(notification)" @click="setNotificationAsRead(notification)"
> >
<UChip color="primary" :show="!notification.read" inset> <UChip color="primary" :show="!notification.read && !notification.readAt" inset>
<UAvatar alt="FEDEO" size="md" /> <UAvatar alt="FEDEO" size="md" />
</UChip> </UChip>
@@ -49,7 +52,7 @@ const setNotificationAsRead = async (notification) => {
<p class="flex items-center justify-between"> <p class="flex items-center justify-between">
<span class="text-gray-900 dark:text-white font-medium">{{notification.title}}</span> <span class="text-gray-900 dark:text-white font-medium">{{notification.title}}</span>
<time :datetime="notification.date" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.created_at))" /> <time :datetime="notification.date || notification.createdAt || notification.created_at" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.createdAt || notification.created_at))" />
</p> </p>
<p class="text-gray-500 dark:text-gray-400"> <p class="text-gray-500 dark:text-gray-400">
{{ notification.message }} {{ notification.message }}

View File

@@ -2,7 +2,6 @@
const toast = useToast() const toast = useToast()
const dataStore = useDataStore() const dataStore = useDataStore()
const supabase = useSupabaseClient()
const modal = useModal() const modal = useModal()
const props = defineProps({ const props = defineProps({
type: { type: {
@@ -35,11 +34,10 @@ const item = ref({})
const setupPage = async () => { const setupPage = async () => {
if(props.mode === "show") { if(props.mode === "show") {
//Load Data for Show //Load Data for Show
item.value = await useEntities(props.type).selectSingle(props.id, dataType.supabaseSelectWithInformation || "*") item.value = await useEntities(props.type).selectSingle(props.id, dataType.selectWithInformation || "*")
} else if(props.mode === "edit") { } else if(props.mode === "edit") {
//Load Data for Edit //Load Data for Edit
const data = JSON.stringify(await useEntities(props.type).selectSingle(props.id)/*(await supabase.from(props.type).select().eq("id", props.id).single()).data*/) const data = JSON.stringify(await useEntities(props.type).selectSingle(props.id))
//await useSupabaseSelectSingle(type, route.params.id)
item.value = data item.value = data
} else if(props.mode === "create") { } else if(props.mode === "create") {
@@ -48,7 +46,7 @@ const setupPage = async () => {
} else if(props.mode === "list") { } else if(props.mode === "list") {
//Load Data for List //Load Data for List
items.value = await useEntities(props.type).select(dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false) items.value = await useEntities(props.type).select(dataType.selectWithInformation || "*", dataType.sortColumn,dataType.sortAscending || false)
} }
loaded.value = true loaded.value = true

View File

@@ -3,11 +3,6 @@
const { isHelpSlideoverOpen } = useDashboard() const { isHelpSlideoverOpen } = useDashboard()
const { isDashboardSearchModalOpen } = useUIState() const { isDashboardSearchModalOpen } = useUIState()
const { metaSymbol } = useShortcuts() const { metaSymbol } = useShortcuts()
const user = useSupabaseUser()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()
const items = computed(() => [ const items = computed(() => [

View File

@@ -7,11 +7,9 @@ const props = defineProps({
} }
}) })
const supabase = useSupabaseClient()
let inventoryitemgroups = await Promise.all(props.row.inventoryitemgroups.map(async (i) => { let inventoryitemgroups = await Promise.all(props.row.inventoryitemgroups.map(async (i) => {
return (await supabase.from("inventoryitemgroups").select("id,name").eq("id",i).single()).data.name const group = await useEntities("inventoryitemgroups").selectSingle(i)
return group?.name
})) }))
</script> </script>

View File

@@ -7,11 +7,9 @@ const props = defineProps({
} }
}) })
const supabase = useSupabaseClient()
let inventoryitems = await Promise.all(props.row.inventoryitems.map(async (i) => { let inventoryitems = await Promise.all(props.row.inventoryitems.map(async (i) => {
return (await supabase.from("inventoryitems").select("id,name").eq("id",i).single()).data.name const item = await useEntities("inventoryitems").selectSingle(i)
return item?.name
})) }))
</script> </script>

View File

@@ -7,11 +7,9 @@ const props = defineProps({
} }
}) })
const supabase = useSupabaseClient()
let vehicles = await Promise.all(props.row.vehicles.map(async (i) => { let vehicles = await Promise.all(props.row.vehicles.map(async (i) => {
return (await supabase.from("vehicles").select("id,licensePlate").eq("id",i).single()).data.licensePlate const vehicle = await useEntities("vehicles").selectSingle(i)
return vehicle?.licensePlate
})) }))
</script> </script>

View File

@@ -1,8 +1,4 @@
<script setup> <script setup>
const supabase = useSupabaseClient()
const profileStore = useProfileStore()
const props = defineProps({ const props = defineProps({
item: { item: {
required: true, required: true,

View File

@@ -8,15 +8,12 @@ let incomeData = ref({})
let expenseData = ref({}) let expenseData = ref({})
const setup = async () => { const setup = async () => {
//let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).eq("state","Gebucht").in('type',['invoices','advanceInvoices','cancellationInvoices'])).data
let incomeRawData = (await useEntities("createddocuments").select()).filter(i => i.state === "Gebucht" && ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type)) let incomeRawData = (await useEntities("createddocuments").select()).filter(i => i.state === "Gebucht" && ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type))
let incomeRawFilteredData = incomeRawData.filter(x => x.state === 'Gebucht' && incomeRawData.find(i => i.linkedDocument && i.linkedDocument.id === x.id && i.type === 'cancellationInvoices') && ['invoices','advanceInvoices'].includes(row.type)) let incomeRawFilteredData = incomeRawData.filter(x => x.state === 'Gebucht' && incomeRawData.find(i => i.linkedDocument && i.linkedDocument.id === x.id && i.type === 'cancellationInvoices') && ['invoices','advanceInvoices'].includes(row.type))
//let expenseRawData =(await supabase.from("incominginvoices").select().eq("tenant",profileStore.currentTenant)).data
let expenseRawData =(await useEntities("incominginvoices").select()) let expenseRawData =(await useEntities("incominginvoices").select())
//let withoutInvoiceRawData = (await supabase.from("statementallocations").select().eq("tenant",profileStore.currentTenant).not("account","is",null)).data
let withoutInvoiceRawData = (await useEntities("statementallocations").select()).filter(i => i.account) let withoutInvoiceRawData = (await useEntities("statementallocations").select()).filter(i => i.account)
let withoutInvoiceRawDataExpenses = [] let withoutInvoiceRawDataExpenses = []

View File

@@ -2,8 +2,8 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
const profileStore = useProfileStore(); const profileStore = useProfileStore();
const supabase = useSupabaseClient()
const toast = useToast() const toast = useToast()
const staffTime = useStaffTime()
const runningTimeInfo = ref({}) const runningTimeInfo = ref({})
@@ -11,12 +11,9 @@ const projects = ref([])
const platform = ref("default") const platform = ref("default")
const setupPage = async () => { const setupPage = async () => {
runningTimeInfo.value = (await supabase.from("times").select().eq("profile", profileStore.activeProfile.id).is("endDate", null).single()).data || {} const rows = await staffTime.list({ user_id: profileStore.activeProfile?.user_id || profileStore.activeProfile?.id })
runningTimeInfo.value = rows.find((r) => !r.stopped_at && r.type === "work") || {}
//projects.value = (await useSupabaseSelect("projects")) projects.value = await useEntities("projects").select("*")
} }
setupPage() setupPage()
@@ -26,47 +23,25 @@ setupPage()
}*/ }*/
const startTime = async () => { const startTime = async () => {
console.log("started") try {
runningTimeInfo.value = { await staffTime.start("Arbeitszeit")
profile: profileStore.activeProfile.id, toast.add({title: "Projektzeit erfolgreich gestartet"})
startDate: dayjs(), await setupPage()
tenant: profileStore.currentTenant, } catch (error) {
state: platform.value === "mobile" ? "In der App gestartet" : "Im Web gestartet",
source: "Dashboard"
}
const {data,error} = await supabase
.from("times")
.insert([runningTimeInfo.value])
.select()
if(error) {
console.log(error) console.log(error)
toast.add({title: "Fehler beim starten der Projektzeit",color:"rose"}) toast.add({title: "Fehler beim starten der Projektzeit",color:"rose"})
} else if(data) {
toast.add({title: "Projektzeit erfolgreich gestartet"})
runningTimeInfo.value = data[0]
//console.log(runningTimeInfo.value)
} }
} }
const stopStartedTime = async () => { const stopStartedTime = async () => {
runningTimeInfo.value.endDate = dayjs() try {
runningTimeInfo.value.state = platform.value === "mobile" ? "In der App gestoppt" : "Im Web gestoppt" await staffTime.stop()
const {error,status} = await supabase
.from("times")
.update(runningTimeInfo.value)
.eq('id',runningTimeInfo.value.id)
if(error) {
console.log(error)
let errorId = await useError().logError(`${status} - ${JSON.stringify(error)}`)
toast.add({title: errorId ? `Fehler beim stoppen der Projektzeit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Projektzeit`,color:"rose"})
} else {
toast.add({title: "Projektzeit erfolgreich gestoppt"}) toast.add({title: "Projektzeit erfolgreich gestoppt"})
runningTimeInfo.value = {} runningTimeInfo.value = {}
} catch (error) {
console.log(error)
let errorId = await useError().logError(`${JSON.stringify(error)}`)
toast.add({title: errorId ? `Fehler beim stoppen der Projektzeit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Projektzeit`,color:"rose"})
} }
} }
@@ -74,9 +49,9 @@ const stopStartedTime = async () => {
</script> </script>
<template> <template>
<div v-if="runningTimeInfo.startDate"> <div v-if="runningTimeInfo.started_at">
<p>Start: {{dayjs(runningTimeInfo.startDate).format("HH:mm")}}</p> <p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p>
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') + ' min' }}</p> <p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p>
<UFormGroup <UFormGroup
class="mt-2" class="mt-2"

View File

@@ -2,14 +2,14 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
const profileStore = useProfileStore(); const profileStore = useProfileStore();
const supabase = useSupabaseClient()
const toast = useToast() const toast = useToast()
const staffTime = useStaffTime()
const runningTimeInfo = ref({}) const runningTimeInfo = ref({})
const setupPage = async () => { const setupPage = async () => {
runningTimeInfo.value = (await supabase.from("workingtimes").select().eq("profile", profileStore.activeProfile.id).is("endDate", null).single()).data || {} const rows = await staffTime.list({ user_id: profileStore.activeProfile?.user_id || profileStore.activeProfile?.id })
console.log(runningTimeInfo.value) runningTimeInfo.value = rows.find((r) => !r.stopped_at && r.type === "work") || {}
} }
setupPage() setupPage()
@@ -19,47 +19,25 @@ setupPage()
}*/ }*/
const startTime = async () => { const startTime = async () => {
console.log("started") try {
runningTimeInfo.value = { await staffTime.start("Arbeitszeit")
profile: profileStore.activeProfile.id, toast.add({title: "Anwesenheit erfolgreich gestartet"})
startDate: dayjs(), await setupPage()
tenant: profileStore.currentTenant, } catch (error) {
state: "Im Web gestartet",
source: "Dashboard"
}
const {data,error} = await supabase
.from("workingtimes")
.insert([runningTimeInfo.value])
.select()
if(error) {
console.log(error) console.log(error)
toast.add({title: "Fehler beim starten der Zeit",color:"rose"}) toast.add({title: "Fehler beim starten der Zeit",color:"rose"})
} else if(data) {
toast.add({title: "Anwesenheit erfolgreich gestartet"})
runningTimeInfo.value = data[0]
console.log(runningTimeInfo.value)
} }
} }
const stopStartedTime = async () => { const stopStartedTime = async () => {
runningTimeInfo.value.endDate = dayjs() try {
runningTimeInfo.value.state = "Im Web gestoppt" await staffTime.stop()
const {error,status} = await supabase
.from("workingtimes")
.update(runningTimeInfo.value)
.eq('id',runningTimeInfo.value.id)
if(error) {
console.log(error)
let errorId = await useError().logError(`${status} - ${JSON.stringify(error)}`)
toast.add({title: errorId ? `Fehler beim stoppen der Anwesenheit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Anwesenheit`,color:"rose"})
} else {
toast.add({title: "Anwesenheit erfolgreich gestoppt"}) toast.add({title: "Anwesenheit erfolgreich gestoppt"})
runningTimeInfo.value = {} runningTimeInfo.value = {}
} catch (error) {
console.log(error)
let errorId = await useError().logError(`${JSON.stringify(error)}`)
toast.add({title: errorId ? `Fehler beim stoppen der Anwesenheit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Anwesenheit`,color:"rose"})
} }
} }
@@ -67,9 +45,9 @@ const stopStartedTime = async () => {
</script> </script>
<template> <template>
<div v-if="runningTimeInfo.startDate"> <div v-if="runningTimeInfo.started_at">
<p>Start: {{dayjs(runningTimeInfo.startDate).format("HH:mm")}}</p> <p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p>
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') + ' min' }}</p> <p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p>
<UFormGroup <UFormGroup
class="mt-2" class="mt-2"

View File

@@ -0,0 +1,247 @@
<template>
<div class="email-editor">
<div v-if="editor" class="toolbar">
<div class="toolbar-group">
<button
class="toolbar-btn"
:class="{ 'is-active': editor.isActive('bold') }"
type="button"
title="Fett"
@click="editor.chain().focus().toggleBold().run()"
>
B
</button>
<button
class="toolbar-btn italic"
:class="{ 'is-active': editor.isActive('italic') }"
type="button"
title="Kursiv"
@click="editor.chain().focus().toggleItalic().run()"
>
I
</button>
<button
class="toolbar-btn line-through"
:class="{ 'is-active': editor.isActive('strike') }"
type="button"
title="Durchgestrichen"
@click="editor.chain().focus().toggleStrike().run()"
>
S
</button>
</div>
<div class="toolbar-group">
<button
class="toolbar-btn"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
type="button"
title="Überschrift"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
>
H2
</button>
<button
class="toolbar-btn"
:class="{ 'is-active': editor.isActive('bulletList') }"
type="button"
title="Liste"
@click="editor.chain().focus().toggleBulletList().run()"
>
</button>
<button
class="toolbar-btn"
:class="{ 'is-active': editor.isActive('orderedList') }"
type="button"
title="Nummerierte Liste"
@click="editor.chain().focus().toggleOrderedList().run()"
>
1.
</button>
</div>
<div class="toolbar-group">
<button
class="toolbar-btn"
:class="{ 'is-active': editor.isActive('link') }"
type="button"
title="Link"
@click="setLink"
>
Link
</button>
<button
class="toolbar-btn"
type="button"
title="Link entfernen"
@click="unsetLink"
>
Unlink
</button>
</div>
</div>
<EditorContent :editor="editor" class="editor-content" />
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, watch } from 'vue'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import Link from '@tiptap/extension-link'
const props = withDefaults(defineProps<{
preloadedContent?: string
}>(), {
preloadedContent: '',
})
const emit = defineEmits<{
updateContent: [payload: { json: Record<string, any>; html: string; text: string }]
}>()
const emitContent = () => {
if (!editor.value) return
emit('updateContent', {
json: editor.value.getJSON(),
html: editor.value.getHTML(),
text: editor.value.getText(),
})
}
const editor = useEditor({
content: props.preloadedContent,
extensions: [
StarterKit,
Placeholder.configure({ placeholder: 'E-Mail Inhalt eingeben...' }),
Link.configure({ openOnClick: false, autolink: true }),
],
editorProps: {
attributes: {
class: 'prosemirror-email',
},
},
onCreate: emitContent,
onUpdate: emitContent,
})
watch(
() => props.preloadedContent,
(value) => {
if (!editor.value) return
if (value === editor.value.getHTML()) return
editor.value.commands.setContent(value || '', false)
}
)
const setLink = () => {
if (!editor.value) return
const previousUrl = editor.value.getAttributes('link').href
const url = window.prompt('URL eingeben:', previousUrl)
if (url === null) return
if (!url.trim()) {
editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
return
}
editor.value.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
}
const unsetLink = () => {
if (!editor.value) return
editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
}
onBeforeUnmount(() => {
editor.value?.destroy()
})
</script>
<style scoped>
.email-editor {
border: 1px solid #69c350;
border-radius: 10px;
background: #fff;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.75rem;
border-bottom: 1px solid #e5e7eb;
}
.toolbar-group {
display: flex;
gap: 0.25rem;
padding-right: 0.5rem;
margin-right: 0.25rem;
border-right: 1px solid #e5e7eb;
}
.toolbar-group:last-child {
border-right: 0;
margin-right: 0;
padding-right: 0;
}
.toolbar-btn {
min-width: 2rem;
height: 2rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
background: #fff;
color: #374151;
padding: 0 0.5rem;
font-size: 0.875rem;
}
.toolbar-btn:hover {
background: #f3f4f6;
}
.toolbar-btn.is-active {
background: #dcfce7;
border-color: #69c350;
color: #14532d;
}
.editor-content {
min-height: 320px;
padding: 1rem;
}
:deep(.prosemirror-email) {
outline: none;
min-height: 280px;
}
:deep(.prosemirror-email p) {
margin: 0.3rem 0;
}
:deep(.prosemirror-email ul) {
list-style-type: disc;
padding-left: 1.5rem;
margin: 0.4rem 0;
}
:deep(.prosemirror-email ol) {
list-style-type: decimal;
padding-left: 1.5rem;
margin: 0.4rem 0;
}
:deep(.prosemirror-email li) {
display: list-item;
margin: 0.2rem 0;
}
:deep(.prosemirror-email a) {
color: #2563eb;
text-decoration: underline;
}
</style>

View File

@@ -32,15 +32,7 @@ const default_data = {
const newProjectDescription = ref(data|| default_data.value); const newProjectDescription = ref(data|| default_data.value);
const saveProjectDescription = async () => { const saveProjectDescription = async () => {
//Update Project Description
/*const {data:updateData,error:updateError} = await supabase
.from("projects")
.update({description: newProjectDescription.value})
.eq('id',currentProject.id)
.select()
console.log(updateData)
console.log(updateError)*/

View File

@@ -1,10 +1,20 @@
<script setup> <script setup>
const supabase = useSupabaseClient()
const profileStore = useProfileStore() const profileStore = useProfileStore()
const workingtimes = ref([]) const workingtimes = ref([])
const setupPage = async () => { const setupPage = async () => {
workingtimes.value = (await supabase.from("workingtimes").select().eq("tenant",profileStore.currentTenant).is("endDate",null)).data const profiles = profileStore.profiles || []
const checks = await Promise.all(profiles.map(async (profile) => {
try {
const spans = await useNuxtApp().$api(`/api/staff/time/spans?targetUserId=${profile.user_id || profile.id}`)
const openSpan = (spans || []).find((s) => !s.endedAt && s.type === "work")
if (openSpan) return { profile: profile.id }
} catch (e) {
return null
}
return null
}))
workingtimes.value = checks.filter(Boolean)
} }
setupPage() setupPage()

View File

@@ -2,8 +2,6 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
const supabase = useSupabaseClient()
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,

View File

@@ -1,25 +0,0 @@
export const useErrorLogging = (resourceType) => {
const supabase = useSupabaseClient()
const toast = useToast()
const profileStore = useProfileStore()
const logError = async (error) => {
let errorData = {
message: error,
tenant: profileStore.currentTenant,
profile: profileStore.activeProfile.id
}
const {data:supabaseData,error:supabaseError} = await supabase.from("errors").insert(errorData).select().single()
if(supabaseError) {
console.error(supabaseError)
} else if(supabaseData) {
return supabaseData.id
}
}
return { logError}
}

View File

@@ -1,6 +1,5 @@
export const useFiles = () => { export const useFiles = () => {
const supabase = useSupabaseClient()
const toast = useToast() const toast = useToast()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -4,7 +4,6 @@ import dayjs from "dayjs";
const baseURL = /*"http://192.168.1.129:3333"*/ /*"http://localhost:3333"*/ "https://functions.fedeo.io" const baseURL = /*"http://192.168.1.129:3333"*/ /*"http://localhost:3333"*/ "https://functions.fedeo.io"
export const useFunctions = () => { export const useFunctions = () => {
const supabase = useSupabaseClient()
const getWorkingTimesEvaluationData = async (user_id, startDate, endDate) => { const getWorkingTimesEvaluationData = async (user_id, startDate, endDate) => {
// Der neue Endpunkt ist /staff/time/evaluation und erwartet die Benutzer-ID als targetUserId Query-Parameter. // Der neue Endpunkt ist /staff/time/evaluation und erwartet die Benutzer-ID als targetUserId Query-Parameter.
@@ -30,26 +29,6 @@ export const useFunctions = () => {
} }
const useCreateTicket = async (subject,message,url,source) => {
const {data:{session:{access_token}}} = await supabase.auth.getSession()
const {data} = await axios({
method: "POST",
url: `${baseURL}/functions/createticket`,
data: {
subject,
message,
source,
url
},
headers: {
Authorization: `Bearer ${access_token}`
}
})
return !!data.ticket_created;
}
const useBankingGenerateLink = async (institutionId) => { const useBankingGenerateLink = async (institutionId) => {
return (await useNuxtApp().$api(`/api/banking/link/${institutionId}`)).link return (await useNuxtApp().$api(`/api/banking/link/${institutionId}`)).link
@@ -81,46 +60,8 @@ export const useFunctions = () => {
const useGetInvoiceData = async (file) => {
const {data:{session:{access_token}}} = await supabase.auth.getSession()
const {data} = await axios({
method: "POST",
url: `${baseURL}/functions/getinvoicedatafromgpt`,
data: {
file
},
headers: {
Authorization: `Bearer ${access_token}`
}
})
console.log(data)
return data
}
const useSendTelegramNotification = async (message) => {
const {data:{session:{access_token}}} = await supabase.auth.getSession()
const {data,error} = await axios({
method: "POST",
url: `${baseURL}/functions/sendtelegramnotification`,
data: {
message: message
},
headers: {
Authorization: `Bearer ${access_token}`
}
})
if(error){
} else {
return true
}
}
const useBankingCheckInstitutions = async (bic) => { const useBankingCheckInstitutions = async (bic) => {

View File

@@ -1,61 +0,0 @@
export const useNumberRange = (resourceType) => {
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const numberRanges = profileStore.ownTenant.numberRanges
const numberRange = numberRanges[resourceType]
const useNextNumber = async () => {
let nextNumber = numberRange.nextNumber
let newNumberRanges = numberRanges
newNumberRanges[resourceType].nextNumber += 1
const {data,error} = await supabase
.from("tenants")
.update({numberRanges: newNumberRanges})
.eq('id',profileStore.currentTenant)
await profileStore.fetchOwnTenant()
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
}
return { useNextNumber}
}
/*export const useNumberRange = (resourceType) => {
const supabase = useSupabaseClient()
const {numberRanges} = storeToRefs(useDataStore())
const {fetchNumberRanges} = useDataStore()
const numberRange = numberRanges.value.find(range => range.resourceType === resourceType)
const useNextNumber = async () => {
let nextNumber = numberRange.nextNumber
const {data,error} = await supabase
.from("numberranges")
.update({nextNumber: nextNumber + 1})
.eq('id',numberRange.id)
fetchNumberRanges()
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
}
return { useNextNumber}
}*/

View File

@@ -1,27 +0,0 @@
import Handlebars from "handlebars";
export const usePrintLabel = async (printServerId,printerName , rawZPL ) => {
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
await supabase.from("printJobs").insert({
tenant: profileStore.currentTenant,
rawContent: rawZPL,
printerName: printerName,
printServer: printServerId
})
}
export const useGenerateZPL = (rawZPL,data) => {
let template = Handlebars.compile(rawZPL)
return template(data)
}

View File

@@ -41,18 +41,23 @@ export const useStaffTime = () => {
* aber wir nutzen dafür besser die createEntry Funktion unten. * aber wir nutzen dafür besser die createEntry Funktion unten.
*/ */
const start = async (description = "Arbeitszeit", time?: string) => { const start = async (description = "Arbeitszeit", time?: string) => {
console.log(auth.user)
await $api('/api/staff/time/event', { await $api('/api/staff/time/event', {
method: 'POST', method: 'POST',
body: { body: {
eventtype: 'work_start', eventtype: 'work_start',
eventtime: time || new Date().toISOString(), // 💡 Fix: Zeit akzeptieren eventtime: time || new Date().toISOString(), // 💡 Fix: Zeit akzeptieren
payload: { description } payload: { description },
user_id: auth.user?.id
} }
}) })
} }
const stop = async () => { const stop = async () => {
await $api('/api/staff/time/event', { method: 'POST', body: { eventtype: 'work_end', eventtime: new Date().toISOString() } }) await $api('/api/staff/time/event', { method: 'POST', body: { eventtype: 'work_end', eventtime: new Date().toISOString(),
user_id: auth.user?.id } })
} }
const submit = async (entry: any) => { const submit = async (entry: any) => {

View File

@@ -1,8 +1,5 @@
export const useSum = () => { export const useSum = () => {
const supabase = useSupabaseClient()
const getIncomingInvoiceSum = (invoice) => { const getIncomingInvoiceSum = (invoice) => {
let sum = 0 let sum = 0
invoice.accounts.forEach(account => { invoice.accounts.forEach(account => {

View File

@@ -7,7 +7,7 @@ export default defineNuxtConfig({
} }
}, },
modules: ['@vite-pwa/nuxt','@pinia/nuxt', '@nuxt/ui', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'], modules: ['@vite-pwa/nuxt','@pinia/nuxt', '@nuxt/ui', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'],
ssr: false, ssr: false,
@@ -34,12 +34,6 @@ export default defineNuxtConfig({
}, },
supabase: {
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo",
url: "https://uwppvcxflrcsibuzsbil.supabase.co",
redirect: false
},
vite: { vite: {
resolve: { resolve: {
dedupe: [ dedupe: [

View File

@@ -12,7 +12,6 @@
"devDependencies": { "devDependencies": {
"@capacitor/cli": "^7.0.0", "@capacitor/cli": "^7.0.0",
"@nuxtjs/leaflet": "^1.2.3", "@nuxtjs/leaflet": "^1.2.3",
"@nuxtjs/supabase": "^1.1.4",
"@vite-pwa/nuxt": "^1.1.0", "@vite-pwa/nuxt": "^1.1.0",
"@vueuse/core": "^14.1.0", "@vueuse/core": "^14.1.0",
"@vueuse/nuxt": "^14.1.0", "@vueuse/nuxt": "^14.1.0",

View File

@@ -143,7 +143,7 @@ const openBankstatements = () => {
</UButton> </UButton>
<UButton <UButton
v-if="itemInfo.createddocument" v-if="itemInfo.createddocument"
@click="router.push(`/createDocument/show/${itemInfo.createddocument}`)" @click="router.push(`/createDocument/show/${itemInfo.createddocument.id}`)"
icon="i-heroicons-link" icon="i-heroicons-link"
variant="outline" variant="outline"
> >

View File

@@ -15,7 +15,7 @@ const showDocument = ref(false)
const uri = ref("") const uri = ref("")
const setupPage = async () => { const setupPage = async () => {
letterheads.value = await useSupabaseSelect("letterheads","*") letterheads.value = await useEntities("letterheads").select("*")
preloadedContent.value = `<p></p><p></p><p></p>` preloadedContent.value = `<p></p><p></p><p></p>`
} }

View File

@@ -1,5 +1,4 @@
<script setup> <script setup>
//TODO: BACKENDCHANGE EMAIL SENDING
const dataStore = useDataStore() const dataStore = useDataStore()
const profileStore = useProfileStore() const profileStore = useProfileStore()
const route = useRoute() const route = useRoute()
@@ -38,7 +37,7 @@ const setupPage = async () => {
if(route.query.to) emailData.value.to = route.query.to if(route.query.to) emailData.value.to = route.query.to
if(route.query.cc) emailData.value.cc = route.query.cc if(route.query.cc) emailData.value.cc = route.query.cc
if(route.query.bcc) emailData.value.bcc = route.query.bcc if(route.query.bcc) emailData.value.bcc = route.query.bcc
if(route.query.subject) emailData.value.to = route.query.subject if(route.query.subject) emailData.value.subject = route.query.subject
if(route.query.loadDocuments) { if(route.query.loadDocuments) {
@@ -285,7 +284,7 @@ const sendEmail = async () => {
</div> </div>
<Tiptap <EmailTiptapEditor
class="mt-3" class="mt-3"
@updateContent="contentChanged" @updateContent="contentChanged"
:preloadedContent="preloadedContent" :preloadedContent="preloadedContent"

View File

@@ -20,7 +20,6 @@ defineShortcuts({
}) })
const dataStore = useDataStore() const dataStore = useDataStore()
const supabase = useSupabaseClient()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
@@ -38,7 +37,7 @@ const openTab = ref(0)
//Functions //Functions
const setupPage = async () => { const setupPage = async () => {
if(mode.value === "show" || mode.value === "edit"){ if(mode.value === "show" || mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("roles",route.params.id,"*") itemInfo.value = await useEntities("roles").selectSingle(route.params.id,"*")
} }
} }

View File

@@ -3,7 +3,7 @@
const items = ref([]) const items = ref([])
const setup = async () => { const setup = async () => {
items.value = await useSupabaseSelect("roles","*") items.value = await useEntities("roles").select("*")
} }
setup() setup()

View File

@@ -1,8 +1,4 @@
<script setup> <script setup>
import axios from "axios"
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const createEMailAddress = ref("") const createEMailAddress = ref("")
const createEMailType = ref("imap") const createEMailType = ref("imap")

View File

@@ -41,7 +41,6 @@ const setupPage = async (sort_column = null, sort_direction = null) => {
console.log(item.value) console.log(item.value)
} else if (mode.value === "list") { } else if (mode.value === "list") {
//Load Data for List //Load Data for List
//items.value = await useEntities(type).select(dataType.supabaseSelectWithInformation, sort_column || dataType.supabaseSortColumn, sort_direction === "asc", true)
items.value = await useEntities(type).select({ items.value = await useEntities(type).select({
filters: {}, filters: {},
sort: [], sort: [],

View File

@@ -64,7 +64,7 @@ const pageLimit = ref(15)
const page = ref(tempStore.pages[type] || 1) const page = ref(tempStore.pages[type] || 1)
const sort = ref({ const sort = ref({
column: dataType.supabaseSortColumn || "created_at", column: dataType.sortColumn || "created_at",
direction: 'desc' direction: 'desc'
}) })
@@ -156,7 +156,7 @@ const setupPage = async () => {
const {data,meta} = await useEntities(type).selectPaginated({ const {data,meta} = await useEntities(type).selectPaginated({
select: dataType.supabaseSelectWithInformation || "*", select: dataType.selectWithInformation || "*",
filters: filters, filters: filters,
sort: [{field: sort.value.column, direction: sort.value.direction}], sort: [{field: sort.value.column, direction: sort.value.direction}],
page: page.value, page: page.value,

View File

@@ -3,7 +3,6 @@
import {useFunctions} from "~/composables/useFunctions.js"; import {useFunctions} from "~/composables/useFunctions.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
const supabase = useSupabaseClient()
const profileStore = useProfileStore() const profileStore = useProfileStore()
const toast = useToast() const toast = useToast()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -10,15 +10,6 @@ const showClosedTickets = ref(false)
const selectedTenant = ref(null) const selectedTenant = ref(null)
const setup = async () => { const setup = async () => {
/*if(profileStore.currentTenant === 5) {
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*), tenant(*)").order("created_at", {ascending: false})).data
} else {
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*)").eq("tenant",profileStore.currentTenant).order("created_at", {ascending: false})).data
}
if(profileStore.currentTenant === 5) {
tenants.value = (await supabase.from("tenants").select().order("id")).data
}*/
tickets.value = await useEntities("tickets").select("*,created_by(*), ticketmessages(*)", "created_at", false) tickets.value = await useEntities("tickets").select("*,created_by(*), ticketmessages(*)", "created_at", false)

View File

@@ -1,500 +0,0 @@
<script setup>
import dayjs from "dayjs";
import VueDatePicker from '@vuepic/vue-datepicker'
import '@vuepic/vue-datepicker/dist/main.css'
import {setPageLayout} from "#app";
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const user = useSupabaseUser()
const toast = useToast()
const timeTypes = profileStore.ownTenant.timeConfig.timeTypes
const timeInfo = ref({
profile: "",
startDate: "",
endDate: null,
notes: null,
project: null,
type: null
})
const filterUser = ref(profileStore.activeProfile.id || "")
const times = ref([])
const runningTimeInfo = ref({})
const showConfigTimeModal = ref(false)
const configTimeMode = ref("create")
const platform = ref("default")
const projects = ref([])
const setup = async () => {
times.value = await useSupabaseSelect("times","*, profile(*), project(id, name)")
projects.value = await useSupabaseSelect("projects","*")
runningTimeInfo.value = (await supabase
.from("times")
.select()
.eq("tenant", profileStore.currentTenant)
.eq("profile", profileStore.activeProfile.id)
.is("endDate",null)
.single()).data
}
setup()
const filteredRows = computed(() => {
//let times = times.value
/*if(dataStore.hasRight('viewTimes')) {
if(filterUser.value !== "") {
times = times.filter(i => i.user === filterUser.value)
}
} else if(dataStore.hasRight('viewOwnTimes')) {
times = times.filter(i => i.user === user.value.id)
} else {
times = []
}*/
return times.value
})
const itemInfo = ref({
user: "",
notes: null,
project: null,
type: null,
state: "Entwurf"
})
const columns = [
{
key:"state",
label: "Status",
},
{
key: "user",
label: "Benutzer",
},
{
key:"startDate",
label:"Start",
},
{
key: "endDate",
label: "Ende",
},
{
key:"type",
label:"Typ",
},
{
key: "project",
label: "Projekt",
},
{
key: "notes",
label: "Notizen",
}
]
const startTime = async () => {
console.log("started")
timeInfo.value.profile = profileStore.activeProfile.id
timeInfo.value.startDate = dayjs()
timeInfo.value.tenant = profileStore.currentTenant
const {data,error} = await supabase
.from("times")
.insert([timeInfo.value])
.select()
if(error) {
console.log(error)
toast.add({title: "Fehler beim starten der Zeit",color:"rose"})
} else if(data) {
toast.add({title: "Zeit erfolgreich gestartet"})
runningTimeInfo.value = data[0]
}
}
const stopStartedTime = async () => {
runningTimeInfo.value.endDate = dayjs()
runningTimeInfo.value.state = "Im Web gestoppt"
const {data,error} = await supabase
.from("times")
.update(runningTimeInfo.value)
.eq('id',runningTimeInfo.value.id)
.select()
if(error) {
console.log(error)
} else {
toast.add({title: "Zeit erfolgreich gestoppt"})
runningTimeInfo.value = null
setup()
}
}
const createTime = async () => {
const {data,error} = await supabase
.from("times")
.insert({...itemInfo.value, tenant: profileStore.currentTenant})
.select()
if(error) {
console.log(error)
} else if(data) {
itemInfo.value = {}
toast.add({title: "Zeit erfolgreich erstellt"})
showConfigTimeModal.value = false
setup()
}
}
const openTime = (row) => {
itemInfo.value = row
itemInfo.value.project = itemInfo.value.project.id
itemInfo.value.profile = itemInfo.value.profile.id
showConfigTimeModal.value = true
configTimeMode.value = "edit"
}
const updateTime = async () => {
let data = itemInfo.value
data.profile = data.profile.id
data.project = data.project.id
const {error} = await supabase
.from("times")
.update(data)
.eq('id',itemInfo.value.id)
if(error) {
console.log(error)
}
toast.add({title: "Zeit erfolgreich gespeichert"})
showConfigTimeModal.value = false
setup()
}
const format = (date) => {
let dateFormat = dayjs(date).format("DD.MM.YY HH:mm")
return `${dateFormat}`;
}
/*const getDuration = (time) => {
const dez = dayjs(time.end).diff(time.start,'hour',true).toFixed(2)
const hours = Math.floor(dez)
const minutes = Math.floor((dez - hours) * 60)
return {
dezimal: dez,
hours: hours,
minutes: minutes,
composed: `${hours}:${minutes}`
}
}*/
const setState = async (newState) => {
itemInfo.value.state = newState
await updateTime()
}
const getSecondInfo = (item) => {
let returnArray = []
if(item.type) returnArray.push(item.type)
if(item.project) returnArray.push(item.project.name)
if(item.notes) returnArray.push(item.notes)
return returnArray
}
</script>
<template>
<UDashboardNavbar title="Projektzeiten">
<template #toggle>
<div v-if="platform === 'mobile'"></div>
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<UButton
@click="startTime"
:disabled="runningTimeInfo "
>
Start
</UButton>
<UButton
@click="stopStartedTime"
:disabled="!runningTimeInfo"
>
Stop
</UButton>
<UButton
v-if="platform !== 'mobile'"
@click="configTimeMode = 'create'; itemInfo = {startDate: new Date(), endDate: new Date()}; showConfigTimeModal = true"
>
Erstellen
</UButton>
<USelectMenu
v-if="platform !== 'mobile'"
:options="profileStore.profiles"
option-attribute="fullName"
value-attribute="id"
v-model="filterUser"
>
<template #label>
{{profileStore.getProfileById(filterUser) ? profileStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
</template>
</UDashboardToolbar>
<div v-if="runningTimeInfo" class="m-3">
Start: {{dayjs(runningTimeInfo.startDate).format("DD.MM.YY HH:mm")}}
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="runningTimeInfo.notes"
/>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
:options="dataStore.projects"
option-attribute="name"
value-attribute="id"
v-model="runningTimeInfo.project"
>
<template #label>
{{ dataStore.projects.find(project => project.id === runningTimeInfo.project) ? dataStore.projects.find(project => project.id === runningTimeInfo.project).name : "Projekt auswählen" }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kategorie:"
>
<USelectMenu
v-model="runningTimeInfo.type"
:options="timeTypes"
option-attribute="label"
value-attribute="label"
>
<template #label>
{{runningTimeInfo.type ? runningTimeInfo.type : "Kategorie auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
</div>
<UModal
v-model="showConfigTimeModal"
>
<UCard>
<template #header>
Projektzeit {{configTimeMode === 'create' ? "erstellen" : "bearbeiten"}}
</template>
<UFormGroup
label="Start:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.startDate ? dayjs(itemInfo.startDate).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.startDate" @close="close" mode="dateTime" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Ende:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.endDate ? dayjs(itemInfo.endDate).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.endDate" @close="close" mode="dateTime" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Benutzer:"
>
<USelectMenu
:options="profileStore.profiles"
v-model="itemInfo.profile"
option-attribute="fullName"
value-attribute="id"
>
<template #label>
{{profileStore.profiles.find(profile => profile.id === itemInfo.profile) ? profileStore.profiles.find(profile => profile.id === itemInfo.profile).fullName : "Benutzer auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
:options="projects"
v-model="itemInfo.project"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
>
<template #label>
{{dataStore.projects.find(project => project.id === itemInfo.project) ? dataStore.projects.find(project => project.id === itemInfo.project).name : "Projekt auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<USelectMenu
v-model="itemInfo.type"
:options="timeTypes"
option-attribute="label"
value-attribute="label"
>
<template #label>
{{itemInfo.type ? itemInfo.type : "Kategorie auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.notes"
/>
</UFormGroup>
<template #footer>
<InputGroup>
<UButton
@click="createTime"
v-if="configTimeMode === 'create'"
>
Erstellen
</UButton>
<UButton
@click="updateTime"
v-else-if="configTimeMode === 'edit'"
>
Speichern
</UButton>
<UButton
@click="setState('Eingereicht')"
v-if="itemInfo.state === 'Entwurf'"
>
Einreichen
</UButton>
</InputGroup>
</template>
</UCard>
</UModal>
<UDashboardPanelContent class="w-full" v-if="platform === 'mobile'">
<a
v-for="item in filteredRows"
class="my-1"
>
<p class="truncate text-left text-primary text-xl">{{dayjs(item.startDate).format("DD.MM.YYYY HH:mm")}} - {{dayjs(item.endDate).format("HH:mm")}}</p>
<p class="text-sm">
<span v-for="(i,index) in getSecondInfo(item)">
{{i}}{{index < getSecondInfo(item).length - 1 ? " - " : ""}}
</span>
</p>
</a>
</UDashboardPanelContent>
<UTable
v-else
class="mt-3"
:columns="columns"
:rows="filteredRows"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
@select="(row) => openTime(row)"
>
<template #state-data="{row}">
<span
v-if="row.state === 'Entwurf'"
class="text-rose-500"
>{{row.state}}</span>
<span
v-if="row.state === 'Eingereicht'"
class="text-cyan-500"
>{{row.state}}</span>
<span
v-if="row.state === 'Bestätigt'"
class="text-primary-500"
>{{row.state}}</span>
</template>
<template #user-data="{row}">
{{row.profile ? row.profile.fullName : "" }}
</template>
<template #startDate-data="{row}">
{{dayjs(row.startDate).format("DD.MM.YY HH:mm")}}
</template>
<template #endDate-data="{row}">
{{dayjs(row.endDate).format("DD.MM.YY HH:mm")}}
</template>
<template #project-data="{row}">
{{row.project ? row.project.name : "" }}
</template>
</UTable>
</template>
<style scoped>
</style>

View File

@@ -1,7 +1,6 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import dayjs from "dayjs" import dayjs from "dayjs"
//import {typeOf} from "uri-js/dist/esnext/util"; //import {typeOf} from "uri-js/dist/esnext/util";
import {useNumberRange} from "~/composables/useNumberRange.js";
import projecttype from "~/components/columnRenderings/projecttype.vue" import projecttype from "~/components/columnRenderings/projecttype.vue"
@@ -57,7 +56,7 @@ export const useDataStore = defineStore('data', () => {
isStandardEntity: true, isStandardEntity: true,
redirect: true, redirect: true,
historyItemHolder: "task", historyItemHolder: "task",
supabaseSelectWithInformation: "*, plant(*), project(*), customer(*)", selectWithInformation: "*, plant(*), project(*), customer(*)",
filters: [ filters: [
{ {
name: "Nur Offene Aufgaben", name: "Nur Offene Aufgaben",
@@ -165,8 +164,8 @@ export const useDataStore = defineStore('data', () => {
redirect:true, redirect:true,
numberRangeHolder: "customerNumber", numberRangeHolder: "customerNumber",
historyItemHolder: "customer", historyItemHolder: "customer",
supabaseSortColumn: "customerNumber", sortColumn: "customerNumber",
supabaseSelectWithInformation: "*, projects(*), plants(*), contracts(*), contacts(*), createddocuments(*, statementallocations(*)), files(*), events(*)", selectWithInformation: "*, projects(*), plants(*), contracts(*), contacts(*), createddocuments(*, statementallocations(*)), files(*), events(*)",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -426,7 +425,7 @@ export const useDataStore = defineStore('data', () => {
isStandardEntity: true, isStandardEntity: true,
redirect:true, redirect:true,
historyItemHolder: "contact", historyItemHolder: "contact",
supabaseSelectWithInformation: "*, customer(*), vendor(*)", selectWithInformation: "*, customer(*), vendor(*)",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -566,7 +565,7 @@ export const useDataStore = defineStore('data', () => {
"Allgemeines", "Allgemeines",
"Abrechnung" "Abrechnung"
], ],
supabaseSelectWithInformation: "*, customer(*), files(*)", selectWithInformation: "*, customer(*), files(*)",
templateColumns: [ templateColumns: [
{ {
key: 'contractNumber', key: 'contractNumber',
@@ -719,9 +718,9 @@ export const useDataStore = defineStore('data', () => {
label: "Abwesenheiten", label: "Abwesenheiten",
labelSingle: "Abwesenheit", labelSingle: "Abwesenheit",
isStandardEntity: true, isStandardEntity: true,
supabaseSortColumn:"startDate", sortColumn:"startDate",
supabaseSortAscending: false, sortAscending: false,
supabaseSelectWithInformation: "*", selectWithInformation: "*",
historyItemHolder: "absencerequest", historyItemHolder: "absencerequest",
redirect:true, redirect:true,
filters:[{ filters:[{
@@ -813,8 +812,8 @@ export const useDataStore = defineStore('data', () => {
isStandardEntity: true, isStandardEntity: true,
redirect:true, redirect:true,
historyItemHolder: "plant", historyItemHolder: "plant",
supabaseSortColumn:"name", sortColumn:"name",
supabaseSelectWithInformation: "*, customer(id,name)", selectWithInformation: "*, customer(id,name)",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -869,7 +868,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Artikel", labelSingle: "Artikel",
isStandardEntity: true, isStandardEntity: true,
redirect:true, redirect:true,
supabaseSelectWithInformation: "*, unit(name)", selectWithInformation: "*, unit(name)",
historyItemHolder: "product", historyItemHolder: "product",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
@@ -996,8 +995,8 @@ export const useDataStore = defineStore('data', () => {
redirect:true, redirect:true,
historyItemHolder: "project", historyItemHolder: "project",
numberRangeHolder: "projectNumber", numberRangeHolder: "projectNumber",
supabaseSelectWithInformation: "*, customer(id,name), plant(id,name), projecttype(name, id), tasks(*, project(id,name), customer(id,name), plant(id,name)), files(*), createddocuments(*, statementallocations(*)), events(*), times(*, profile(id, fullName))", selectWithInformation: "*, customer(id,name), plant(id,name), projecttype(name, id), tasks(*, project(id,name), customer(id,name), plant(id,name)), files(*), createddocuments(*, statementallocations(*)), events(*), times(*, profile(id, fullName))",
supabaseSortColumn: "projectNumber", sortColumn: "projectNumber",
filters: [ filters: [
{ {
name: "Nur Offene Projekte", name: "Nur Offene Projekte",
@@ -1136,8 +1135,8 @@ export const useDataStore = defineStore('data', () => {
isStandardEntity: true, isStandardEntity: true,
redirect:true, redirect:true,
historyItemHolder: "vehicle", historyItemHolder: "vehicle",
supabaseSortColumn:"licensePlate", sortColumn:"licensePlate",
supabaseSelectWithInformation: "*, checks(*), files(*)", selectWithInformation: "*, checks(*), files(*)",
filters:[{ filters:[{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -1243,8 +1242,8 @@ export const useDataStore = defineStore('data', () => {
redirect:true, redirect:true,
numberRangeHolder: "vendorNumber", numberRangeHolder: "vendorNumber",
historyItemHolder: "vendor", historyItemHolder: "vendor",
supabaseSortColumn: "vendorNumber", sortColumn: "vendorNumber",
supabaseSelectWithInformation: "*, contacts(*)", selectWithInformation: "*, contacts(*)",
filters: [{ filters: [{
name: "SEPA nicht erteilt", name: "SEPA nicht erteilt",
"filterFunction": function (row) { "filterFunction": function (row) {
@@ -1395,8 +1394,8 @@ export const useDataStore = defineStore('data', () => {
label: "Lagerplätze", label: "Lagerplätze",
labelSingle: "Lagerplatz", labelSingle: "Lagerplatz",
isStandardEntity: true, isStandardEntity: true,
supabaseSelectWithInformation: "*, files(*)", selectWithInformation: "*, files(*)",
supabaseSortColumn: "spaceNumber", sortColumn: "spaceNumber",
redirect: true, redirect: true,
numberRangeHolder: "spaceNumber", numberRangeHolder: "spaceNumber",
historyItemHolder: "space", historyItemHolder: "space",
@@ -1528,7 +1527,7 @@ export const useDataStore = defineStore('data', () => {
isArchivable: true, isArchivable: true,
label: "Dokumente", label: "Dokumente",
labelSingle: "Dokument", labelSingle: "Dokument",
supabaseSelectWithInformation: "*, files(*), statementallocations(*)", selectWithInformation: "*, files(*), statementallocations(*)",
filters: [ filters: [
{ {
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
@@ -1565,13 +1564,13 @@ export const useDataStore = defineStore('data', () => {
isArchivable: true, isArchivable: true,
label: "Dateien", label: "Dateien",
labelSingle: "Datei", labelSingle: "Datei",
supabaseSelectWithInformation: "*", selectWithInformation: "*",
}, },
folders: { folders: {
isArchivable: true, isArchivable: true,
label: "Ordner", label: "Ordner",
labelSingle: "Ordner", labelSingle: "Ordner",
supabaseSelectWithInformation: "*", selectWithInformation: "*",
}, },
incominginvoices: { incominginvoices: {
label: "Eingangsrechnungen", label: "Eingangsrechnungen",
@@ -1637,7 +1636,7 @@ export const useDataStore = defineStore('data', () => {
label: "Inventarartikel", label: "Inventarartikel",
labelSingle: "Inventarartikel", labelSingle: "Inventarartikel",
isStandardEntity: true, isStandardEntity: true,
supabaseSelectWithInformation: "*, files(*), vendor(id,name), currentSpace(id,name)", selectWithInformation: "*, files(*), vendor(id,name), currentSpace(id,name)",
redirect: true, redirect: true,
numberRangeHolder: "articleNumber", numberRangeHolder: "articleNumber",
historyItemHolder: "inventoryitem", historyItemHolder: "inventoryitem",
@@ -1789,7 +1788,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Inventarartikelgruppe", labelSingle: "Inventarartikelgruppe",
isStandardEntity: true, isStandardEntity: true,
historyItemHolder: "inventoryitemgroup", historyItemHolder: "inventoryitemgroup",
supabaseSelectWithInformation: "*", selectWithInformation: "*",
redirect: true, redirect: true,
filters:[{ filters:[{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
@@ -1857,7 +1856,7 @@ export const useDataStore = defineStore('data', () => {
label: "Dokumentenboxen", label: "Dokumentenboxen",
labelSingle: "Dokumentenbox", labelSingle: "Dokumentenbox",
isStandardEntity: true, isStandardEntity: true,
supabaseSelectWithInformation: "*, space(*), files(*)", selectWithInformation: "*, space(*), files(*)",
redirect: true, redirect: true,
numberRangeHolder: "key", numberRangeHolder: "key",
historyItemHolder: "documentbox", historyItemHolder: "documentbox",
@@ -1922,7 +1921,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Leistung", labelSingle: "Leistung",
isStandardEntity: true, isStandardEntity: true,
redirect: true, redirect: true,
supabaseSelectWithInformation: "*, unit(*)", selectWithInformation: "*, unit(*)",
historyItemHolder: "service", historyItemHolder: "service",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
@@ -2058,7 +2057,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Stundensatz", labelSingle: "Stundensatz",
isStandardEntity: true, isStandardEntity: true,
redirect: true, redirect: true,
supabaseSelectWithInformation: "*", selectWithInformation: "*",
historyItemHolder: "hourrate", historyItemHolder: "hourrate",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
@@ -2109,7 +2108,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Termin", labelSingle: "Termin",
isStandardEntity: true, isStandardEntity: true,
historyItemHolder: "event", historyItemHolder: "event",
supabaseSelectWithInformation: "*, project(id,name), customer(*)", selectWithInformation: "*, project(id,name), customer(*)",
redirect: true, redirect: true,
filters:[{ filters:[{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
@@ -2261,8 +2260,8 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Artikelkategorie", labelSingle: "Artikelkategorie",
isStandardEntity: true, isStandardEntity: true,
redirect: true, redirect: true,
supabaseSortColumn: "name", sortColumn: "name",
supabaseSelectWithInformation: "*", selectWithInformation: "*",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -2303,8 +2302,8 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Leistungskategorie", labelSingle: "Leistungskategorie",
isStandardEntity: true, isStandardEntity: true,
redirect: true, redirect: true,
supabaseSortColumn: "name", sortColumn: "name",
supabaseSelectWithInformation: "*", selectWithInformation: "*",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -2357,7 +2356,7 @@ export const useDataStore = defineStore('data', () => {
label: "Überprüfungen", label: "Überprüfungen",
labelSingle: "Überprüfung", labelSingle: "Überprüfung",
isStandardEntity: true, isStandardEntity: true,
supabaseSelectWithInformation: "*, vehicle(id,licensePlate), profile(id, fullName), inventoryitem(name), files(*)", selectWithInformation: "*, vehicle(id,licensePlate), profile(id, fullName), inventoryitem(name), files(*)",
redirect: true, redirect: true,
historyItemHolder: "check", historyItemHolder: "check",
filters: [{ filters: [{
@@ -2440,8 +2439,8 @@ export const useDataStore = defineStore('data', () => {
redirect:true, redirect:true,
numberRangeHolder: "number", numberRangeHolder: "number",
historyItemHolder: "costcentre", historyItemHolder: "costcentre",
supabaseSortColumn: "number", sortColumn: "number",
supabaseSelectWithInformation: "*, project(*), vehicle(*), inventoryitem(*)", selectWithInformation: "*, project(*), vehicle(*), inventoryitem(*)",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,
@@ -2520,8 +2519,8 @@ export const useDataStore = defineStore('data', () => {
isStandardEntity: true, isStandardEntity: true,
redirect:true, redirect:true,
historyItemHolder: "ownaccount", historyItemHolder: "ownaccount",
supabaseSortColumn: "number", sortColumn: "number",
supabaseSelectWithInformation: "*, statementallocations(*, bs_id(*))", selectWithInformation: "*, statementallocations(*, bs_id(*))",
filters: [{ filters: [{
name: "Archivierte ausblenden", name: "Archivierte ausblenden",
default: true, default: true,

View File

@@ -1,105 +1,70 @@
import {defineStore} from 'pinia' import { defineStore } from "pinia"
import OneSignal from "onesignal-cordova-plugin"; import OneSignal from "onesignal-cordova-plugin"
import {Capacitor} from "@capacitor/core"; import { Capacitor } from "@capacitor/core"
// @ts-ignore
export const useProfileStore = defineStore('profile', () => {
const supabase = useSupabaseClient() export const useProfileStore = defineStore("profile", () => {
const auth = useAuthStore()
const dataStore = useDataStore() const dataStore = useDataStore()
const user = useSupabaseUser()
const toast = useToast() const toast = useToast()
const router = useRouter() const tempStore = useTempStore()
const loaded = ref(false) const loaded = ref(false)
const showProfileSelection = ref(false) const showProfileSelection = ref(false)
const ownTenant = ref({ const ownTenant = ref({
calendarConfig: { calendarConfig: { eventTypes: [] },
eventTypes: [] timeConfig: { timeTypes: [] },
}, tags: { documents: [], products: [] },
timeConfig: {
timeTypes: []
},
tags: {
documents: [] ,
products: []
},
measures: [] measures: []
}) })
const profiles = ref([]) const profiles = ref([])
const ownProfiles = ref([]) const ownProfiles = ref([])
const activeProfile = ref([]) const activeProfile = ref(null)
const tenants = ref([]) const tenants = ref([])
const currentTenant = ref(null) const currentTenant = ref(null)
const syncFromAuth = () => {
async function initializeData (userId) { currentTenant.value = auth.activeTenant || null
activeProfile.value = auth.profile || null
let profileconnections = (await supabase.from("profileconnections").select()).data tenants.value = auth.tenants || []
let profiles = (await supabase.from("profiles").select("*, role(*)")).data
let activeProfileConnection = profileconnections.find(i => i.active)
if(activeProfileConnection) {
if(!false) {
toast.add({title: 'Aktives Profil ausgewählt'})
} }
console.log("Active Profile selected") async function initializeData() {
activeProfile.value = profiles.find(i => i.id === activeProfileConnection.profile_id) await auth.fetchMe()
currentTenant.value = activeProfile.value.tenant syncFromAuth()
if(Capacitor.getPlatform() === "ios") { if (activeProfile.value?.temp_config) {
OneSignal.initialize("1295d5ff-28f8-46a6-9c62-fe5d090016d7"); tempStore.setStoredTempConfig(activeProfile.value.temp_config)
OneSignal.Location.setShared(false)
OneSignal.Notifications.requestPermission();
OneSignal.login(activeProfileConnection.user_id)
} }
if (currentTenant.value) {
await fetchData() await fetchData()
await dataStore.fetchData() await dataStore.fetchData()
if (Capacitor.getPlatform() === "ios" && activeProfile.value?.user_id) {
OneSignal.initialize("1295d5ff-28f8-46a6-9c62-fe5d090016d7")
OneSignal.Location.setShared(false)
OneSignal.Notifications.requestPermission()
OneSignal.login(activeProfile.value.user_id)
}
} else { } else {
toast.add({title: 'Kein aktives Profil', color: 'orange'}) toast.add({ title: "Kein aktiver Tenant", color: "orange" })
await fetchOwnProfiles()
await fetchTenants()
showProfileSelection.value = true showProfileSelection.value = true
} }
} }
async function changeProfile(newActiveProfileId) { async function changeProfile(newActiveTenantId) {
loaded.value = false loaded.value = false
await auth.switchTenant(String(newActiveTenantId))
let profileconnections = (await supabase.from("profileconnections").select()).data
let oldActiveProfileConnection = profileconnections.find(i => i.active)
const {error} = await supabase.from("profileconnections").update({active: true}).eq("profile_id", newActiveProfileId)
if(error) {
console.log(error)
} else {
if(oldActiveProfileConnection){
const {error} = await supabase.from("profileconnections").update({active: false}).eq("profile_id", oldActiveProfileConnection.profile_id)
}
reloadNuxtApp({
path:"/",
ttl: 10000
})
}
} }
async function fetchData() { async function fetchData() {
await fetchOwnProfiles() await fetchOwnProfiles()
await fetchProfiles() await fetchProfiles()
await fetchTenants() await fetchTenants()
await fetchOwnTenant() await fetchOwnTenant()
loaded.value = true loaded.value = true
console.log("Finished Loading")
} }
function clearStore() { function clearStore() {
@@ -111,42 +76,42 @@ export const useProfileStore = defineStore('profile', () => {
} }
async function fetchOwnTenant() { async function fetchOwnTenant() {
ownTenant.value = (await supabase.from("tenants").select().eq('id', currentTenant.value).single()).data syncFromAuth()
ownTenant.value = auth.activeTenantData || ownTenant.value
} }
async function fetchProfiles() { async function fetchProfiles() {
profiles.value = (await supabase.from("profiles").select().eq("tenant",currentTenant.value).order("lastName")).data try {
const res = await useNuxtApp().$api("/api/tenant/profiles")
profiles.value = res?.data || []
} catch (e) {
profiles.value = activeProfile.value ? [activeProfile.value] : []
}
} }
async function fetchOwnProfiles() { async function fetchOwnProfiles() {
let profiles = (await supabase.from("profiles").select().order("tenant")).data ownProfiles.value = profiles.value
let conns = (await supabase.from("profileconnections").select()).data.map(i => i.profile_id)
ownProfiles.value = profiles.filter(i => conns.includes(i.id))
} }
async function fetchTenants() { async function fetchTenants() {
tenants.value = (await supabase.from("tenants").select().order("id",{ascending: true})).data syncFromAuth()
} }
const getOwnProfile = computed(() => { const getOwnProfile = computed(() => {
return profiles.value.find(i => i.id === user.value.id) return activeProfile.value
}) })
const getProfileById = computed(() => (itemId) => { const getProfileById = computed(() => (itemId) => {
return profiles.value.find(item => item.id === itemId) return profiles.value.find((item) => item.id === itemId || item.user_id === itemId)
}) })
return { return {
//General
currentTenant, currentTenant,
loaded, loaded,
showProfileSelection, showProfileSelection,
ownTenant, ownTenant,
initializeData, initializeData,
changeProfile, changeProfile,
//Data
profiles, profiles,
ownProfiles, ownProfiles,
activeProfile, activeProfile,
@@ -157,6 +122,4 @@ export const useProfileStore = defineStore('profile', () => {
getOwnProfile, getOwnProfile,
getProfileById getProfileById
} }
}) })