feat: exercices, workouts and joins for admin panel

This commit is contained in:
Yehoshua Sandler 2025-05-16 17:14:35 -05:00
parent 880694da19
commit eb2737fd5e
9 changed files with 771 additions and 938 deletions

1215
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,19 +19,19 @@
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
},
"dependencies": {
"@payloadcms/admin-bar": "3.33.0",
"@payloadcms/db-postgres": "3.33.0",
"@payloadcms/live-preview-react": "3.33.0",
"@payloadcms/next": "3.33.0",
"@payloadcms/payload-cloud": "3.33.0",
"@payloadcms/plugin-form-builder": "3.33.0",
"@payloadcms/plugin-multi-tenant": "^3.37.0",
"@payloadcms/plugin-nested-docs": "3.33.0",
"@payloadcms/plugin-redirects": "3.33.0",
"@payloadcms/plugin-search": "3.33.0",
"@payloadcms/plugin-seo": "3.33.0",
"@payloadcms/richtext-lexical": "3.33.0",
"@payloadcms/ui": "3.33.0",
"@payloadcms/admin-bar": "3.38.0",
"@payloadcms/db-postgres": "3.38.0",
"@payloadcms/live-preview-react": "3.38.0",
"@payloadcms/next": "3.38.0",
"@payloadcms/payload-cloud": "3.38.0",
"@payloadcms/plugin-form-builder": "3.38.0",
"@payloadcms/plugin-multi-tenant": "3.38.0",
"@payloadcms/plugin-nested-docs": "3.38.0",
"@payloadcms/plugin-redirects": "3.38.0",
"@payloadcms/plugin-search": "3.38.0",
"@payloadcms/plugin-seo": "3.38.0",
"@payloadcms/richtext-lexical": "3.38.0",
"@payloadcms/ui": "3.38.0",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0",

View File

@ -1,8 +1,5 @@
import type { Metadata } from 'next'
import { cn } from '@/utilities/ui'
import { GeistMono } from 'geist/font/mono'
import { GeistSans } from 'geist/font/sans'
import React from 'react'
import { AdminBar } from '@/components/AdminBar'
@ -20,7 +17,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
const { isEnabled } = await draftMode()
return (
<html className={cn(GeistSans.variable, GeistMono.variable)} lang="en" suppressHydrationWarning>
<html lang="en" suppressHydrationWarning>
<head>
<InitTheme />
<link href="/favicon.ico" rel="icon" sizes="32x32" />

View File

@ -18,6 +18,7 @@ import { PreviewComponent as PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860 }
import { SlugComponent as SlugComponent_92cc057d0a2abb4f6cf0307edf59f986 } from '@/fields/slug/SlugComponent'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BlocksFeatureClient as BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { WatchTenantCollection as WatchTenantCollection_1d0591e3cf4f332c83a86da13a0de59a } from '@payloadcms/plugin-multi-tenant/client'
import { LinkToDoc as LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634 } from '@payloadcms/plugin-search/client'
import { ReindexButton as ReindexButton_aead06e4cbf6b2620c5c51c9ab283634 } from '@payloadcms/plugin-search/client'
import { RowLabel as RowLabel_ec255a65fa6fa8d1faeb09cf35284224 } from '@/Header/RowLabel'
@ -48,6 +49,7 @@ export const importMap = {
"@/fields/slug/SlugComponent#SlugComponent": SlugComponent_92cc057d0a2abb4f6cf0307edf59f986,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/plugin-multi-tenant/client#WatchTenantCollection": WatchTenantCollection_1d0591e3cf4f332c83a86da13a0de59a,
"@payloadcms/plugin-search/client#LinkToDoc": LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
"@payloadcms/plugin-search/client#ReindexButton": ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
"@/Header/RowLabel#RowLabel": RowLabel_ec255a65fa6fa8d1faeb09cf35284224,

View File

@ -0,0 +1,111 @@
import { CollectionConfig } from "payload";
export const ExerciseTypes: CollectionConfig = {
slug: 'exerciseTypes',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
unique: true,
admin: {
description: 'Strength, Cardio, etc...',
},
},
{
name: 'description',
type: 'textarea',
},
],
}
export const MuscleGroups: CollectionConfig = {
slug: 'muscleGroups',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
unique: true,
admin: {
description: 'Legs, Chest, or more specific...',
},
},
{
name: 'description',
type: 'text',
},
],
}
export const Equipments: CollectionConfig = {
slug: 'equipments',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
unique: true,
},
{
name: 'description',
type: 'textarea',
},
{
name: 'warning',
type: 'textarea',
},
],
}
export const Exercises: CollectionConfig = {
slug: 'exercises',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
unique: true,
},
{
name: 'type',
type: 'relationship',
relationTo: 'exerciseTypes',
hasMany: true,
},
{
name: 'muscleGroup',
type: 'relationship',
relationTo: 'muscleGroups',
hasMany: true,
},
{
name: 'equipmentNeeded',
type: 'relationship',
relationTo: 'equipments',
hasMany: true,
},
{
name: 'difficulty',
type: 'number',
max: 10,
},
{
name: 'instructions',
type: 'richText',
},
{
name: 'displayImage',
type: 'relationship',
relationTo: 'media'
},
],
}

View File

@ -0,0 +1,102 @@
import { CollectionConfig, Field } from "payload";
const WorkoutExercices: Field[] = [
{
type: 'row',
fields: [
{
name: 'exercise',
type: 'relationship',
relationTo: 'exercises',
hasMany: false,
admin: {
width: '25%',
},
},
{
name: 'reps',
type: 'number',
min: 1,
admin: {
width: '25%',
},
},
{
name: 'sets',
type: 'number',
min: 1,
admin: {
width: '25%',
},
},
{
name: 'restMinutesAfterSet',
type: 'number',
admin: {
width: '25%',
},
},
]
}
]
export const WorkoutTypes: CollectionConfig = {
slug: 'workoutTypes',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
unique: true,
admin: {
description: 'Strength, Cardio, etc...',
},
},
{
name: 'description',
type: 'textarea',
},
],
}
export const Workouts: CollectionConfig = {
slug: 'workouts',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
unique: true,
},
{
name: 'type',
type: 'relationship',
relationTo: 'workoutTypes',
hasMany: true,
},
{
name: 'difficulty',
type: 'number',
max: 10,
},
{
name: 'description',
type: 'textarea',
},
{
name: 'durationMinutes',
type: 'number',
},
{
name: 'exercises',
type: 'array',
fields: WorkoutExercices
}
],
}

View File

@ -73,6 +73,12 @@ export interface Config {
categories: Category;
users: User;
tenants: Tenant;
exercises: Exercise;
exerciseTypes: ExerciseType;
muscleGroups: MuscleGroup;
equipments: Equipment;
workouts: Workout;
workoutTypes: WorkoutType;
redirects: Redirect;
forms: Form;
'form-submissions': FormSubmission;
@ -90,6 +96,12 @@ export interface Config {
categories: CategoriesSelect<false> | CategoriesSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
tenants: TenantsSelect<false> | TenantsSelect<true>;
exercises: ExercisesSelect<false> | ExercisesSelect<true>;
exerciseTypes: ExerciseTypesSelect<false> | ExerciseTypesSelect<true>;
muscleGroups: MuscleGroupsSelect<false> | MuscleGroupsSelect<true>;
equipments: EquipmentsSelect<false> | EquipmentsSelect<true>;
workouts: WorkoutsSelect<false> | WorkoutsSelect<true>;
workoutTypes: WorkoutTypesSelect<false> | WorkoutTypesSelect<true>;
redirects: RedirectsSelect<false> | RedirectsSelect<true>;
forms: FormsSelect<false> | FormsSelect<true>;
'form-submissions': FormSubmissionsSelect<false> | FormSubmissionsSelect<true>;
@ -761,6 +773,119 @@ export interface Form {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exercises".
*/
export interface Exercise {
id: number;
tenant?: (number | null) | Tenant;
name?: string | null;
type?: (number | ExerciseType)[] | null;
muscleGroup?: (number | MuscleGroup)[] | null;
equipmentNeeded?: (number | Equipment)[] | null;
difficulty?: number | null;
instructions?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
displayImage?: (number | null) | Media;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exerciseTypes".
*/
export interface ExerciseType {
id: number;
tenant?: (number | null) | Tenant;
/**
* Strength, Cardio, etc...
*/
name?: string | null;
description?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "muscleGroups".
*/
export interface MuscleGroup {
id: number;
tenant?: (number | null) | Tenant;
/**
* Legs, Chest, or more specific...
*/
name?: string | null;
description?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "equipments".
*/
export interface Equipment {
id: number;
tenant?: (number | null) | Tenant;
name?: string | null;
description?: string | null;
warning?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "workouts".
*/
export interface Workout {
id: number;
tenant?: (number | null) | Tenant;
name?: string | null;
type?: (number | WorkoutType)[] | null;
difficulty?: number | null;
description?: string | null;
durationMinutes?: number | null;
exercises?:
| {
exercise?: (number | null) | Exercise;
reps?: number | null;
sets?: number | null;
restMinutesAfterSet?: number | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "workoutTypes".
*/
export interface WorkoutType {
id: number;
tenant?: (number | null) | Tenant;
/**
* Strength, Cardio, etc...
*/
name?: string | null;
description?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "redirects".
@ -957,6 +1082,30 @@ export interface PayloadLockedDocument {
relationTo: 'tenants';
value: number | Tenant;
} | null)
| ({
relationTo: 'exercises';
value: number | Exercise;
} | null)
| ({
relationTo: 'exerciseTypes';
value: number | ExerciseType;
} | null)
| ({
relationTo: 'muscleGroups';
value: number | MuscleGroup;
} | null)
| ({
relationTo: 'equipments';
value: number | Equipment;
} | null)
| ({
relationTo: 'workouts';
value: number | Workout;
} | null)
| ({
relationTo: 'workoutTypes';
value: number | WorkoutType;
} | null)
| ({
relationTo: 'redirects';
value: number | Redirect;
@ -1336,6 +1485,90 @@ export interface TenantsSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exercises_select".
*/
export interface ExercisesSelect<T extends boolean = true> {
tenant?: T;
name?: T;
type?: T;
muscleGroup?: T;
equipmentNeeded?: T;
difficulty?: T;
instructions?: T;
displayImage?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exerciseTypes_select".
*/
export interface ExerciseTypesSelect<T extends boolean = true> {
tenant?: T;
name?: T;
description?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "muscleGroups_select".
*/
export interface MuscleGroupsSelect<T extends boolean = true> {
tenant?: T;
name?: T;
description?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "equipments_select".
*/
export interface EquipmentsSelect<T extends boolean = true> {
tenant?: T;
name?: T;
description?: T;
warning?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "workouts_select".
*/
export interface WorkoutsSelect<T extends boolean = true> {
tenant?: T;
name?: T;
type?: T;
difficulty?: T;
description?: T;
durationMinutes?: T;
exercises?:
| T
| {
exercise?: T;
reps?: T;
sets?: T;
restMinutesAfterSet?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "workoutTypes_select".
*/
export interface WorkoutTypesSelect<T extends boolean = true> {
tenant?: T;
name?: T;
description?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "redirects_select".

View File

@ -17,6 +17,8 @@ import { plugins } from './plugins'
import { defaultLexical } from '@/fields/defaultLexical'
import { getServerSideURL } from './utilities/getURL'
import { Tenants } from './collections/Tenants'
import { Equipments, Exercises, ExerciseTypes, MuscleGroups } from './collections/Exercises'
import { Workouts, WorkoutTypes } from './collections/Workouts'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@ -65,7 +67,7 @@ export default buildConfig({
connectionString: process.env.DATABASE_URI || '',
},
}),
collections: [Pages, Posts, Media, Categories, Users, Tenants],
collections: [Pages, Posts, Media, Categories, Users, Tenants, Exercises, ExerciseTypes, MuscleGroups, Equipments, Workouts, WorkoutTypes],
cors: [getServerSideURL()].filter(Boolean),
globals: [Header, Footer],
plugins: [

View File

@ -98,10 +98,13 @@ export const plugins: Plugin[] = [
debug: true,
enabled: true,
collections: {
pages: {
useBaseListFilter: true,
useTenantAccess: true,
},
pages: {},
exercises: {},
exerciseTypes: {},
muscleGroups: {},
equipments: {},
workouts: {},
workoutTypes: {},
},
tenantsArrayField: {
includeDefaultField: false,