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.
|
||||
- `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é.
|
||||
- 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 :
|
||||
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@ export function validateSiteContent(site) {
|
|||
"toolboxes.modules.taskPlanner.categoryLabel",
|
||||
"toolboxes.modules.taskPlanner.categoryPlaceholder",
|
||||
"toolboxes.modules.taskPlanner.categoryReorderTitle",
|
||||
"toolboxes.modules.taskPlanner.uncategorizedDropTitle",
|
||||
"toolboxes.modules.taskPlanner.showCategoryTitle",
|
||||
"toolboxes.modules.taskPlanner.hideCategoryTitle",
|
||||
"toolboxes.modules.taskPlanner.taskWeeklyResetDayLabel",
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@
|
|||
"categoryLabel": "Catégorie",
|
||||
"categoryPlaceholder": "Nom de catégorie",
|
||||
"categoryReorderTitle": "Déplacer la catégorie",
|
||||
"uncategorizedDropTitle": "Sans catégorie",
|
||||
"showCategoryTitle": "Afficher cette catégorie",
|
||||
"hideCategoryTitle": "Réduire cette catégorie",
|
||||
"taskWeeklyResetDayLabel": "Jour hebdo",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { InlineNotice } from "../../../components/AppOverlays.jsx";
|
|||
import { usePointerReorder } from "../../../hooks/usePointerReorder.js";
|
||||
|
||||
const TASK_TYPES = ["daily", "weekly", "unique"];
|
||||
const UNCATEGORIZED_DROP_ID = "task-planner:uncategorized";
|
||||
const WEEK_DAYS = [
|
||||
{ value: 1, label: "Lundi" },
|
||||
{ value: 2, label: "Mardi" },
|
||||
|
|
@ -252,6 +253,10 @@ function isCategorySectionId(id) {
|
|||
return String(id || "").startsWith("category\n");
|
||||
}
|
||||
|
||||
function isUncategorizedDropId(id) {
|
||||
return id === UNCATEGORIZED_DROP_ID;
|
||||
}
|
||||
|
||||
function getRootTaskIdsForCategory(tasks, parentMap, category) {
|
||||
return tasks
|
||||
.filter((task) => !parentMap.get(task.id) && getTaskCategory(task) === category)
|
||||
|
|
@ -275,29 +280,58 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing }) {
|
|||
dropTarget,
|
||||
startDrag
|
||||
} = usePointerReorder({
|
||||
targetSelector: ".task-planner-item, .task-planner-category-section",
|
||||
getTargetId: (target) => target.dataset.taskId || getCategorySectionId(target.dataset.parentId || "", target.dataset.category || ""),
|
||||
targetSelector: ".task-planner-item, .task-planner-category-section, .task-planner-uncategorized-drop-zone",
|
||||
getTargetId: (target) => target.dataset.dropId || target.dataset.taskId || getCategorySectionId(target.dataset.parentId || "", target.dataset.category || ""),
|
||||
canDropOn: (target, draggingTaskId) => {
|
||||
const draggingTask = data.tasks.find((task) => task.id === draggingTaskId);
|
||||
if (!draggingTask) return false;
|
||||
if (target.classList.contains("task-planner-category-section")) {
|
||||
return !parentMap.get(draggingTaskId) && !getTaskCategory(draggingTask) && !target.dataset.parentId && Boolean(target.dataset.category);
|
||||
const draggingParentId = parentMap.get(draggingTaskId) || "";
|
||||
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) || "")
|
||||
&& (
|
||||
target.dataset.category === getTaskCategoryDataset(draggingTask, data.tasks, parentMap)
|
||||
|| (!parentMap.get(draggingTaskId) && !getTaskCategory(draggingTask) && Boolean(target.dataset.category))
|
||||
);
|
||||
if (target.classList.contains("task-planner-category-section")) {
|
||||
return !draggingParentId && !target.dataset.parentId && Boolean(target.dataset.category) && target.dataset.category !== draggingCategory;
|
||||
}
|
||||
if (target.dataset.parentId !== draggingParentId) return false;
|
||||
if (target.dataset.category === draggingCategory) return true;
|
||||
return !draggingParentId;
|
||||
},
|
||||
onMove: (fromTaskId, toId, placement) => {
|
||||
if (isCategorySectionId(toId)) {
|
||||
const targetCategory = parseCategorySectionId(toId).category;
|
||||
const targetIds = getRootTaskIdsForCategory(data.tasks, parentMap, targetCategory);
|
||||
const fromTask = data.tasks.find((task) => task.id === fromTaskId);
|
||||
if (!fromTask) return;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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 {
|
||||
|
|
@ -608,6 +642,8 @@ function TaskPlannerBranch({
|
|||
const children = [...groupedTasks.uncategorized, ...groupedTasks.categories.flatMap((group) => group.tasks)].filter((task) => !visited.has(task.id));
|
||||
if (!children.length) return null;
|
||||
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()) {
|
||||
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);
|
||||
return renderCategory(entry.group);
|
||||
}).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({
|
||||
|
|
|
|||
|
|
@ -1369,6 +1369,31 @@ textarea:focus {
|
|||
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 {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue