add status grid

This commit is contained in:
marlon-erler
2024-07-25 04:12:30 +02:00
parent 31c942a810
commit 9e69c44014
9 changed files with 440 additions and 19 deletions
+2 -2
View File
@@ -40,7 +40,7 @@ function KanbanBoard(categoryName: string, boardViewModel: BoardViewModel) {
new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName);
return (
<div class="flex-column flex-no" style="width: 18rem; min-width: 18rem">
<div class="flex-column flex-no">
<div class="flex-row width-input">
<input
placeholder={
@@ -61,7 +61,7 @@ function KanbanBoard(categoryName: string, boardViewModel: BoardViewModel) {
<hr></hr>
<div
class="flex-column gap"
class="kanban-column"
children:append={[taskViewModels, TaskViewModelToEntry]}
></div>
</div>
+4
View File
@@ -6,6 +6,7 @@ import BoardViewModel, {
import { BoardKanbanPage } from "./boardKanbanPage";
import { BoardSettingsModal } from "../Modals/boardSettingsModal";
import { BoardStatusGridPage } from "./boardStatusGridPage";
import { BoardViewToggleButton } from "../Components/boardViewToggleButton";
import { TaskSettingsModal } from "../Modals/taskSettingsModal";
import { TaskViewModelToEntry } from "../Components/taskEntry";
@@ -21,6 +22,9 @@ export function BoardPage(boardViewModel: BoardViewModel) {
case BoardPageType.Kanban: {
return BoardKanbanPage(boardViewModel);
}
case BoardPageType.StatusGrid: {
return BoardStatusGridPage(boardViewModel);
}
default: {
return (
<div
+177
View File
@@ -0,0 +1,177 @@
import * as React from "bloatless-react";
import {
TaskCategoryBulkChangeViewModel,
TaskStatusBulkChangeViewModel,
} from "../../ViewModel/Pages/taskPropertyBulkChangeViewModel";
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
import { FilteredList } from "../Components/filteredList";
import { PropertyValueList } from "../Components/propertyList";
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
import { TaskViewModelToEntry } from "../Components/taskEntry";
import { collectObjectValuesForKey } from "../../Model/Utility/utility";
import { translations } from "../translations";
export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
const categoryRowConverter: React.StateItemConverter<string> = (
categoryName: string
) => {
return CategoryRow(categoryName, statuses, boardViewModel);
};
const statusNameCellConverter: React.StateItemConverter<string> = (
statusName: string
) => {
return StatusNameCell(statusName, boardViewModel);
};
const statuses: React.ListState<string> = new React.ListState();
boardViewModel.taskViewModels.subscribe(() => {
statuses.clear();
const statusArray: string[] = collectObjectValuesForKey(
"status",
(taskViewModel: TaskViewModel) => taskViewModel.task,
[...boardViewModel.taskViewModels.value.values()]
);
statuses.add(...statusArray);
});
return (
<div class="status-page-content">
<div
class="status-name-row"
children:append={[statuses, statusNameCellConverter]}
></div>
{PropertyValueList(
"category",
(taskViewModel: TaskViewModel) => taskViewModel.task,
boardViewModel.taskViewModels,
(categories: React.ListState<string>) => {
return (
<div
class="status-grid-wrapper"
children:append={[categories, categoryRowConverter]}
></div>
);
}
)}
</div>
);
}
function StatusNameCell(statusName: string, boardViewModel: BoardViewModel) {
const taskViewModelsWithMatchingStatus: React.ListState<TaskViewModel> =
new React.ListState();
boardViewModel.taskViewModels.handleAddition(
(taskViewModel: TaskViewModel) => {
const doesMatchStatus: boolean = taskViewModel.task.status == statusName;
if (doesMatchStatus == false) return;
taskViewModelsWithMatchingStatus.add(taskViewModel);
boardViewModel.taskViewModels.handleRemoval(taskViewModel, () => {
taskViewModelsWithMatchingStatus.remove(taskViewModel);
});
}
);
const viewModel: TaskStatusBulkChangeViewModel =
new TaskStatusBulkChangeViewModel(
taskViewModelsWithMatchingStatus,
statusName
);
return (
<div class="flex-row">
<div class="property-input-wrapper">
<input
placeholder={
translations.chatPage.task.renameCategoryInputPlaceholder
}
bind:value={viewModel.inputValue}
on:enter={viewModel.set}
></input>
<button
class="primary"
on:click={viewModel.set}
toggle:disabled={viewModel.cannotSet}
>
<span class="icon">check</span>
</button>
</div>
</div>
);
}
function CategoryRow(
categoryName: string,
allStatuses: React.ListState<string>,
boardViewModel: BoardViewModel
) {
return FilteredList(
{ category: categoryName },
(taskViewModel: TaskViewModel) => taskViewModel.task,
boardViewModel.taskViewModels,
(taskViewModels: React.ListState<TaskViewModel>) => {
const statusNameConverter: React.StateItemConverter<string> = (
statusName: string
) => {
return CategoryStatusColumn(statusName, taskViewModels);
};
const viewModel: TaskCategoryBulkChangeViewModel =
new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName);
return (
<div class="flex-row flex-no large-gap">
<div class="property-input-wrapper">
<input
placeholder={
translations.chatPage.task.renameCategoryInputPlaceholder
}
bind:value={viewModel.inputValue}
on:enter={viewModel.set}
></input>
<button
class="primary"
on:click={viewModel.set}
toggle:disabled={viewModel.cannotSet}
>
<span class="icon">check</span>
</button>
</div>
<div
class="flex-row large-gap padding-right"
children:append={[allStatuses, statusNameConverter]}
></div>
</div>
);
}
);
}
function CategoryStatusColumn(
statusName: string,
taskViewModelsWithMatchingCategory: React.ListState<TaskViewModel>
) {
const taskViewModels: React.ListState<TaskViewModel> = new React.ListState();
taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => {
const doesMatchStatus: boolean = taskViewModel.status.value == statusName;
if (doesMatchStatus == false) return;
taskViewModels.add(taskViewModel);
taskViewModelsWithMatchingCategory.handleRemoval(taskViewModel, () => {
taskViewModels.remove(taskViewModel);
});
});
return (
<div
class="status-column gap"
children:append={[taskViewModels, TaskViewModelToEntry]}
></div>
);
}
+41 -1
View File
@@ -24,7 +24,8 @@
padding: var(--content-padding);
}
#chat-page > div > #main > #task-page .kanban-board-wrapper {
#chat-page > div > #main > #task-page .kanban-board-wrapper,
#chat-page > div > #main > #task-page .status-page-content {
display: flex;
gap: var(--large-gap);
@@ -36,6 +37,45 @@
padding: var(--content-padding);
}
#chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column,
#chat-page > div > #main > #task-page .status-grid-wrapper .status-column {
display: flex;
flex-direction: column;
gap: var(--gap-between-tiles);
}
#chat-page > div > #main > #task-page .property-input-wrapper {
display: flex;
flex-direction: row;
}
#chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column,
#chat-page > div > #main > #task-page .property-input-wrapper,
#chat-page > div > #main > #task-page .status-grid-wrapper .status-column,
#chat-page > div > #main > #task-page .status-name-row > *,
#chat-page > div > #main > #task-page .status-name-row::before {
width: 18rem;
min-width: 18rem;
max-width: 18rem;
}
#chat-page > div > #main > #task-page .status-name-row,
#chat-page > div > #main > #task-page .status-grid-wrapper {
display: flex;
gap: var(--large-gap);
}
#chat-page > div > #main > #task-page .status-page-content,
#chat-page > div > #main > #task-page .status-grid-wrapper {
flex-direction: column;
}
#chat-page > div > #main > #task-page .status-name-row::before {
content: "";
display: flex;
}
@media (min-width: 640px) {
#chat-page > div > #main > #task-page .task-grid {
display: grid;
+1 -1
View File
@@ -9,7 +9,7 @@ export function PropertyValueList<T>(
propertyKey: string,
stringEntryObjectConverter: (object: T) => StringEntryObject,
objects: React.ListState<T> | React.MapState<T>,
viewBuilder: (categories: React.ListState<string>) => HTMLElement
viewBuilder: (keys: React.ListState<string>) => HTMLElement
) {
const propertyValues: React.ListState<string> = new React.ListState();
+4 -1
View File
@@ -13,7 +13,10 @@ export function TaskEntry(taskViewModel: TaskViewModel) {
};
const view = (
<button class="tile flex-no" on:click={taskViewModel.open}>
<button
class="tile flex-no"
on:click={taskViewModel.open}
>
<div>
<b subscribe:innerText={taskViewModel.name}></b>
<hr></hr>