Files
tickte-system/src/lib/teamApi.js
T
ClaudeandClaude Opus 5.5 00fa229235 Shop Review B I-8: Webshop ohne Zahlung nur mit Zustimmung in Textform aktivieren
Das Textform-Formular (Haken, Datum, Weg, Chat-Vorlage) gilt wie beim Terminplaner auch
fuer den Webshop, wenn das Portal im Feld shop textformNoetig meldet. Kaestchentext und
Chat-Vorlage nennen die Unterlagen des Webshops, die Fassung und die Erklaerungen, die das
Portal im Nachweis speichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:45:31 +00:00

118 lines
6.4 KiB
JavaScript

import { account } from './appwrite'
import { createFinanceRequest } from './financeRequest'
import { createJwtCache } from './serverpulsAuth'
import { anleitungPfad, featureBody, entscheidungBody } from './funktionenView'
/**
* Client fuer den Team-Eingang des Kundenportals (/api/team): alle Mitarbeiter, Appwrite-JWT (Bearer).
* Anfrage-Kern (JWT-Cache, Wiederholung bei 401, deutsche Fehlertexte) wie bei den Finanzen: financeRequest.js.
*/
export const PORTAL_URL =
import.meta.env.VITE_PROJECT_ADMIN_URL || 'https://project.webklar.com'
const request = createFinanceRequest({ baseUrl: `${PORTAL_URL}/api/team`, createJWT: () => account.createJWT() })
// Eigenes JWT fuer Anfragen, die nicht ueber request laufen (Hochladen im Chat-Baustein), 10 Minuten gecacht
const jwtCache = createJwtCache({ createJWT: () => account.createJWT() })
const enc = encodeURIComponent
/** { hasAccess, lastLoginAt } */
export const getCustomerPortalStatus = (customerId) => request(`/customers/${enc(customerId)}/portal`)
/** { entries: [{ id, at, staffName, action, detail }] }, neueste zuerst */
export const getCustomerViewLog = (customerId) => request(`/customers/${enc(customerId)}/protokoll`)
/** Einmal-Link (60 s) in die Kundenansicht des Portals */
export async function createKundenansichtLink(customerId) {
const { openUrl } = await request(`/customers/${enc(customerId)}/kundenansicht`, { method: 'POST', body: {} })
return openUrl
}
/** Einmal-Link fuer die Preview eines Projekts */
export async function createPreviewLink({ subdomain, previewUrl }) {
const { openUrl } = await request('/preview-link', { method: 'POST', body: { subdomain, previewUrl } })
return openUrl
}
// --- Chat (Messenger, Reiter "Chat", Zahl in der Sidebar) ---
/** { chats: [{ customerId, customerName, lastText, lastSender, lastAt, unreadFromCustomer }] } */
export const listChats = () => request('/chats')
/** { unread } */
export const getChatUnreadCount = () => request('/chats/unread-count')
/** { messages }; mit since nur neuere (Abfrage im offenen Gespraech) */
export const getChatMessages = (customerId, since = '') =>
request(`/chats/${enc(customerId)}/messages${since ? `?since=${enc(since)}` : ''}`)
/** { message, notice } */
export const sendChatMessage = (customerId, { text, attachmentIds }) =>
request(`/chats/${enc(customerId)}/messages`, { method: 'POST', body: { text, attachmentIds } })
export const markChatRead = (customerId) => request(`/chats/${enc(customerId)}/read`, { method: 'POST', body: {} })
/** Upload-Adresse fuer den Chat-Baustein (POST roh, DELETE <url>/<id>) */
export const chatUploadUrl = (customerId) => `${PORTAL_URL}/api/team/chats/${enc(customerId)}/attachments`
/** Header fuer den Chat-Baustein (Option requestHeaders) */
export async function teamAuthHeaders() {
return { Authorization: `Bearer ${await jwtCache.get()}` }
}
// --- Gutschriften (Uebernahmegutschriften) ---
const query = (params) => {
const q = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '')).toString()
return q ? `?${q}` : ''
}
/** { requests, openCount }; status 'open' (Standard) oder 'all', optional nur ein Kunde */
export const listCredits = ({ status = 'open', customerId } = {}) => request(`/takeover-credits${query({ status, customerId })}`)
export const getCreditOpenCount = () => request('/takeover-credits/open-count')
export const getCredit = (id) => request(`/takeover-credits/${enc(id)}`)
export const calcCredit = (body) => request('/takeover-credits/calc', { method: 'POST', body })
export const decideCredit = (id, body) => request(`/takeover-credits/${enc(id)}/decision`, { method: 'POST', body })
export const applyCredit = (id) => request(`/takeover-credits/${enc(id)}/apply`, { method: 'POST', body: {} })
// --- Abo-Angebote ---
export const listOffers = ({ customerId } = {}) => request(`/offers${query({ customerId })}`)
export const getOfferContext = (customerId) => request(`/offers/context/${enc(customerId)}`)
export const createOffer = (body) => request('/offers', { method: 'POST', body })
export const withdrawOffer = (id) => request(`/offers/${enc(id)}/withdraw`, { method: 'POST', body: {} })
export const notifyOffer = (id) => request(`/offers/${enc(id)}/notify`, { method: 'POST', body: {} })
// --- E-Mail-Speicher ---
export const listStorageTasks = () => request('/storage-tasks')
export const getStorageOpenCount = () => request('/storage-tasks/open-count')
export const markStorageDone = (id) => request(`/storage-tasks/${enc(id)}/done`, { method: 'POST', body: {} })
// --- Funktionen (News-Pinnwand, Terminplaner je Projekt) ---
export const getFunktionen = (customerId) => request(`/funktionen/${enc(customerId)}`)
/**
* Aktivieren (ohne Zahlung) oder Abschalten einer Funktion (key: news | termine | shop); Antwort { projects: [das eine Projekt] }.
* Terminplaner und Webshop im Modus on: beim Aktivieren zusaetzlich textformDate (JJJJ-MM-TT) und textformChannel (chat | email).
*/
export const setFeature = (customerId, key, { projectId, action, note, textformDate, textformChannel }) =>
request(`/funktionen/${enc(customerId)}/${enc(key)}`, { method: 'POST', body: featureBody({ projectId, action, note, textformDate, textformChannel }) })
export const setNewsFeature = (customerId, body) => setFeature(customerId, 'news', body)
/** Einbauanleitung des Terminplaners: { stand, warnungen, abschnitte, text, state, ticketWoid, ticketVersion, hindernis } (Aufbereitung: panelModell). */
export const getTermineAnleitung = (customerId, projectId) => request(anleitungPfad(customerId, projectId))
// --- Webshop (Anfrage des Kunden, Stripe-Stand, Kennzahlen) ---
/** { projects: [{ projectId, projectName, state, anfrage, stripe, geoeffnet, zahlen }] } (Aufbereitung: funktionenView) */
export const getShop = (customerId) => request(`/shop/${enc(customerId)}`)
/** Anfrage annehmen oder ablehnen: body { projectId, entscheidung: 'annehmen' | 'ablehnen', begruendung } => { ok, anfrage, mailFehler? } */
export const entscheideShopAnfrage = (customerId, { projectId, entscheidung, begruendung }) =>
request(`/shop/${enc(customerId)}/anfrage`, { method: 'POST', body: entscheidungBody({ projectId, entscheidung, begruendung }) })
// --- Abo-Status (alle Mitarbeiter) ---
/** { productKey, active, status, storageUnits, startedAt, serviceStart, currentPeriodEnd, cancelAt } */
export const getCustomerAboStatus = (customerId) => request(`/customers/${enc(customerId)}/abo`)