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>
118 lines
6.4 KiB
JavaScript
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`)
|