add item status
This commit is contained in:
@@ -3,12 +3,12 @@ import * as React from "bloatless-react";
|
||||
import { Chat, MessageObject } from "../../Model/chatModel";
|
||||
|
||||
import { ObjectEntryView } from "./objectEntryView";
|
||||
import { ObjectGridView } from "./objectGridView";
|
||||
import { PlaceholderView } from "./placeholderView";
|
||||
import { RenameView } from "../renameView";
|
||||
import { translation } from "../../translations";
|
||||
|
||||
interface KanbanBoard {
|
||||
title: string;
|
||||
category: string;
|
||||
items: KanbanBoardItem[];
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function KanbanView(
|
||||
const boardTitle = latest.categoryName;
|
||||
|
||||
if (!boards.value.has(boardTitle))
|
||||
boards.set(boardTitle, { title: boardTitle, items: [] });
|
||||
boards.set(boardTitle, { category: boardTitle, items: [] });
|
||||
|
||||
boards.value.get(boardTitle)?.items.push({
|
||||
priority: latest.priority ?? 0,
|
||||
@@ -48,7 +48,7 @@ export function KanbanView(
|
||||
) : (
|
||||
<div class="flex-row large-gap width-100 height-100 scroll-v scroll-h padding">
|
||||
{...[...boards.value.values()]
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.sort((a, b) => a.category.localeCompare(b.category))
|
||||
.map((board) =>
|
||||
KanbanBoardView(chat, board, selectedObject, isShowingObjectModal)
|
||||
)}
|
||||
@@ -67,20 +67,13 @@ function KanbanBoardView(
|
||||
selectedObject: React.State<MessageObject | undefined>,
|
||||
isShowingObjectModal: React.State<boolean>
|
||||
) {
|
||||
const editingBoardTitle = new React.State(kanbanBoard.title);
|
||||
const isEditing = new React.State(false);
|
||||
const cannotRename = React.createProxyState(
|
||||
[editingBoardTitle],
|
||||
() =>
|
||||
editingBoardTitle.value == kanbanBoard.title ||
|
||||
editingBoardTitle.value == ""
|
||||
);
|
||||
const editingCategory = new React.State(kanbanBoard.category);
|
||||
|
||||
function renameBoard() {
|
||||
kanbanBoard.items.forEach((kanbanBoardItem) => {
|
||||
const { messageObject } = kanbanBoardItem;
|
||||
const latest = chat.getMostRecentContent(messageObject);
|
||||
latest.categoryName = editingBoardTitle.value;
|
||||
latest.categoryName = editingCategory.value;
|
||||
chat.addObjectAndSend(messageObject);
|
||||
});
|
||||
}
|
||||
@@ -98,32 +91,17 @@ function KanbanBoardView(
|
||||
if (!messageObject) return;
|
||||
|
||||
const latest = chat.getMostRecentContent(messageObject);
|
||||
latest.categoryName = kanbanBoard.title;
|
||||
latest.categoryName = kanbanBoard.category;
|
||||
chat.addObjectAndSend(messageObject);
|
||||
}
|
||||
|
||||
const titleInput = (
|
||||
<input bind:value={editingBoardTitle} on:enter={renameBoard}></input>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
class="flex-column flex-no"
|
||||
style="min-width: 280px"
|
||||
class="flex-column flex-no object-entry-wide"
|
||||
on:dragover={dragOver}
|
||||
on:drop={drop}
|
||||
>
|
||||
<div class="flex-row align-center justify-apart">
|
||||
{titleInput}
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={translation.rename}
|
||||
on:click={renameBoard}
|
||||
toggle:disabled={cannotRename}
|
||||
>
|
||||
<span class="icon">edit</span>
|
||||
</button>
|
||||
</div>
|
||||
{RenameView(editingCategory, kanbanBoard.category, renameBoard)}
|
||||
<hr></hr>
|
||||
<div class="flex-column gap padding-bottom">
|
||||
{...kanbanBoard.items
|
||||
|
||||
@@ -34,6 +34,10 @@ export function ObjectDetailModal(
|
||||
[selectedMessageObject],
|
||||
() => selectedMessageObject.value.categoryName ?? ""
|
||||
);
|
||||
const editingStatus = React.createProxyState(
|
||||
[selectedMessageObject],
|
||||
() => selectedMessageObject.value.status ?? ""
|
||||
);
|
||||
const editingDate = React.createProxyState(
|
||||
[selectedMessageObject],
|
||||
() => selectedMessageObject.value.date ?? ""
|
||||
@@ -50,6 +54,7 @@ export function ObjectDetailModal(
|
||||
[
|
||||
editingNoteContent,
|
||||
editingCategory,
|
||||
editingStatus,
|
||||
editingDate,
|
||||
editingTime,
|
||||
editingPriority,
|
||||
@@ -84,6 +89,7 @@ export function ObjectDetailModal(
|
||||
noteContent: editingNoteContent.value,
|
||||
priority: editingPriority.value,
|
||||
categoryName: editingCategory.value,
|
||||
status: editingStatus.value,
|
||||
date: editingDate.value,
|
||||
time: editingTime.value,
|
||||
});
|
||||
@@ -152,7 +158,7 @@ export function ObjectDetailModal(
|
||||
<div>
|
||||
<span>{translation.note}</span>
|
||||
<textarea
|
||||
rows="5"
|
||||
rows="10"
|
||||
class="height-auto"
|
||||
bind:value={editingNoteContent}
|
||||
placeholder={translation.noteContentPlaceholder}
|
||||
@@ -160,6 +166,8 @@ export function ObjectDetailModal(
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<hr></hr>
|
||||
|
||||
<label class="tile">
|
||||
<span class="icon">{icons.categoryName}</span>
|
||||
<div>
|
||||
@@ -189,6 +197,17 @@ export function ObjectDetailModal(
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="tile">
|
||||
<span class="icon">{icons.status}</span>
|
||||
<div>
|
||||
<span>{translation.status}</span>
|
||||
<input
|
||||
bind:value={editingStatus}
|
||||
placeholder={translation.statusPlaceholder}
|
||||
></input>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<hr></hr>
|
||||
|
||||
<label class="tile">
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ObjectEntryView(
|
||||
const fields = {
|
||||
[icons.noteContent]: "---",
|
||||
[icons.priority]: "---",
|
||||
[icons.categoryName]: "---",
|
||||
[icons.status]: "---",
|
||||
[icons.date]: "---",
|
||||
[icons.time]: "---",
|
||||
};
|
||||
@@ -39,6 +39,7 @@ export function ObjectEntryView(
|
||||
const icon = icons[key];
|
||||
if (!icon) return;
|
||||
|
||||
if (!fields[icon]) return;
|
||||
fields[icon] = value;
|
||||
});
|
||||
|
||||
@@ -60,7 +61,7 @@ export function ObjectEntryView(
|
||||
on:dragstart={dragStart}
|
||||
>
|
||||
<div>
|
||||
<b>{chat.getObjectTitle(messageObject)}</b>
|
||||
<b class="ellipsis">{chat.getObjectTitle(messageObject)}</b>
|
||||
<hr></hr>
|
||||
<span class="flex-column gap height-100 flex secondary ellipsis ">
|
||||
{...fieldElements}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AllObjectsView } from "./allObjectsView";
|
||||
import { KanbanView } from "./kanbanView";
|
||||
import { NoteObjectsView } from "./noteObjectsView";
|
||||
import { ObjectDetailModal } from "./objectDetailModal";
|
||||
import { StatusView } from "./statusView";
|
||||
import { icons } from "../../icons";
|
||||
import { translation } from "../../translations";
|
||||
|
||||
@@ -13,6 +14,7 @@ export const viewTypes = {
|
||||
all: [translation.viewAll, "grid_view"],
|
||||
notes: [translation.viewNotes, icons.noteContent],
|
||||
kanban: [translation.viewKanban, "view_kanban"],
|
||||
status: [translation.viewStatus, icons.status],
|
||||
};
|
||||
|
||||
export function ObjectPane(chat: Chat) {
|
||||
@@ -40,6 +42,8 @@ export function ObjectPane(chat: Chat) {
|
||||
return NoteObjectsView;
|
||||
case "kanban":
|
||||
return KanbanView;
|
||||
case "status":
|
||||
return StatusView;
|
||||
default:
|
||||
return AllObjectsView;
|
||||
}
|
||||
@@ -78,7 +82,10 @@ export function ObjectPane(chat: Chat) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="width-100 height-100 flex scroll-no" children:set={mainView}></div>
|
||||
<div
|
||||
class="width-100 height-100 flex scroll-no"
|
||||
children:set={mainView}
|
||||
></div>
|
||||
|
||||
<div children:set={objectModal}></div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import * as React from "bloatless-react";
|
||||
|
||||
import { Chat, MessageObject } from "../../Model/chatModel";
|
||||
|
||||
import { ObjectEntryView } from "./objectEntryView";
|
||||
import { PlaceholderView } from "./placeholderView";
|
||||
import { RenameView } from "../renameView";
|
||||
import { translation } from "../../translations";
|
||||
|
||||
interface KanbanRow {
|
||||
category: string;
|
||||
statusColumns: Map<string, StatusColumn>;
|
||||
}
|
||||
|
||||
interface StatusColumn {
|
||||
title: string;
|
||||
items: StatusCellItem[];
|
||||
}
|
||||
|
||||
interface StatusCellItem {
|
||||
priority: number;
|
||||
status: string;
|
||||
messageObject: MessageObject;
|
||||
}
|
||||
|
||||
interface StatusItemList {
|
||||
title: string;
|
||||
items: MessageObject[];
|
||||
}
|
||||
|
||||
export function StatusView(
|
||||
chat: Chat,
|
||||
selectedObject: React.State<MessageObject | undefined>,
|
||||
isShowingObjectModal: React.State<boolean>
|
||||
) {
|
||||
const rows = new React.MapState<KanbanRow>();
|
||||
const statuses = new Map<string, StatusItemList>();
|
||||
|
||||
chat.objects.subscribe(() => {
|
||||
rows.clear();
|
||||
statuses.clear();
|
||||
|
||||
// add items
|
||||
chat.objects.value.forEach((messageObject) => {
|
||||
const latest = chat.getMostRecentContent(messageObject);
|
||||
if (!latest.categoryName || !latest.status) return;
|
||||
|
||||
const category = latest.categoryName;
|
||||
const status = latest.status;
|
||||
|
||||
if (!statuses.has(status)) {
|
||||
statuses.set(status, { title: status, items: [] });
|
||||
}
|
||||
statuses.get(status)!.items.push(messageObject);
|
||||
|
||||
if (!rows.value.has(category)) {
|
||||
rows.set(category, { category: category, statusColumns: new Map() });
|
||||
}
|
||||
const row = rows.value.get(category)!;
|
||||
|
||||
if (!row.statusColumns.has(status)) {
|
||||
row.statusColumns.set(status, { title: status, items: [] });
|
||||
}
|
||||
const column = row.statusColumns.get(status)!;
|
||||
|
||||
column.items.push({
|
||||
priority: latest.priority ?? 0,
|
||||
status,
|
||||
messageObject,
|
||||
});
|
||||
});
|
||||
|
||||
// add status placeholders
|
||||
statuses.forEach((statusItemList) => {
|
||||
rows.value.forEach((row) => {
|
||||
if (row.statusColumns.has(statusItemList.title)) return;
|
||||
row.statusColumns.set(statusItemList.title, {
|
||||
title: statusItemList.title,
|
||||
items: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const content = React.createProxyState([chat.objects], () =>
|
||||
rows.value.size == 0 ? (
|
||||
PlaceholderView()
|
||||
) : (
|
||||
<div class="flex-column large-gap width-100 height-100 scroll-v scroll-h padding">
|
||||
<div class="flex-row large-gap">
|
||||
<div class="flex-column object-entry-wide"></div>
|
||||
{...[...statuses.values()]
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.map((status) => StatusHeaderView(chat, status))}
|
||||
</div>
|
||||
{...[...rows.value.values()]
|
||||
.sort((a, b) => a.category.localeCompare(b.category))
|
||||
.map((board) =>
|
||||
KanbanRowView(chat, board, selectedObject, isShowingObjectModal)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="width-100 height-100 scroll-no" children:set={content}></div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusHeaderView(chat: Chat, status: StatusItemList) {
|
||||
const editingStatus = new React.State(status.title);
|
||||
|
||||
function rename() {
|
||||
status.items.forEach((messageObject) => {
|
||||
const latest = chat.getMostRecentContent(messageObject);
|
||||
if (!latest) return;
|
||||
|
||||
latest.status = editingStatus.value;
|
||||
chat.addObject(messageObject);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="object-entry-wide">
|
||||
{RenameView(editingStatus, status.title, rename)}
|
||||
<hr></hr>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function KanbanRowView(
|
||||
chat: Chat,
|
||||
kanbanRow: KanbanRow,
|
||||
selectedObject: React.State<MessageObject | undefined>,
|
||||
isShowingObjectModal: React.State<boolean>
|
||||
) {
|
||||
const editingCategory = new React.State(kanbanRow.category);
|
||||
|
||||
const cellItemEntries = [...kanbanRow.statusColumns.entries()];
|
||||
|
||||
function renameCategory() {
|
||||
kanbanRow.statusColumns.forEach((statusColumns) => {
|
||||
statusColumns.items.forEach((statusCellItem) => {
|
||||
const { messageObject } = statusCellItem;
|
||||
const latest = chat.getMostRecentContent(messageObject);
|
||||
latest.categoryName = editingCategory.value;
|
||||
chat.addObjectAndSend(messageObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="flex-row flex-no large-gap">
|
||||
<div class="flex-row align-start">
|
||||
{RenameView(editingCategory, kanbanRow.category, renameCategory)}
|
||||
</div>
|
||||
|
||||
{...cellItemEntries
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.map((entry) =>
|
||||
StatusColumnView(
|
||||
chat,
|
||||
entry[1].items,
|
||||
kanbanRow.category,
|
||||
entry[1].title,
|
||||
selectedObject,
|
||||
isShowingObjectModal
|
||||
)
|
||||
)}
|
||||
|
||||
<hr></hr>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusColumnView(
|
||||
chat: Chat,
|
||||
items: StatusCellItem[],
|
||||
category: string,
|
||||
status: string,
|
||||
selectedObject: React.State<MessageObject | undefined>,
|
||||
isShowingObjectModal: React.State<boolean>
|
||||
) {
|
||||
function dragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function drop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
var id = event.dataTransfer?.getData("text");
|
||||
if (!id) return;
|
||||
|
||||
const messageObject = chat.objects.value.get(id);
|
||||
if (!messageObject) return;
|
||||
|
||||
const latest = chat.getMostRecentContent(messageObject);
|
||||
latest.status = status;
|
||||
latest.categoryName = category;
|
||||
chat.addObjectAndSend(messageObject);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class="flex-column gap object-entry-wide"
|
||||
on:dragover={dragOver}
|
||||
on:drop={drop}
|
||||
>
|
||||
{...items
|
||||
.sort((a, b) => b.priority - a.priority)
|
||||
.map((kanbanBoardItem) =>
|
||||
ObjectEntryView(
|
||||
chat,
|
||||
kanbanBoardItem.messageObject,
|
||||
selectedObject,
|
||||
isShowingObjectModal
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user