allow drag in and out of categorie
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
This commit is contained in:
parent
3726b2330b
commit
ee579af2ec
5 changed files with 91 additions and 16 deletions
|
|
@ -319,7 +319,7 @@ Notes :
|
||||||
- une tâche ne conserve qu'un seul parent.
|
- une tâche ne conserve qu'un seul parent.
|
||||||
- `prerequisite: true` marque aussi ce parent comme pré requis.
|
- `prerequisite: true` marque aussi ce parent comme pré requis.
|
||||||
- Les pré requis sont non bloquants : ils affichent un avertissement si le parent requis n'est pas effectué.
|
- Les pré requis sont non bloquants : ils affichent un avertissement si le parent requis n'est pas effectué.
|
||||||
- En tri manuel, une tâche catégorisée ne peut être réordonnée qu'avec des tâches du même parent et de la même catégorie.
|
- Le drag & drop conserve l'ordre manuel, et peut aussi ajouter une tâche racine à une catégorie existante ou la sortir vers les tâches sans catégorie.
|
||||||
|
|
||||||
Stockage compact :
|
Stockage compact :
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,7 @@ export function validateSiteContent(site) {
|
||||||
"toolboxes.modules.taskPlanner.categoryLabel",
|
"toolboxes.modules.taskPlanner.categoryLabel",
|
||||||
"toolboxes.modules.taskPlanner.categoryPlaceholder",
|
"toolboxes.modules.taskPlanner.categoryPlaceholder",
|
||||||
"toolboxes.modules.taskPlanner.categoryReorderTitle",
|
"toolboxes.modules.taskPlanner.categoryReorderTitle",
|
||||||
|
"toolboxes.modules.taskPlanner.uncategorizedDropTitle",
|
||||||
"toolboxes.modules.taskPlanner.showCategoryTitle",
|
"toolboxes.modules.taskPlanner.showCategoryTitle",
|
||||||
"toolboxes.modules.taskPlanner.hideCategoryTitle",
|
"toolboxes.modules.taskPlanner.hideCategoryTitle",
|
||||||
"toolboxes.modules.taskPlanner.taskWeeklyResetDayLabel",
|
"toolboxes.modules.taskPlanner.taskWeeklyResetDayLabel",
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,7 @@
|
||||||
"categoryLabel": "Catégorie",
|
"categoryLabel": "Catégorie",
|
||||||
"categoryPlaceholder": "Nom de catégorie",
|
"categoryPlaceholder": "Nom de catégorie",
|
||||||
"categoryReorderTitle": "Déplacer la catégorie",
|
"categoryReorderTitle": "Déplacer la catégorie",
|
||||||
|
"uncategorizedDropTitle": "Sans catégorie",
|
||||||
"showCategoryTitle": "Afficher cette catégorie",
|
"showCategoryTitle": "Afficher cette catégorie",
|
||||||
"hideCategoryTitle": "Réduire cette catégorie",
|
"hideCategoryTitle": "Réduire cette catégorie",
|
||||||
"taskWeeklyResetDayLabel": "Jour hebdo",
|
"taskWeeklyResetDayLabel": "Jour hebdo",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { InlineNotice } from "../../../components/AppOverlays.jsx";
|
||||||
import { usePointerReorder } from "../../../hooks/usePointerReorder.js";
|
import { usePointerReorder } from "../../../hooks/usePointerReorder.js";
|
||||||
|
|
||||||
const TASK_TYPES = ["daily", "weekly", "unique"];
|
const TASK_TYPES = ["daily", "weekly", "unique"];
|
||||||
|
const UNCATEGORIZED_DROP_ID = "task-planner:uncategorized";
|
||||||
const WEEK_DAYS = [
|
const WEEK_DAYS = [
|
||||||
{ value: 1, label: "Lundi" },
|
{ value: 1, label: "Lundi" },
|
||||||
{ value: 2, label: "Mardi" },
|
{ value: 2, label: "Mardi" },
|
||||||
|
|
@ -252,6 +253,10 @@ function isCategorySectionId(id) {
|
||||||
return String(id || "").startsWith("category\n");
|
return String(id || "").startsWith("category\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUncategorizedDropId(id) {
|
||||||
|
return id === UNCATEGORIZED_DROP_ID;
|
||||||
|
}
|
||||||
|
|
||||||
function getRootTaskIdsForCategory(tasks, parentMap, category) {
|
function getRootTaskIdsForCategory(tasks, parentMap, category) {
|
||||||
return tasks
|
return tasks
|
||||||
.filter((task) => !parentMap.get(task.id) && getTaskCategory(task) === category)
|
.filter((task) => !parentMap.get(task.id) && getTaskCategory(task) === category)
|
||||||
|
|
@ -275,29 +280,58 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing }) {
|
||||||
dropTarget,
|
dropTarget,
|
||||||
startDrag
|
startDrag
|
||||||
} = usePointerReorder({
|
} = usePointerReorder({
|
||||||
targetSelector: ".task-planner-item, .task-planner-category-section",
|
targetSelector: ".task-planner-item, .task-planner-category-section, .task-planner-uncategorized-drop-zone",
|
||||||
getTargetId: (target) => target.dataset.taskId || getCategorySectionId(target.dataset.parentId || "", target.dataset.category || ""),
|
getTargetId: (target) => target.dataset.dropId || target.dataset.taskId || getCategorySectionId(target.dataset.parentId || "", target.dataset.category || ""),
|
||||||
canDropOn: (target, draggingTaskId) => {
|
canDropOn: (target, draggingTaskId) => {
|
||||||
const draggingTask = data.tasks.find((task) => task.id === draggingTaskId);
|
const draggingTask = data.tasks.find((task) => task.id === draggingTaskId);
|
||||||
if (!draggingTask) return false;
|
if (!draggingTask) return false;
|
||||||
if (target.classList.contains("task-planner-category-section")) {
|
const draggingParentId = parentMap.get(draggingTaskId) || "";
|
||||||
return !parentMap.get(draggingTaskId) && !getTaskCategory(draggingTask) && !target.dataset.parentId && Boolean(target.dataset.category);
|
const draggingCategory = getTaskCategoryDataset(draggingTask, data.tasks, parentMap);
|
||||||
|
if (target.classList.contains("task-planner-uncategorized-drop-zone")) {
|
||||||
|
return !draggingParentId && Boolean(draggingCategory);
|
||||||
}
|
}
|
||||||
return target.dataset.parentId === (parentMap.get(draggingTaskId) || "")
|
if (target.classList.contains("task-planner-category-section")) {
|
||||||
&& (
|
return !draggingParentId && !target.dataset.parentId && Boolean(target.dataset.category) && target.dataset.category !== draggingCategory;
|
||||||
target.dataset.category === getTaskCategoryDataset(draggingTask, data.tasks, parentMap)
|
}
|
||||||
|| (!parentMap.get(draggingTaskId) && !getTaskCategory(draggingTask) && Boolean(target.dataset.category))
|
if (target.dataset.parentId !== draggingParentId) return false;
|
||||||
);
|
if (target.dataset.category === draggingCategory) return true;
|
||||||
|
return !draggingParentId;
|
||||||
},
|
},
|
||||||
onMove: (fromTaskId, toId, placement) => {
|
onMove: (fromTaskId, toId, placement) => {
|
||||||
if (isCategorySectionId(toId)) {
|
const fromTask = data.tasks.find((task) => task.id === fromTaskId);
|
||||||
const targetCategory = parseCategorySectionId(toId).category;
|
if (!fromTask) return;
|
||||||
const targetIds = getRootTaskIdsForCategory(data.tasks, parentMap, targetCategory);
|
function setRootTaskCategory(tasks, category) {
|
||||||
|
return tasks.map((task) => {
|
||||||
|
if (task.id !== fromTaskId) return task;
|
||||||
|
const nextTask = { ...task };
|
||||||
|
if (category) nextTask.category = category;
|
||||||
|
else delete nextTask.category;
|
||||||
|
return nextTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function moveToCategory(category) {
|
||||||
|
const nextTasks = setRootTaskCategory(data.tasks, category);
|
||||||
|
const targetIds = category
|
||||||
|
? getRootTaskIdsForCategory(data.tasks, parentMap, category).filter((taskId) => taskId !== fromTaskId)
|
||||||
|
: data.tasks.filter((task) => !parentMap.get(task.id) && !getTaskCategory(task) && task.id !== fromTaskId).map((task) => task.id);
|
||||||
const boundaryTaskId = getBoundaryTaskId(data.tasks, targetIds, placement);
|
const boundaryTaskId = getBoundaryTaskId(data.tasks, targetIds, placement);
|
||||||
if (boundaryTaskId) save({ ...data, tasks: moveTask(data.tasks, fromTaskId, boundaryTaskId, placement) });
|
save({ ...data, tasks: boundaryTaskId ? moveTask(nextTasks, fromTaskId, boundaryTaskId, placement) : nextTasks });
|
||||||
|
}
|
||||||
|
if (isUncategorizedDropId(toId)) {
|
||||||
|
moveToCategory("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
save({ ...data, tasks: moveTask(data.tasks, fromTaskId, toId, placement) });
|
if (isCategorySectionId(toId)) {
|
||||||
|
const targetCategory = parseCategorySectionId(toId).category;
|
||||||
|
moveToCategory(targetCategory);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetTask = data.tasks.find((task) => task.id === toId);
|
||||||
|
const targetCategory = targetTask ? getTaskCategoryDataset(targetTask, data.tasks, parentMap) : "";
|
||||||
|
const sourceCategory = getTaskCategoryDataset(fromTask, data.tasks, parentMap);
|
||||||
|
const shouldChangeRootCategory = !parentMap.get(fromTaskId) && targetTask && !parentMap.get(toId) && targetCategory !== sourceCategory;
|
||||||
|
const tasks = shouldChangeRootCategory ? setRootTaskCategory(data.tasks, targetCategory) : data.tasks;
|
||||||
|
save({ ...data, tasks: moveTask(tasks, fromTaskId, toId, placement) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const {
|
const {
|
||||||
|
|
@ -608,6 +642,8 @@ function TaskPlannerBranch({
|
||||||
const children = [...groupedTasks.uncategorized, ...groupedTasks.categories.flatMap((group) => group.tasks)].filter((task) => !visited.has(task.id));
|
const children = [...groupedTasks.uncategorized, ...groupedTasks.categories.flatMap((group) => group.tasks)].filter((task) => !visited.has(task.id));
|
||||||
if (!children.length) return null;
|
if (!children.length) return null;
|
||||||
const nextVisited = new Set([...visited, ...children.map((task) => task.id)]);
|
const nextVisited = new Set([...visited, ...children.map((task) => task.id)]);
|
||||||
|
const draggingTask = draggingId ? getTaskById(tasks, draggingId) : null;
|
||||||
|
const showUncategorizedDropZone = !parentId && draggingTask && !parentMap.get(draggingTask.id) && Boolean(getTaskCategoryDataset(draggingTask, tasks, parentMap));
|
||||||
|
|
||||||
function hasVisibleDescendants(taskId, seen = new Set()) {
|
function hasVisibleDescendants(taskId, seen = new Set()) {
|
||||||
if (seen.has(taskId)) return false;
|
if (seen.has(taskId)) return false;
|
||||||
|
|
@ -739,10 +775,22 @@ function TaskPlannerBranch({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupedTasks.entries.map((entry) => {
|
const entries = groupedTasks.entries.map((entry) => {
|
||||||
if (entry.type === "task") return visited.has(entry.task.id) ? null : renderTask(entry.task);
|
if (entry.type === "task") return visited.has(entry.task.id) ? null : renderTask(entry.task);
|
||||||
return renderCategory(entry.group);
|
return renderCategory(entry.group);
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
|
if (!showUncategorizedDropZone) return entries;
|
||||||
|
const dropZoneClassName = [
|
||||||
|
"task-planner-uncategorized-drop-zone",
|
||||||
|
dropTarget.id === UNCATEGORIZED_DROP_ID ? "is-drop-target" : "",
|
||||||
|
dropTarget.id === UNCATEGORIZED_DROP_ID && dropTarget.placement === "after" ? "drop-after" : ""
|
||||||
|
].filter(Boolean).join(" ");
|
||||||
|
return [
|
||||||
|
<li className={dropZoneClassName} key={UNCATEGORIZED_DROP_ID} data-drop-id={UNCATEGORIZED_DROP_ID}>
|
||||||
|
{textContent.uncategorizedDropTitle || "Sans catégorie"}
|
||||||
|
</li>,
|
||||||
|
...entries
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function TaskPlannerItem({
|
function TaskPlannerItem({
|
||||||
|
|
|
||||||
|
|
@ -1369,6 +1369,31 @@ textarea:focus {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-planner-uncategorized-drop-zone {
|
||||||
|
padding: 9px 12px;
|
||||||
|
border: 1px dashed rgba(165, 180, 252, 0.22);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: rgba(15, 23, 42, 0.26);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-planner-uncategorized-drop-zone.is-drop-target {
|
||||||
|
border-color: rgba(246, 196, 83, 0.72);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(246, 196, 83, 0.08), transparent 56%),
|
||||||
|
rgba(15, 23, 42, 0.34);
|
||||||
|
color: var(--color-accent-gold);
|
||||||
|
box-shadow: inset 0 3px 0 rgba(246, 196, 83, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-planner-uncategorized-drop-zone.is-drop-target.drop-after {
|
||||||
|
box-shadow: inset 0 -3px 0 rgba(246, 196, 83, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.task-planner-category-section {
|
.task-planner-category-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue