Edit and Add Actions dummy implementation

This commit is contained in:
petkir 2020-06-20 11:50:46 +02:00
parent f5a3e8e9f8
commit 5a84a00f54
9 changed files with 259 additions and 77 deletions

View File

@ -5,4 +5,9 @@ export interface IKanbanBoardRenderers{
task?: (task:IKanbanTask) => JSX.Element ;
bucketHeadline?: (bucket:IKanbanBucket) => JSX.Element ;
taskDetail?: (task:IKanbanTask) => JSX.Element ;
/*
its an action not a renderer
taskEdit?: (task:IKanbanTask) => JSX.Element ;
taskAdd?: (bucket?:IKanbanBucket) => JSX.Element ;
*/
}

View File

@ -1,8 +1,18 @@
import { IKanbanBucket } from "./IKanbanBucket";
import { IKanbanTask } from "./IKanbanTask";
export interface IKanbanBoardTaskActions {
toggleCompleted?: (taskId: string) => void;
allowMove?: (taskId: string, prevBucket: IKanbanBucket, targetBucket: IKanbanBucket) => boolean;
moved?: (taskId: string, targetBucket: IKanbanBucket) => void;
/* think about Await???
*/
addTaskSaved?: (task: IKanbanTask) => void;
editTaskSaved?: (task: IKanbanTask) => void;
//deleteTask?: (task: IKanbanTask) => void;
taskEdit?: (task:IKanbanTask) => void ;
taskAdd?: (bucket?:IKanbanBucket) => void ;
}

View File

@ -15,13 +15,16 @@ export interface IKanbanTaskManagedProps {
renderer?: (name: string, value: object, type:KanbanTaskMamagedPropertyType) => JSX.Element;
}
/* 0 is bad because
const value = EnumType.xyz // = 0
if(value) {is false}
*/
export enum KanbanTaskMamagedPropertyType {
string,
number,
percent,
html,
person,
persons,
complex
string=1,
number=2,
percent=3,
html=4,
person=5,
persons=6,
complex=7
}

View File

@ -13,7 +13,10 @@ import * as strings from 'KanbanBoardStrings';
export interface IKanbanBucketProps extends IKanbanBucket {
buckettasks: IKanbanTask[];
tasksettings: IKanbanBoardTaskSettings;
taskactions: IKanbanBoardTaskActions;
toggleCompleted?: (taskId: string) => void;
addTask?: (bucket: string) => void;
onDragStart: (event, taskId: string, bucket: string) => void;
onDragOver: (event, targetbucket: string) => void;
@ -50,7 +53,7 @@ export default class KanbanBucket extends React.Component<IKanbanBucketProps, IK
*/
public render(): React.ReactElement<IKanbanBucketProps> {
const { bucket, bucketheadline, color, buckettasks,
tasksettings, taskactions, percentageComplete,
tasksettings, percentageComplete,
allowAddTask, overBucket, leavingTaskId, leavingBucket } = this.props;
return (
@ -71,19 +74,20 @@ export default class KanbanBucket extends React.Component<IKanbanBucketProps, IK
{allowAddTask && (<ActionButton
iconProps={{ iconName: 'Add' }}
allowDisabledFocus={true}
onClick={() => this.props.addTask(bucket)}
>
{strings.AddTask}
</ActionButton>)}
{
buckettasks.map((t) => {
const merge = { ...t, ...tasksettings, ...taskactions };
const merge = { ...t, ...tasksettings, };
const isMoving = (t.taskId === leavingTaskId && t.bucket === leavingBucket);
return (<div className={isMoving ? styles.placeholder : undefined} key={'' + t.taskId} >
<KanbanTask
key={'task' + t.taskId}
{...merge}
toggleCompleted={this.props.toggleCompleted}
isMoving={isMoving}
openDetails={this.props.openDetails}
onDragStart={(event) => this.props.onDragStart(event, t.taskId, t.bucket)}

View File

@ -1,5 +1,7 @@
import * as React from 'react';
import styles from './KanbanComponent.module.scss';
import * as strings from 'KanbanBoardStrings';
import { IKanbanTask } from './IKanbanTask';
import { IKanbanBoardTaskSettings } from './IKanbanBoardTaskSettings';
import { IKanbanBoardTaskActions } from './IKanbanBoardTaskActions';
@ -11,11 +13,11 @@ import KanbanTaskManagedProp from './KanbanTaskManagedProp';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { IStackStyles, Stack } from 'office-ui-fabric-react/lib/Stack';
import { clone } from '@microsoft/sp-lodash-subset';
import { CommandBar } from 'office-ui-fabric-react/lib/CommandBar';
import { stringIsNullOrEmpty } from '@pnp/common';
import { TooltipHost } from 'office-ui-fabric-react';
export interface IKanbanComponentProps {
buckets: IKanbanBucket[];
@ -24,6 +26,9 @@ export interface IKanbanComponentProps {
taskactions: IKanbanBoardTaskActions;
showCommandbar?: boolean;
renderers?: IKanbanBoardRenderers;
allowEdit?: boolean;
allowAdd?: boolean;
editSchema?: boolean;
/*
showCommandbarNew: boolean;
allowDialog: boolean; TODO im mock
@ -36,6 +41,15 @@ export interface IKanbanComponentState {
overBucket?: string;
openDialog: boolean;
openTaskId?: string;
dialogState?: DialogState;
editTask?: IKanbanTask;
addBucket?: IKanbanBucket;
}
export enum DialogState {
New = 1,
Edit = 2,
Display = 3
}
export default class KanbanComponent extends React.Component<IKanbanComponentProps, IKanbanComponentState> {
@ -55,7 +69,11 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
public render(): React.ReactElement<IKanbanComponentProps> {
const { buckets, tasks, tasksettings, taskactions, showCommandbar } = this.props;
const { openDialog } = this.state;
const { leavingBucket, leavingTaskId, overBucket } = this.state
const { leavingBucket, leavingTaskId, overBucket } = this.state;
const wrappedTaskActions: IKanbanBoardTaskActions = {
};
return (
<div>
{showCommandbar && <CommandBar
@ -74,8 +92,12 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
{...merge}
buckettasks={tasks.filter((x) => x.bucket == b.bucket)}
tasksettings={tasksettings}
taskactions={taskactions}
openDetails={this.openDialog.bind(this)}
toggleCompleted={this.props.taskactions && this.props.taskactions.toggleCompleted ? this.props.taskactions.toggleCompleted : undefined}
addTask={this.internalAddTask.bind(this)}
openDetails={this.internalOpenDialog.bind(this)}
onDrop={this.onDrop.bind(this)}
onDragLeave={this.onDragLeave.bind(this)}
onDragOver={this.onDragOver.bind(this)}
@ -87,54 +109,119 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
)}
</div>
{openDialog && (this.renderDetails())}
{openDialog && (this.renderDialog())}
</div>
);
}
private renderDetails(): JSX.Element {
const renderer = this.props.renderers && this.props.renderers.taskDetail ? this.props.renderers.taskDetail : this.internalTaskDetailRenderer;
private getTaskByID(taskId: string): IKanbanTask {
const tasks = this.props.tasks.filter(t => t.taskId == this.state.openTaskId);
if (tasks.length == 1) {
const task = tasks[0];
return tasks[0];
}
throw "Error Taks not found by taskId";
}
return (<Dialog
minWidth={600}
hidden={!this.state.openDialog}
onDismiss={this.closeDialog.bind(this)}
dialogContentProps={{
type: DialogType.largeHeader,
title: task.title,
subText: ''
}}
modalProps={{
isBlocking: false,
styles: { main: { minWidth: 600 } }
}}
>
{renderer(task)}
</Dialog>);
private renderDialog(): JSX.Element {
let renderer: (task?: IKanbanTask, bucket?: IKanbanBucket) => JSX.Element = () => (<div>Dialog Renderer Not Set</div>);
let task: IKanbanTask = undefined;
let bucket: IKanbanBucket = undefined;
let dialogheadline:string ='';
switch (this.state.dialogState) {
case DialogState.Edit:
task = this.getTaskByID(this.state.openTaskId);
renderer = this.internalTaskEditRenderer.bind(this);
dialogheadline =strings.EditTaskDlgHeadline;
break;
case DialogState.New:
renderer = this.internalTaskAddRenderer.bind(this);
dialogheadline =strings.AddTaskDlgHeadline;
break;
default:
task = this.getTaskByID(this.state.openTaskId);
dialogheadline =task.title;
renderer = (this.props.renderers && this.props.renderers.taskDetail) ? this.props.renderers.taskDetail : this.internalTaskDetailRenderer.bind(this);
break;
}
return (<Dialog
minWidth={600}
hidden={!this.state.openDialog}
onDismiss={this.internalCloseDialog.bind(this)}
dialogContentProps={{
type: DialogType.largeHeader,
title: dialogheadline,
subText: ''
}}
modalProps={{
isBlocking: false,
styles: { main: { minWidth: 600 } }
}}
>
{renderer(task, bucket)}
<DialogFooter>
{(this.props.allowEdit && this.state.dialogState === DialogState.Display) &&
(<PrimaryButton onClick={this.clickEditTask.bind(this)} text={strings.EditTaskBtn} />)}
{(this.props.allowEdit && this.state.dialogState === DialogState.Edit) &&
(<PrimaryButton onClick={this.saveEditTask.bind(this)} text={strings.SaveTaskBtn} />)}
{(this.props.allowAdd && this.state.dialogState === DialogState.New) &&
(<PrimaryButton onClick={this.saveAddTask.bind(this)} text={strings.SaveAddTaskBtn} />)}
<DefaultButton onClick={this.internalCloseDialog.bind(this)} text={strings.CloseTaskDialog} />
</DialogFooter>
</Dialog>);
// Error Not found or more than one
throw "Error Not found or more than one";
return (<div></div>);
}
private clickEditTask(): void {
const task = this.getTaskByID(this.state.openTaskId);
if (this.props.taskactions.taskEdit) {
this.internalCloseDialog();
this.props.taskactions.taskEdit(clone(task));
} else {
this.setState({
dialogState: DialogState.Edit,
editTask: clone(task)
});
}
}
private saveEditTask() {
if (this.props.taskactions.editTaskSaved) {
const edittask = clone(this.state.editTask);
//check fist state and than event or in the other way
this.internalCloseDialog();
this.props.taskactions.editTaskSaved(edittask);
} else {
throw "allowEdit is Set but no handler is set";
}
}
private saveAddTask() {
if (this.props.taskactions.editTaskSaved) {
const edittask = clone(this.state.editTask);
//check fist state and than event or in the other way
this.internalCloseDialog();
this.props.taskactions.editTaskSaved(edittask);
} else {
throw "allowAdd is Set but no handler is set";
}
}
private internalTaskDetailRenderer(task: IKanbanTask): JSX.Element {
return (<Stack>
{/* <Stack horizontal horizontalAlign="stretch">
<Stack.Item align="auto" styles={rowStyle1}>
<span>% Complete</span>
</Stack.Item>
<Stack.Item align="stretch" styles={rowStyle2}>
<span>{task.bucket}</span>
</Stack.Item>
</Stack>
<KanbanTaskManagedProp name:'person' ... key={p.name+i} />
*/}
{task.mamagedProperties && (
task.mamagedProperties.map((p, i) => {
return (
@ -146,18 +233,54 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
</Stack>
);
}
private closeDialog(ev?: React.MouseEvent<HTMLButtonElement>) {
private internalTaskEditRenderer(task: IKanbanTask): JSX.Element {
const schema = this.props.editSchema; //TODO
return (<div>Edit</div>);
}
private internalTaskAddRenderer(task?: IKanbanTask, bucket?: IKanbanBucket): JSX.Element {
const schema = this.props.editSchema; //TODO
return (<div>New</div>);
}
private internalCloseDialog(ev?: React.MouseEvent<HTMLButtonElement>) {
this.setState({
openDialog: false,
openTaskId: undefined
openTaskId: undefined,
dialogState: undefined,
editTask: undefined,
addBucket: undefined
});
}
private openDialog(taskid: string) {
private internalOpenDialog(taskid: string) {
this.setState({
openDialog: true,
openTaskId: taskid
openTaskId: taskid,
dialogState: DialogState.Display
});
}
private internalAddTask(targetbucket?: string) {
let bucket: IKanbanBucket = undefined;
if (bucket) {
const buckets = this.props.buckets.filter((p) => p.bucket === targetbucket)
if (buckets.length === 1) {
bucket = clone(buckets[0]);
} else {
throw "Bucket not Found in addDialog";
}
}
if (this.props.taskactions && this.props.taskactions.taskAdd) {
this.props.taskactions.taskAdd(bucket);
} else {
this.setState({
openDialog: true,
openTaskId: '',
dialogState: DialogState.New,
addBucket: bucket
});
}
}
private onDragLeave(event): void {
@ -170,7 +293,7 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
private onDragEnd(event): void {
console.log('onDragEnd');
this.dragelement=undefined;
this.dragelement = undefined;
}
private onDragStart(event, taskId: string, bucket: string): void {
@ -204,7 +327,7 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
event.preventDefault();
console.log('onDragOver this.dragelement');
console.log(this.dragelement);
if (this.dragelement.bucket !== targetbucket) {
/* if (!this.bucketRef.current.classList.contains(styles.dragover)) {
this.bucketRef.current.classList.add(styles.dragover)
@ -225,7 +348,7 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
const taskId = this.dragelement.taskId;
const source = this.props.buckets.filter(s => s.bucket == this.dragelement.bucket)[0];
const target = this.props.buckets.filter(s => s.bucket == targetbucket)[0];
if (this.props.taskactions) {
let allowMove = true;
if (this.props.taskactions.allowMove) {
@ -249,15 +372,21 @@ export default class KanbanComponent extends React.Component<IKanbanComponentPro
}
private getItems = () => {
return [
{
key: 'newItem',
name: 'New',
cacheKey: 'myCacheKey', // changing this key will invalidate this items cache
iconProps: {
iconName: 'Add'
}
}]
if (this.props.allowAdd) {
//TODO
return [
{
key: 'newItem',
name: 'New',
cacheKey: 'myAddBtnKey',
iconProps: {
iconName: 'Add'
},
onClick: () => this.internalAddTask.bind(this)
}];
}
return [];
}
private getFarItems = () => {

View File

@ -8,9 +8,9 @@ import { IKanbanBoardTaskActions } from './IKanbanBoardTaskActions';
import classNames from 'classnames';
import { IconNames } from 'office-ui-fabric-react';
export interface IKanbanTaskProps extends IKanbanTask, IKanbanBoardTaskSettings, IKanbanBoardTaskActions {
export interface IKanbanTaskProps extends IKanbanTask, IKanbanBoardTaskSettings {
toggleCompleted?: (taskId: string) => void;
openDetails: (taskId: string) => void;
onDragStart: (event) => void;
onDragEnd: (event) => void;

View File

@ -2,7 +2,7 @@
Thinking about Kanban component with Fluent Ui Components
# current
## current
allowMove from one Bucket to the Other tested
move task to other Bucket works
@ -38,7 +38,8 @@ Something like this sould come out, but styling is currently bad
Something like this sould come out, but styling is currently bad
![prototype](./sample.gif "prototype on 2nd day")
IMPORTANT
# IMPORTANT
```
IKanbanTask {
taskId: string;
@ -55,3 +56,21 @@ event.dataTransfer.setData('xyz','1')
Unexpected call to method or property access.
```
# Next Steps Component:
* think about Promise Task Actions, because actions are async
* internalDislplayRenderer: Person / Persons
* EditSchema To support Edit and New PNP Controls :)
* BucketEdit Component (can be used in CustomPropertyPane)
# Webpart Steps
* PNP Placeholder Control for Config
* PNP WebpartTitle Control
* DataConnection
* Usage of BucketEdit in pane
* PNP Order pane Control

View File

@ -1,9 +1,15 @@
define([], function() {
define([], function () {
return {
"CompleteButton": "Complete",
"IsCompleted": "Is Completed",
"IsNotCompleted": "Is Not Completed",
"AddTask": "Add Task",
"OpenDetails":"Open Details"
"IsCompleted": "Is Completed",
"IsNotCompleted": "Is Not Completed",
"AddTask": "Add Task",
"OpenDetails": "Open Details",
"EditTaskBtn": "Edit",
"SaveTaskBtn": "Save and Close",
"SaveAddTaskBtn": "Add and Close",
"CloseTaskDialog": "Close",
"AddTaskDlgHeadline":"Add Task",
"EditTaskDlgHeadline":"Edit Task",
}
});
});

View File

@ -4,6 +4,12 @@ declare interface IKanbanBoardStrings {
IsNotCompleted: string;
AddTask: string;
OpenDetails: string;
EditTaskBtn:string;
SaveTaskBtn:string;
SaveAddTaskBtn:string;
CloseTaskDialog:string;
AddTaskDlgHeadline:string;
EditTaskDlgHeadline:string;
}
declare module 'KanbanBoardStrings' {