drag and drop
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-11 12:30:19 +03:30
parent eb2f23c403
commit 328402fd7f
9 changed files with 339 additions and 91 deletions
+90 -60
View File
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@headlessui/react": "^2.2.0",
"@hello-pangea/dnd": "^18.0.1",
"@material-tailwind/react": "^2.1.10",
"@tanstack/react-query": "^5.62.8",
"axios": "^1.7.9",
@@ -1568,7 +1569,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1585,7 +1585,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1602,7 +1601,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1619,7 +1617,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1636,7 +1633,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1653,7 +1649,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1670,7 +1665,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1687,7 +1681,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1704,7 +1697,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1721,7 +1713,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1738,7 +1729,6 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1755,7 +1745,6 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1772,7 +1761,6 @@
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1789,7 +1777,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1806,7 +1793,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1823,7 +1809,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1840,7 +1825,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1857,7 +1841,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1874,7 +1857,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1891,7 +1873,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1908,7 +1889,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1925,7 +1905,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1942,7 +1921,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1959,7 +1937,6 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1976,7 +1953,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2215,6 +2191,23 @@
"react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/@hello-pangea/dnd": {
"version": "18.0.1",
"resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz",
"integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.26.7",
"css-box-model": "^1.2.1",
"raf-schd": "^4.0.3",
"react-redux": "^9.2.0",
"redux": "^5.0.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -2747,7 +2740,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2761,7 +2753,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2775,7 +2766,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2789,7 +2779,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2803,7 +2792,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2817,7 +2805,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2831,7 +2818,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2845,7 +2831,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2859,7 +2844,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2873,7 +2857,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2887,7 +2870,6 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2901,7 +2883,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2915,7 +2896,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2929,7 +2909,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2943,7 +2922,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2957,7 +2935,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2971,7 +2948,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2985,7 +2961,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2999,7 +2974,6 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3013,7 +2987,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3098,7 +3071,7 @@
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.20.7",
@@ -3112,7 +3085,7 @@
"version": "7.6.8",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
"integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
@@ -3122,7 +3095,7 @@
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.1.0",
@@ -3133,7 +3106,7 @@
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.20.7"
@@ -3235,6 +3208,12 @@
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz",
@@ -4136,6 +4115,15 @@
"node": ">=8"
}
},
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"license": "MIT",
"dependencies": {
"tiny-invariant": "^1.0.6"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -4491,7 +4479,6 @@
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -6050,7 +6037,7 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@@ -6434,7 +6421,6 @@
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
"type": "github",
@@ -6786,7 +6772,6 @@
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -7045,6 +7030,12 @@
"node": ">= 12.0.0"
}
},
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
"license": "MIT"
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -7226,6 +7217,29 @@
"react-dom": ">=16.8.6 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-redux": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz",
"integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -7320,6 +7334,12 @@
"node": ">=8.10.0"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -7496,7 +7516,6 @@
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.7"
@@ -7840,7 +7859,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -8352,6 +8370,12 @@
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
"license": "MIT"
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@@ -8557,7 +8581,7 @@
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -8745,6 +8769,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -8756,7 +8789,6 @@
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
@@ -8861,7 +8893,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -8879,7 +8910,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -9477,7 +9507,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"dev": true,
"devOptional": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
+1
View File
@@ -11,6 +11,7 @@
},
"dependencies": {
"@headlessui/react": "^2.2.0",
"@hello-pangea/dnd": "^18.0.1",
"@material-tailwind/react": "^2.1.10",
"@tanstack/react-query": "^5.62.8",
"axios": "^1.7.9",
+67 -8
View File
@@ -1,23 +1,82 @@
import { Add, AddCircle, CloseCircle, More } from "iconsax-react";
import { Children, useState, type FC, type ReactNode } from "react";
import { useState, type FC } from "react";
import type { Column as ColumnType, Task as TaskType } from "../types";
import Task from "./Task";
type Props = {
title: string;
children?: ReactNode;
column: ColumnType;
tasks: TaskType[];
draggedTaskId: string | null;
onDragStart: (taskId: string) => void;
onDragEnd: () => void;
onDrop: (columnId: string, index: number) => void;
};
const Column: FC<Props> = ({ title, children }) => {
const Column: FC<Props> = ({
column,
tasks,
draggedTaskId,
onDragStart,
onDragEnd,
onDrop,
}) => {
const [isAdding, setIsAdding] = useState(false);
const hasTasks = Children.count(children) > 0;
const [isDragOver, setIsDragOver] = useState(false);
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
setIsDragOver(true);
};
const handleDragLeave = (e: React.DragEvent) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDragOver(false);
}
};
const handleDrop = (e: React.DragEvent, index: number) => {
e.preventDefault();
e.stopPropagation();
setIsDragOver(false);
onDrop(column.id, index);
};
return (
<div className="bg-[#F0F3F7] rounded-xl p-6 max-w-[310px] w-full flex flex-col self-start">
<div
className={`bg-[#F0F3F7] rounded-xl p-6 max-w-[310px] w-full flex flex-col self-start transition-colors ${
isDragOver ? "ring-2 ring-primary/40" : ""
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, tasks.length)}
>
<div className="flex justify-between items-center shrink-0">
<div className="font-bold text-sm">{title}</div>
<div className="font-bold text-sm">{column.title}</div>
<More size={20} color="black" />
</div>
<div className={hasTasks ? "mt-5" : ""}>{children}</div>
{tasks.length > 0 && (
<div className="mt-5 flex flex-col gap-4">
{tasks.map((task, index) => (
<div
key={task.id}
onDragOver={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onDrop={(e) => handleDrop(e, index)}
>
<Task
task={task}
isDragging={draggedTaskId === task.id}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
/>
</div>
))}
</div>
)}
{isAdding ? (
<div className="mt-5 shrink-0 flex flex-col gap-3">
+26 -7
View File
@@ -1,25 +1,44 @@
import { AttachCircle, Calendar, TickSquare } from "iconsax-react";
import { type FC } from "react";
import type { Task as TaskType } from "../types";
const Task: FC = () => {
type Props = {
task: TaskType;
isDragging?: boolean;
onDragStart: (taskId: string) => void;
onDragEnd: () => void;
};
const Task: FC<Props> = ({ task, isDragging, onDragStart, onDragEnd }) => {
return (
<div className="bg-white rounded-lg p-2">
<div
draggable
onDragStart={() => onDragStart(task.id)}
onDragEnd={onDragEnd}
className={`bg-white rounded-lg p-2 cursor-grab active:cursor-grabbing transition-opacity ${
isDragging ? "opacity-40" : ""
}`}
>
<div className="h-10 bg-yellow-500 rounded-lg"></div>
<div className="bg-[#FF76C2] h-5 px-3 flex items-center mt-2 text-[10px] text-white rounded-md w-fit">برچشب</div>
<div className="font-bold text-xs mt-2">اضافه شدن پیش نمایش</div>
<div className="bg-[#FF76C2] h-5 px-3 flex items-center mt-2 text-[10px] text-white rounded-md w-fit">
{task.tag}
</div>
<div className="font-bold text-xs mt-2">{task.title}</div>
<div className="mt-2 flex items-center gap-4 text-xs">
<div className="flex items-center gap-1 text-[10px]">
<Calendar size={16} color="#292D32" />
<div>۱ اردیبهشت - ۲۰ اردیبهشت</div>
<div>{task.dateRange}</div>
</div>
<div className="flex gap-1">
<AttachCircle size={16} color="#292D32" />
<div>1</div>
<div>{task.attachments}</div>
</div>
<div className="flex gap-1">
<TickSquare size={16} color="#292D32" />
<div>1/5</div>
<div>
{task.checklistDone}/{task.checklistTotal}
</div>
</div>
</div>
+53
View File
@@ -0,0 +1,53 @@
{
"columns": [
{ "id": "todo", "title": "برای انجام" },
{ "id": "in-progress", "title": "درحال انجام" },
{ "id": "done", "title": "انجام شده" }
],
"tasks": [
{
"id": "task-1",
"columnId": "todo",
"order": 0,
"title": "اضافه شدن پیش نمایش",
"tag": "برچشب",
"dateRange": "۱ اردیبهشت - ۲۰ اردیبهشت",
"attachments": 1,
"checklistDone": 1,
"checklistTotal": 5
},
{
"id": "task-2",
"columnId": "todo",
"order": 1,
"title": "طراحی صفحه اصلی",
"tag": "طراحی",
"dateRange": "۵ اردیبهشت - ۱۵ اردیبهشت",
"attachments": 2,
"checklistDone": 3,
"checklistTotal": 8
},
{
"id": "task-3",
"columnId": "todo",
"order": 2,
"title": "بررسی فیدبک کاربران",
"tag": "بازبینی",
"dateRange": "۱۰ اردیبهشت - ۱۲ اردیبهشت",
"attachments": 0,
"checklistDone": 0,
"checklistTotal": 3
},
{
"id": "task-4",
"columnId": "done",
"order": 0,
"title": "تنظیمات اولیه پروژه",
"tag": "راه‌اندازی",
"dateRange": "۱ فروردین - ۵ فروردین",
"attachments": 1,
"checklistDone": 5,
"checklistTotal": 5
}
]
}
+21
View File
@@ -0,0 +1,21 @@
export type Column = {
id: string;
title: string;
};
export type Task = {
id: string;
columnId: string;
order: number;
title: string;
tag: string;
dateRange: string;
attachments: number;
checklistDone: number;
checklistTotal: number;
};
export type WorkspaceData = {
columns: Column[];
tasks: Task[];
};
@@ -0,0 +1,38 @@
import type { Task } from "../types";
export const reorderTasks = (
tasks: Task[],
taskId: string,
destColumnId: string,
destIndex: number,
): Task[] => {
const task = tasks.find((t) => t.id === taskId);
if (!task) return tasks;
const sourceColumnId = task.columnId;
const without = tasks.filter((t) => t.id !== taskId);
const destColumnTasks = without
.filter((t) => t.columnId === destColumnId)
.sort((a, b) => a.order - b.order);
destColumnTasks.splice(destIndex, 0, { ...task, columnId: destColumnId });
const updatedDest = destColumnTasks.map((t, i) => ({ ...t, order: i }));
if (sourceColumnId === destColumnId) {
const rest = without.filter((t) => t.columnId !== destColumnId);
return [...rest, ...updatedDest];
}
const sourceTasks = without
.filter((t) => t.columnId === sourceColumnId)
.sort((a, b) => a.order - b.order)
.map((t, i) => ({ ...t, order: i }));
const rest = without.filter(
(t) => t.columnId !== destColumnId && t.columnId !== sourceColumnId,
);
return [...rest, ...sourceTasks, ...updatedDest];
};
+42 -16
View File
@@ -1,28 +1,54 @@
import { type FC } from "react";
import { useMemo, useState, type FC } from "react";
import AddNewColumn from "./components/AddNewColumn";
import Column from "./components/Column";
import HeaderWorkspace from "./components/HeaderWorkspace";
import Task from "./components/Task";
import tasksData from "./data/tasks.json";
import type { Task, WorkspaceData } from "./types";
import { reorderTasks } from "./utils/reorderTasks";
const Workspace: FC = () => {
const { columns } = tasksData as WorkspaceData;
const [tasks, setTasks] = useState<Task[]>(tasksData.tasks as Task[]);
const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null);
const tasksByColumn = useMemo(() => {
return columns.reduce<Record<string, Task[]>>((acc, column) => {
acc[column.id] = tasks
.filter((task) => task.columnId === column.id)
.sort((a, b) => a.order - b.order);
return acc;
}, {});
}, [columns, tasks]);
const handleDragStart = (taskId: string) => {
setDraggedTaskId(taskId);
};
const handleDragEnd = () => {
setDraggedTaskId(null);
};
const handleDrop = (columnId: string, index: number) => {
if (!draggedTaskId) return;
setTasks((prev) => reorderTasks(prev, draggedTaskId, columnId, index));
setDraggedTaskId(null);
};
return (
<div className="bg-[#01347A] h-full rounded-[32px] overflow-hidden flex flex-col">
<HeaderWorkspace />
<div className="px-8 flex gap-7 mt-6 flex-1 min-h-0 overflow-auto pb-6 items-start">
<Column title="برای انجام">
<div className="flex flex-col gap-4">
<Task />
<Task />
<Task />
</div>
</Column>
<Column title="درحال انجام" />
<Column title="انجام شده">
<div className="flex flex-col gap-4">
<Task />
</div>
</Column>
{columns.map((column) => (
<Column
key={column.id}
column={column}
tasks={tasksByColumn[column.id] ?? []}
draggedTaskId={draggedTaskId}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDrop={handleDrop}
/>
))}
<AddNewColumn />
</div>
+1
View File
@@ -12,6 +12,7 @@
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"resolveJsonModule": true,
"noEmit": true,
"jsx": "react-jsx",