Initial commit

This commit is contained in:
Ashikagi
2026-03-23 20:49:30 +01:00
commit f5338ea3b2
82 changed files with 11979 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
import { calculateExercise } from '../utils/patCalculations'
import { supabase } from './supabaseClient'
const hydrateExercise = (exercise = {}) => {
const normalized = {
name: exercise.name || '',
soll: exercise.soll ?? 0,
faktor: exercise.faktor ?? 0
}
if (exercise.subA && exercise.subB) {
normalized.subA = exercise.subA
normalized.subB = exercise.subB
if (exercise.subLabels) {
normalized.subLabels = exercise.subLabels
}
} else {
normalized.values = exercise.values || []
}
const { durchschnitt, points } = calculateExercise({
...normalized,
values: normalized.values || []
})
return {
...normalized,
durchschnitt,
points
}
}
const mapSharedAssessment = (row) => ({
id: row.id,
patType: row.pat_type,
datum: row.datum,
name: row.name,
exercises: (row.exercises || []).map(hydrateExercise)
})
const getSingleRow = (data) => (Array.isArray(data) ? data[0] || null : data || null)
const buildShareUrl = (shareToken) => {
if (typeof window === 'undefined') {
return `?share=${shareToken}`
}
const url = new URL(window.location.origin + window.location.pathname)
url.searchParams.set('share', shareToken)
return url.toString()
}
export const createAssessmentShareLink = async (assessment) => {
if (!assessment) {
throw new Error('Kein Test zum Teilen gefunden.')
}
if (!supabase) {
throw new Error('Supabase ist nicht konfiguriert.')
}
const { data, error } = await supabase.rpc('create_or_get_assessment_share', {
p_assessment_id: assessment.id
})
if (error) throw error
const row = getSingleRow(data)
if (!row?.share_token) {
throw new Error('Kein Freigabetoken erhalten.')
}
return {
token: row.share_token,
url: buildShareUrl(row.share_token)
}
}
export const listAssessmentShares = async (assessmentIds = []) => {
if (!supabase) {
throw new Error('Supabase ist nicht konfiguriert.')
}
if (!assessmentIds.length) {
return {}
}
const { data, error } = await supabase
.from('assessment_shares')
.select('assessment_id, share_token')
.in('assessment_id', assessmentIds)
if (error) throw error
return (data || []).reduce((acc, row) => {
acc[row.assessment_id] = row.share_token
return acc
}, {})
}
export const revokeAssessmentShare = async (assessmentId) => {
if (!supabase) {
throw new Error('Supabase ist nicht konfiguriert.')
}
const { error } = await supabase
.from('assessment_shares')
.delete()
.eq('assessment_id', assessmentId)
if (error) throw error
}
export const getSharedAssessment = async (shareToken) => {
if (!supabase) {
throw new Error('Supabase ist nicht konfiguriert.')
}
const { data, error } = await supabase.rpc('get_shared_assessment', {
p_share_token: shareToken
})
if (error) throw error
const row = getSingleRow(data)
return row ? mapSharedAssessment(row) : null
}

12
src/lib/supabaseClient.js Normal file
View File

@@ -0,0 +1,12 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
if (!supabaseUrl || !supabaseAnonKey) {
console.warn('Supabase env vars missing. Auth is disabled until VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set.')
}
export const supabase = supabaseUrl && supabaseAnonKey
? createClient(supabaseUrl, supabaseAnonKey)
: null

View File

@@ -0,0 +1,66 @@
import { supabase } from './supabaseClient';
const normalizeTasks = (tasks) => {
if (!Array.isArray(tasks)) return [];
return tasks.map((task) => ({
type: task?.type || 'task',
title: task?.title || '',
durationMin: Number(task?.durationMin || 0),
repetitions: task?.repetitions ?? null,
intensity: task?.intensity || null,
instructions: task?.instructions || ''
}));
};
export const mapSessionToRpc = (session) => ({
weekNo: Number(session?.weekNo || session?.week_no || 1),
sessionNo: Number(session?.sessionNo || session?.session_no || 1),
mainExercise: session?.mainExercise || session?.main_exercise || 'Basistechnik',
secondaryExercises: Array.isArray(session?.secondaryExercises)
? session.secondaryExercises
: Array.isArray(session?.secondary_exercises)
? session.secondary_exercises
: [],
tasks: normalizeTasks(session?.tasks),
state: session?.state || 'open',
notes: session?.notes || ''
});
export const saveTrainingPlanWithSessions = async ({
patType,
analysisSnapshot,
durationWeeks,
sessionsPerWeek,
sessions
}) => {
if (!supabase) {
return {
ok: false,
error: new Error('Supabase ist nicht konfiguriert.')
};
}
try {
const payload = {
p_pat_type: patType,
p_analysis_snapshot: analysisSnapshot,
p_duration_weeks: Number(durationWeeks),
p_sessions_per_week: Number(sessionsPerWeek),
p_sessions: (sessions || []).map(mapSessionToRpc)
};
const { data, error } = await supabase.rpc('create_training_plan_with_sessions', payload);
if (error) throw error;
return {
ok: true,
planId: data
};
} catch (error) {
return {
ok: false,
error
};
}
};