Refactoring

This commit is contained in:
Andreas 2024-06-25 08:09:39 +02:00
parent 57375bd902
commit 943267be04
10 changed files with 274 additions and 135 deletions

View File

@ -5,6 +5,7 @@ import Sidebar, { SidebarEntry } from "@/components/server/admin/views/sidebar";
import { cookies } from "next/headers";
import PostView from "@/components/server/admin/views/PostView";
import { ReactNode } from "react";
import ProjectView from "@/components/server/admin/views/ProjectView";
type Props = {
params: {
@ -20,12 +21,16 @@ function Home(){
function PostManager(){
return <div>posts</div>
}
function ProjectManager(){
return <div>projects</div>
}
async function getViewMap():Promise<Map<string, JSX.Element>>{
return new Map([
['home', <Home></Home>],
['man-post', <PostView></PostView>]
['man-post', <PostView></PostView>],
['man-proj', <ProjectView></ProjectView>]
]);
}

View File

@ -11,7 +11,7 @@ export default function TableGen(props:Props){
<table className="table overflow-scroll">
<thead>
<tr>
{props.headings.map((h)=>
{[...props.headings,'Edit', 'Delete'].map((h)=>
<th key={h} scope="col">{h}</th>
)}
</tr>

View File

@ -14,17 +14,17 @@ export type PostTableCallbacks = {
}
export type EditorProps = {
open:boolean;
post:Partial<PostAttributesWithBuckets>;
editorOpenState:boolean;
openedPost:Partial<PostAttributesWithBuckets>;
projects?:Attributes<Project>[];
callbacks:PostTableCallbacks;
}
export default function PostEditor(props:EditorProps){
let [content,setContent] = useState(props.post?.content)
let [title,setTitle] = useState(props.post?.title)
let [projectID,setProjectID] = useState(props.post?.project_id)
let [content,setContent] = useState(props.openedPost?.content)
let [title,setTitle] = useState(props.openedPost?.title)
let [projectID,setProjectID] = useState(props.openedPost?.project_id)
let textbox:any = useRef(undefined);
function adjustHeight(): void {
@ -39,45 +39,50 @@ export default function PostEditor(props:EditorProps){
const projectSelectionChange:ChangeEventHandler<HTMLSelectElement> = (e)=>setProjectID(parseInt(e.target.value));
const onClickSaveButton:MouseEventHandler<HTMLButtonElement> = (e)=>{
props.callbacks.savePost({
id: props.post.id as number,
id: props.openedPost.id as number,
content:content as string,
title:title as string,
project_id:projectID
});
}
const onClickCancelButton:MouseEventHandler<HTMLButtonElement> = (e)=>{props.callbacks.closeEditor();}
return <>
<form className="bg-light w-[100%] h-content p-1">
<h1 className="m-2">Edit Post</h1>
<h2 className="m-2">Title</h2>
<input value={title} onChange={e => setTitle(e.target.value)} type='text' className="m-2"></input>
<h2 className="m-2">Content</h2>
<textarea onChange={onTextAreaChange}
<h1 key="heading-editpost" className="m-2">Edit Post</h1>
<h2 key="label-title" className="m-2">Title</h2>
<input key="input-title"
value={title}
onChange={e => setTitle(e.target.value)}
type='text' className="m-2"/>
<h2 key="label-content" className="m-2">Content</h2>
<textarea key="input-content" onChange={onTextAreaChange}
ref={textbox}
value={content}
style={{"height" : "100%"}}
className="w-[100%] min-h-auto h-content align-top text-start text-base line-clamp-6 m-2"/>
<h2 className="m-2">Project</h2>
<select onChange={projectSelectionChange}
<h2 key="label-project" className="m-2">Project</h2>
<select key="input-project"
onChange={projectSelectionChange}
name="projects"
id="projects"
defaultValue={props.post?.project?.id}
placeholder={props.post?.project?.name}
defaultValue={props.openedPost?.project?.id}
placeholder={props.openedPost?.project?.name}
value={projectID} className="m-2">
<option value={0}>unassigned</option>
<option key="projectSelectionOpt-0" value={0}>unassigned</option>
{props.projects?.map(p=><option key={`projectSelectionOpt-${p.id}`} value={p.id}>{p.readableIdentifier}</option>)}
</select>
<h2>Attachments</h2>
<table className="table table-striped">
<h2 key="label-attachments">Attachments</h2>
<table key="table-buckets" className="table table-striped">
<thead>
<tr><td>Buckets</td></tr>
</thead>
<tbody>
{
props.post.buckets
props.openedPost.buckets
? (()=>{
let bucketMap:Map<UUID,Attributes<Bucket>> = new Map(props.post.buckets.map((b)=>[b.id as UUID,b]));
let bucketList = [...props.post.buckets.map((b)=>b.id)];
let bucketMap:Map<UUID,Attributes<Bucket>> = new Map(props.openedPost.buckets.map((b)=>[b.id as UUID,b]));
let bucketList = [...props.openedPost.buckets.map((b)=>b.id)];
return bucketList.map((e)=>{
return <>
<tr key={`bucketAccordionRow-${bucketList.indexOf(e).toString()}`}><Accordion>
@ -98,7 +103,9 @@ export default function PostEditor(props:EditorProps){
}}/></li>
{(()=>{
const bucket = bucketMap.get(e as UUID)
return bucket && bucket.attachments ? bucket.attachments.map((attachment)=><li key={`listItem-file-${attachment.filename}`}>{attachment.filename}</li>) : <></>
return bucket && bucket.attachments
? bucket.attachments.map((attachment)=><li key={`listItem-file-${attachment.filename}`}>{attachment.filename}</li>)
: <></>
})()}
</ul>
</AccordionBody>

View File

@ -1,13 +1,25 @@
'use client'
import React, { ChangeEvent, ChangeEventHandler, LegacyRef, MouseEventHandler, MutableRefObject, ReactNode, useLayoutEffect, useRef } from "react"
import TableGen from "../TableGen"
import { Attributes, CreationAttributes } from "@sequelize/core";
import React, {
ChangeEvent,
ChangeEventHandler,
LegacyRef,
MouseEventHandler,
MutableRefObject,
ReactNode,
useLayoutEffect,
useRef
} from "react";
import TableGen from "../TableGen";
import toast from "react-hot-toast";
import PostEditor, { EditorProps } from "./PostEditor";
import {
Attributes,
CreationAttributes
} from "@sequelize/core";
import { useState } from "react";
import { Project, Post, Bucket } from "@/model/Models";
import toast from "react-hot-toast"
import { ActionResult } from "@/app/lib/actions/ActionResult";
import { handleActionResult } from "@/app/lib/actions/clientActionHandler";
import PostEditor, { EditorProps } from "./PostEditor";
import { getPostsWithBuckets } from "@/app/lib/actions/entityManagement/postActions";
import { PostAttributesWithBuckets } from "@/model/Post";
@ -17,7 +29,7 @@ type Actions = {
getProjects: () => Promise<ActionResult<Attributes<Project>[]>>
savePost: (data: Partial<Attributes<Post>>) => Promise<ActionResult<Attributes<Post>[]>>
// uploadAttachment: ()=> Promise<ActionResult<any>>
}
};
type Props = {
children?: ReactNode;
@ -27,32 +39,37 @@ type Props = {
actions: Actions;
}
const aifa = (a: ReactNode, b: ReactNode) => a ? a : b
export default function PostTable(props: Props) {
function closeEditor(): void {
setEditor({
open: false
})
}
function showEditor(entry: Partial<PostAttributesWithBuckets>): void {
setEditor({
open: true,
post: entry
})
}
const initEditorState: Partial<EditorProps> = {
open: false
editorOpenState: false
}
// Set up required state hooks
const [posts, setPosts] = useState(props.data);
const [editor, setEditor] = useState(initEditorState)
const [projects, setProjects] = useState(props.projects);
// Define editor controls
function closeEditor(): void {
setEditor({
editorOpenState: false
})
}
function showEditor(entry: Partial<PostAttributesWithBuckets>): void {
setEditor({
editorOpenState: true,
openedPost: entry
})
}
function deletePost(entry: Partial<PostAttributesWithBuckets>) {
if(! entry.id) return;
if (!entry.id) return;
props.actions?.deletePost(entry.id)
.then(actionResult => {
const result = handleActionResult(actionResult);
@ -63,14 +80,13 @@ export default function PostTable(props: Props) {
});
}
const aifa = (a: ReactNode, b: ReactNode) => a ? a : b
function refetch() {
props.actions?.getPosts().then((p) => {
props.actions.getPosts()
.then((p) => {
const result = handleActionResult(p)
if (result) setPosts(result)
});
props.actions.getProjects().then(e => {
}).then(props.actions.getProjects)
.then(e => {
const result = handleActionResult(e);
if (result) setProjects(result)
})
@ -91,29 +107,72 @@ export default function PostTable(props: Props) {
closeEditor();
};
return <>
<TableGen headings={props.headings}>
{posts.map((d: Partial<PostAttributesWithBuckets>) => <>
<tr key={d.id}>
<th key={`row${d.id}`} scope="row">{d.id}</th>
<td key='titlefield'>{d.title}</td>
<td key='contentfield'>{d.content? (d.content.length < 255 ? d.content : `${d.content.substring(0, 255)}...`) : "No Content Found"}</td>
<td key='project'>{
aifa((projects.find((e) => {
return (e.id == d.project_id)
})?.readableIdentifier), 'uncategorized')
}</td>
<td key='createdatfield'>{d.createdAt?.toString()}</td>
<td key='updatedatfield'>{d.updatedAt?.toString()}</td>
<td key='btnedit'><button type="button" className="btn btn-primary" onClick={() => showEditor(d)}>Edit</button></td>
<td key='btndelete'><button type="button" className="btn btn-danger" onClick={() => deletePost(d)}> Delete</button></td>
</tr>
function TableFields({ postData }: { postData: Partial<PostAttributesWithBuckets> }) {
return [
{
(editor.open && editor.post && editor.post.id == d.id)
?<tr key={'activeEditor'}><th scope="row" colSpan={props.headings.length}><PostEditor callbacks={{ savePost: savePost, closeEditor: closeEditor, uploadAttachment: ()=>{} }} open={editor.open} post={editor.post} projects={projects} ></PostEditor></th></tr>
: ""
key: 'post-title',
content: postData.title
}, {
key: 'post-content',
content:
!postData.content ? "No Content Found" :
postData.content.length < 255 ? postData.content :
`${postData.content.substring(0, 255)}...`
}, {
key: 'post-project',
content: aifa((projects.find((e) => {
return (e.id == postData.project_id)
})?.readableIdentifier), 'uncategorized')
}, {
key: 'post-createdat',
content: postData.createdAt?.toString()
}, {
key: 'post-updatedat',
content: postData.updatedAt?.toString()
}, {
key: 'post-editbutton',
content: <button key="editbutton" type="button" className="btn btn-primary" onClick={() => showEditor(postData)}>Edit</button>
}, {
key: 'post-deletebutton',
content: <button key="deletebutton" type="button" className="btn btn-danger" onClick={() => deletePost(postData)}> Delete</button>
}
</>)}
].map(field =>
<td key={field.key}>{field.content}</td>
);
}
function TableRow(props: {postData: Partial<PostAttributesWithBuckets>, headings: string | any[]}){
return <><tr key={props.postData.id}>
<th key={`rowheading-post-${props.postData.id}`} scope="row">{props.postData.id}</th>
<TableFields key={`tableFields-${props.postData.id}`} postData={props.postData} />
</tr>
{(editor.editorOpenState
&& editor.openedPost
&& editor.openedPost.id == props.postData.id)
? <tr>
<th scope="row" colSpan={props.headings.length}>
<PostEditor callbacks={{
savePost: savePost,
closeEditor: closeEditor,
uploadAttachment: () => { }
}}
editorOpenState={editor.editorOpenState}
openedPost={editor.openedPost}
projects={projects} />
</th>
</tr>
: ""}
</>
}
return <>
<TableGen key="der-table" headings={props.headings}>
{posts.map((postData: Partial<PostAttributesWithBuckets>) => <TableRow headings={props.headings}
postData={postData}
key={postData.id}/> )}
</TableGen>
</>
}

View File

@ -2,11 +2,9 @@
import { cookies } from "next/headers";
import LoginForm from "@/components/client/admin/loginForm";
import AdminPanel from "@/components/server/admin/adminPanel";
import ClientAuthHandler from "@/components/client/admin/clientAuthHandler";
import { serverValidateSessionCookie } from "@/app/lib/actions/actions";
import { constructAPIUrl } from "@/util/Utils";
import { ReactNode } from "react";
import { AuthContext, AuthProps } from "@/providers/providers";
@ -21,7 +19,7 @@ interface Props {
export default async function AuthHandler(props: Props) {
const protoKoek = await cookies().get('auth')?.value;
const koek = decodeURIComponent(protoKoek ? protoKoek: "");
console.log("koekje:" + koek)
// console.log("koekje:" + koek)
let p:AuthProps = {
};
if(koek){

View File

@ -25,8 +25,6 @@ export default async function PostView(props:Props){
'Project',
'Date Created',
'Date Modified',
'Edit',
'Delete',
]
const actions = {
deletePost: deletePost,
@ -43,7 +41,11 @@ export default async function PostView(props:Props){
<div className="w-[100%] min-h-fit bg-gray-100 overflow-scroll">
<span className="flex flex-row flex-grow w-[100%] pl-2 pr-2"><h1 className="p-2 inline-block">Post Management</h1><section className="flex-grow"></section><button className='btn btn-success h-12 mt-auto mb-auto self-end'>New</button></span>
<div className="w-[100%] m-auto">
<PostTable data={posts} projects={projects} headings={headings} actions={actions}></PostTable>
<PostTable data={posts}
projects={projects}
headings={headings}
actions={actions}>
</PostTable>
</div>
</div>
);

View File

@ -0,0 +1,66 @@
cache: 'no-store'
import { tryFetchPosts } from "@/app/api/post/route";
import { constructAPIUrl } from "@/util/Utils";
import { ReactNode, useEffect } from "react";
import TableGen from "../../../client/TableGen";
import PostTable from "@/components/client/admin/PostTable";
import { deletePost, getPostsWithBuckets, updatePost } from "@/app/lib/actions/entityManagement/postActions";
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
import { Bucket, Project, Post, dbSync, Attachment } from "@/model/Models";
import { tryCreateAttachment } from "@/app/api/attachment/route";
import { Attributes } from '@sequelize/core';
import * as util from 'util'
type Props = {
children?:ReactNode
}
export default async function ProjectView(props:Props){
await dbSync;
// const headings = [
// '#',
// 'Title',
// 'Content',
// 'Project',
// 'Date Created',
// 'Date Modified',
// 'Edit',
// 'Delete',
// ]
const headings = Project.getAttributes();
// const actions = {
// deletePost: deletePost,
// getPosts:getPostsWithBuckets,
// getProjects:getProjects,
// savePost:updatePost,
// // uploadAttachment:tryCreateAttachment
// };
const posts:Post[] = await Post.findAll({include: {model: Bucket, include: {model: Attachment}}}).then(posts=>posts.map((e)=>JSON.parse(JSON.stringify(e))));
const projects = await Project.findAll().then(projects=>projects.map((e)=>JSON.parse(JSON.stringify(e))));
return <>
<div className="w-[100%] min-h-fit bg-gray-100 overflow-scroll">
<span className="flex flex-row flex-grow w-[100%] pl-2 pr-2"><h1 className="p-2 inline-block">Post Management</h1><section className="flex-grow"></section><button className='btn btn-success h-12 mt-auto mb-auto self-end'>New</button></span>
<div className="w-[100%] m-auto">
{(()=>{
let a:string[] = []
for (const key in headings)
{
a.push(key)
}
// return a.map((e)=> <div key={e}>{e}</div> );
return <TableGen headings={a}><></></TableGen>
})()}
{/* <PostTable data={posts}
projects={projects}
headings={headings}
actions={actions}>
</PostTable> */}
</div>
</div>
</>;
}

View File

@ -5,7 +5,6 @@ import "@/app/index.css"
import styles from "./article.module.css"
import { MDXComponents, MDXContent } from "mdx/types";
import { MDXRemote } from 'next-mdx-remote/rsc'
export async function ExampleComponent(){
return (
@ -16,6 +15,7 @@ export async function ExampleComponent(){
export default async function Article(params: { id: string|undefined, title: string|undefined, content: string|undefined, date?:string|undefined } ) {
const components = { ExampleComponent }
const MDXRemote = (await import ('next-mdx-remote/rsc')).MDXRemote;
return (
<article id={`post-${params.id}`} className="bg-background-800 w-[80%] m-auto">

View File

@ -53,7 +53,7 @@ export class Post extends Model<PostAttributes, PostCreationAttributes> {
@BelongsTo(()=>User, { foreignKey: 'user_id', inverse: { type: 'hasMany', as: 'posts' } })
declare user:NonAttribute<User>;
@BelongsTo(()=>Project, { foreignKey:'project_id', inverse: { type: 'hasMany', as: 'posts' } })
@BelongsTo(()=>Project, { foreignKey:'project_id', foreignKeyConstraints: false, inverse: { type: 'hasMany', as: 'posts' } })
declare project:CreationOptional<Project>;
@BelongsToMany(()=>Tag, { through: { model: ()=>PostTag, unique: false}, inverse: {as: 'taggedPosts'} })

View File

@ -11,6 +11,8 @@ export class Project extends Model<InferAttributes<Project>, InferCreationAttrib
declare readableIdentifier: string;
@Attribute(DataTypes.STRING)
declare name: string;
/** Defined by {@link Post.project} */
declare posts?: NonAttribute<Post>[];
declare static associations: {
posts: Association<Post, Project>;