ar-design 0.4.56 → 0.4.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/css/components/charts/gantt/styles.css +43 -31
- package/dist/components/charts/gantt/IProps.d.ts +24 -0
- package/dist/components/charts/gantt/IProps.js +1 -0
- package/dist/components/charts/gantt/index.d.ts +2 -1
- package/dist/components/charts/gantt/index.js +227 -113
- package/package.json +1 -1
|
@@ -1,49 +1,61 @@
|
|
|
1
1
|
.ar-gantt-chart {
|
|
2
|
-
background-color: var(--gray-700);
|
|
3
|
-
fill: var(--gray-700);
|
|
4
|
-
border-radius: var(--border-radius-lg);
|
|
5
|
-
font-family: var(--system);
|
|
6
2
|
box-shadow: 0px 10px 15px -5px rgba(var(--black-rgb), 0.1);
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
.ar-gantt-chart-svg {
|
|
5
|
+
background-color: var(--white);
|
|
6
|
+
border-top-left-radius: var(--border-radius-lg);
|
|
7
|
+
border-top-right-radius: var(--border-radius-lg);
|
|
8
|
+
font-family: var(--system);
|
|
11
9
|
|
|
12
|
-
> .
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
> .header {
|
|
11
|
+
fill: var(--white);
|
|
12
|
+
height: 75px;
|
|
13
|
+
|
|
14
|
+
> .title-group {
|
|
15
|
+
> .title {
|
|
16
|
+
fill: var(--gray-700);
|
|
17
|
+
font-size: 16px;
|
|
18
|
+
font-weight: bold;
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
> .title-description {
|
|
22
|
+
fill: var(--gray-500);
|
|
23
|
+
font-size: 13.28px;
|
|
24
|
+
}
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
|
-
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
> .body {
|
|
29
|
+
> .time-and-bars {
|
|
30
|
+
/* transition: transform 250ms ease-in-out; */
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
&.dragging {
|
|
33
|
+
/* transition: none !important; */
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
|
-
}
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
> .left-axis {
|
|
38
|
+
fill: var(--white);
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
> .label-list {
|
|
41
|
+
> .label-row {
|
|
42
|
+
> .label-text {
|
|
43
|
+
fill: var(--gray-700);
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
dominant-baseline: central;
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
}
|
|
52
|
+
|
|
53
|
+
> .footer {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: row;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
justify-content: space-between;
|
|
58
|
+
background-color: var(--white);
|
|
59
|
+
padding: 1rem 1.5rem;
|
|
60
|
+
}
|
|
49
61
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Task {
|
|
2
|
+
id: string | number;
|
|
3
|
+
name: string;
|
|
4
|
+
start: string;
|
|
5
|
+
end: string;
|
|
6
|
+
}
|
|
7
|
+
export type Config = {
|
|
8
|
+
locale?: Intl.LocalesArgument;
|
|
9
|
+
isServerSide?: boolean;
|
|
10
|
+
isSearchable?: boolean;
|
|
11
|
+
};
|
|
12
|
+
interface IProps {
|
|
13
|
+
title?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
data: Task[];
|
|
16
|
+
pagination?: {
|
|
17
|
+
totalRecords: number;
|
|
18
|
+
perPage: number;
|
|
19
|
+
currentPage?: number;
|
|
20
|
+
onChange?: (currentPage: number, perPage: number) => void;
|
|
21
|
+
};
|
|
22
|
+
config?: Config;
|
|
23
|
+
}
|
|
24
|
+
export default IProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,151 +1,265 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
3
|
import "../../../assets/css/components/charts/gantt/styles.css";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
start: "2026-04-22T09:30:00Z",
|
|
8
|
-
end: "2026-04-22T14:30:00Z",
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
id: "ff68947f-b2c2-4621-b5db-fd57702d037a",
|
|
12
|
-
name: "Turksat2LoV10000",
|
|
13
|
-
start: "2026-08-31T09:30:00Z",
|
|
14
|
-
end: "2026-08-31T14:30:00Z",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: "ff2e87f5-9b3c-470d-bda9-dfea1851680c",
|
|
18
|
-
name: "IRD 6",
|
|
19
|
-
start: "2026-04-15T10:30:00Z",
|
|
20
|
-
end: "2026-04-15T17:30:00Z",
|
|
21
|
-
},
|
|
22
|
-
];
|
|
23
|
-
const Gantt = () => {
|
|
4
|
+
import Pagination from "../../navigation/pagination";
|
|
5
|
+
import { useTranslation } from "../../../libs/core/application/hooks";
|
|
6
|
+
const colors = ["#A881FA", "#75CFA4", "#EE6D63", "#FAD87A"];
|
|
7
|
+
const Gantt = ({ title, description, data, pagination, config = { isSearchable: false } }) => {
|
|
24
8
|
// refs
|
|
25
9
|
const _svg = useRef(null);
|
|
26
10
|
const _mapIsMoveField = useRef(null);
|
|
11
|
+
const _scrollX = useRef(0);
|
|
12
|
+
const _isPressedCtrl = useRef(false);
|
|
27
13
|
// states
|
|
14
|
+
const [startX, setStartX] = useState(0);
|
|
28
15
|
const [scrollX, setScrollX] = useState(0);
|
|
29
16
|
const [isDragging, setIsDragging] = useState(false);
|
|
30
|
-
const [
|
|
17
|
+
const [zoom, setZoom] = useState(1); // 1 = %100, 1.5 = %150 zoom
|
|
18
|
+
// states -> Pagination
|
|
19
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
20
|
+
const [selectedPerPage, setSelectedPerPage] = useState(pagination?.perPage ?? 10);
|
|
21
|
+
// states -> Mobil
|
|
22
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
31
23
|
// variables
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
const getData = useMemo(() => {
|
|
25
|
+
let _data = [...data];
|
|
26
|
+
if (pagination && !config.isServerSide) {
|
|
27
|
+
const indexOfLastRow = currentPage * selectedPerPage;
|
|
28
|
+
const indexOfFirstRow = indexOfLastRow - selectedPerPage;
|
|
29
|
+
_data = _data.slice(indexOfFirstRow, indexOfLastRow);
|
|
30
|
+
}
|
|
31
|
+
return _data;
|
|
32
|
+
}, [data, currentPage, selectedPerPage, config.isServerSide]);
|
|
33
|
+
const DAY_WIDTH = 60 * zoom;
|
|
34
|
+
const TIMELINE = useMemo(() => generateGanttTimeline(data), [data]);
|
|
35
35
|
const HEADER_HEIGHT = 75;
|
|
36
36
|
const STROKE_WIDTH = 0.5;
|
|
37
|
-
const LABEL_WIDTH = 120;
|
|
38
37
|
const ROW_HEIGHT = 45;
|
|
38
|
+
const LABEL_WIDTH = useMemo(() => {
|
|
39
|
+
const longestName = getData.reduce((maxTask, currentTask) => (currentTask.name.length > maxTask.name.length ? currentTask : maxTask), // Corrected logic
|
|
40
|
+
{
|
|
41
|
+
name: "",
|
|
42
|
+
}).name;
|
|
43
|
+
const estimatedWidth = longestName.length * 7 + 25;
|
|
44
|
+
return Math.min(Math.max(estimatedWidth, 120), 250);
|
|
45
|
+
}, [getData]);
|
|
46
|
+
const SVG_WIDTH = "100%";
|
|
47
|
+
const SVG_HEIGHT = HEADER_HEIGHT + getData.length * ROW_HEIGHT + ROW_HEIGHT * 2;
|
|
39
48
|
let PREVMATCHMONT = 0;
|
|
40
49
|
let PREVMATCHDAY = 0;
|
|
50
|
+
// hooks
|
|
51
|
+
const { t } = useTranslation(String(config.locale ?? "tr"));
|
|
41
52
|
// methods
|
|
42
|
-
const
|
|
53
|
+
const handleResize = useCallback(() => {
|
|
54
|
+
return (_) => {
|
|
55
|
+
setIsMobile(window.innerWidth <= 768);
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
58
|
+
const handleMouseDown = useCallback((e) => {
|
|
43
59
|
if (e.button !== 0)
|
|
44
60
|
return;
|
|
45
61
|
setIsDragging(true);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
setStartX(e.clientX + _scrollX.current);
|
|
63
|
+
}, []);
|
|
64
|
+
const handleKeyboardDown = useCallback((event) => {
|
|
65
|
+
if (["Control", "Meta"].includes(event.key)) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
_isPressedCtrl.current = true;
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
const handleKeyboardUp = useCallback((event) => {
|
|
71
|
+
if (["Control", "Meta"].includes(event.key)) {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
_isPressedCtrl.current = false;
|
|
74
|
+
}
|
|
75
|
+
}, []);
|
|
49
76
|
const handleMouseMove = useCallback((e) => {
|
|
50
77
|
if (!isDragging)
|
|
51
78
|
return;
|
|
52
79
|
let newScrollX = startX - e.clientX;
|
|
53
80
|
if (newScrollX < 0)
|
|
54
81
|
newScrollX = 0;
|
|
82
|
+
// Ekrana sığan alanı çıkarırken doğru clientWidth kontrolü
|
|
83
|
+
const availableWidth = (_svg.current?.clientWidth ?? 0) - LABEL_WIDTH;
|
|
84
|
+
const maxScrollWidth = TIMELINE.days.length * DAY_WIDTH - availableWidth;
|
|
85
|
+
if (newScrollX > maxScrollWidth)
|
|
86
|
+
newScrollX = maxScrollWidth;
|
|
87
|
+
// Eğer içerik zaten ekrana sığıyorsa scrollX 0 olmalı
|
|
88
|
+
if (maxScrollWidth <= 0)
|
|
89
|
+
newScrollX = 0;
|
|
55
90
|
setScrollX(newScrollX);
|
|
56
|
-
|
|
57
|
-
|
|
91
|
+
_scrollX.current = newScrollX;
|
|
92
|
+
}, [startX, isDragging, TIMELINE.days.length, DAY_WIDTH, LABEL_WIDTH]);
|
|
93
|
+
const handleMouseUpOrLeave = useCallback(() => {
|
|
58
94
|
const svgRect = _svg.current?.getBoundingClientRect();
|
|
59
95
|
const mapIsMoveFieldRect = _mapIsMoveField.current?.getBoundingClientRect();
|
|
60
96
|
if (svgRect && mapIsMoveFieldRect) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
const svgElement = _svg.current;
|
|
98
|
+
if (!svgElement)
|
|
99
|
+
return;
|
|
100
|
+
const chartContentWidth = TIMELINE.days.length * DAY_WIDTH;
|
|
101
|
+
const viewportWidth = svgElement.clientWidth;
|
|
102
|
+
const availableChartWidth = viewportWidth - LABEL_WIDTH;
|
|
103
|
+
if (chartContentWidth > availableChartWidth) {
|
|
104
|
+
const maxScrollX = chartContentWidth - availableChartWidth;
|
|
105
|
+
if (scrollX > maxScrollX) {
|
|
106
|
+
setScrollX(maxScrollX);
|
|
107
|
+
_scrollX.current = maxScrollX;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
setScrollX(0);
|
|
64
112
|
}
|
|
65
113
|
}
|
|
66
114
|
setIsDragging(false);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
}, [TIMELINE.days.length, LABEL_WIDTH]);
|
|
116
|
+
// useEffects
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
setIsMobile(window.innerWidth <= 768);
|
|
119
|
+
window.addEventListener("resize", handleResize);
|
|
120
|
+
// Keyboard Events
|
|
121
|
+
window.addEventListener("keydown", handleKeyboardDown);
|
|
122
|
+
window.addEventListener("keyup", handleKeyboardUp);
|
|
123
|
+
return () => {
|
|
124
|
+
window.removeEventListener("resize", handleResize);
|
|
125
|
+
// Keyboard Events
|
|
126
|
+
window.removeEventListener("keydown", handleKeyboardDown);
|
|
127
|
+
window.removeEventListener("keyup", handleKeyboardUp);
|
|
128
|
+
};
|
|
129
|
+
}, [handleResize]);
|
|
130
|
+
return (React.createElement("div", { className: "ar-gantt-chart" },
|
|
131
|
+
React.createElement("svg", { ref: _svg, xmlns: "http://www.w3.org/2000/svg",
|
|
132
|
+
// viewBox={`0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`}
|
|
133
|
+
width: SVG_WIDTH, height: SVG_HEIGHT, className: "ar-gantt-chart-svg" },
|
|
134
|
+
React.createElement("defs", null,
|
|
135
|
+
React.createElement("pattern", { id: "weekend-stripes", width: "8", height: "8", patternUnits: "userSpaceOnUse" },
|
|
136
|
+
React.createElement("rect", { width: "8", height: "8", fill: "rgba(0, 0, 0, 0.03)" }),
|
|
137
|
+
React.createElement("line", { x1: "0", y1: "8", x2: "8", y2: "0", opacity: 0.25, stroke: "var(--red-500)", strokeWidth: 0.5 }))),
|
|
138
|
+
React.createElement("g", { className: "header", width: "100%" },
|
|
139
|
+
React.createElement("rect", { x: 0, y: 0, width: "100%", height: HEADER_HEIGHT }),
|
|
140
|
+
React.createElement("g", { transform: `translate(25, ${HEADER_HEIGHT / 2})`, className: "title-group" },
|
|
141
|
+
React.createElement("text", { className: "title" }, title),
|
|
142
|
+
React.createElement("text", { y: 20, className: "title-description" }, description)),
|
|
143
|
+
React.createElement("line", { x1: "0", y1: HEADER_HEIGHT, x2: "100%", y2: HEADER_HEIGHT, opacity: 0.25, stroke: "var(--black)", strokeWidth: 1 })),
|
|
144
|
+
React.createElement("g", { className: "body", transform: `translate(0, ${HEADER_HEIGHT + ROW_HEIGHT * 2})` },
|
|
145
|
+
React.createElement("g", { className: `${isDragging ? "dragging" : "no-dragging"} time-and-bars`, transform: `translate(${LABEL_WIDTH - scrollX}, 0)`, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUpOrLeave, onMouseLeave: handleMouseUpOrLeave, onWheel: (event) => {
|
|
146
|
+
if (!_isPressedCtrl.current)
|
|
147
|
+
return;
|
|
148
|
+
// 1. Mevcut toplam genişliği hesapla. (eski zoom ile)
|
|
149
|
+
const currentTotalWidth = TIMELINE.days.length * DAY_WIDTH;
|
|
150
|
+
const availableWidth = (_svg.current?.clientWidth ?? 0) - LABEL_WIDTH;
|
|
151
|
+
// 2. Şu anki kaydırma oranını bul. (0 ile 1 arasında bir değer)
|
|
152
|
+
// Eğer içerik ekrana sığıyorsa oran 0'dır.
|
|
153
|
+
const currentScrollRatio = currentTotalWidth > availableWidth ? scrollX / (currentTotalWidth - availableWidth) : 0;
|
|
154
|
+
// 3. Yeni zoom değerini hesapla.
|
|
155
|
+
const nextZoom = event.deltaY < 0 ? Math.min(zoom + 0.5, 5) : Math.max(zoom - 0.5, 1);
|
|
156
|
+
// Eğer zoom değişmeyecekse (sınırlara takıldıysa) işlem yapma.
|
|
157
|
+
if (nextZoom === zoom)
|
|
87
158
|
return;
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
// 4. Yeni zoom'a göre yeni DAY_WIDTH ve yeni toplam genişliği hesapla.
|
|
160
|
+
const nextDayWidth = 60 * nextZoom;
|
|
161
|
+
const nextTotalWidth = TIMELINE.days.length * nextDayWidth;
|
|
162
|
+
// 5. Yeni maksimum kaydırma genişliğini bul ve eski orana göre scrollX'i güncelle.
|
|
163
|
+
const nextMaxScrollWidth = nextTotalWidth - availableWidth;
|
|
164
|
+
const nextScrollX = Math.max(0, nextMaxScrollWidth * currentScrollRatio);
|
|
165
|
+
// State'leri güncelle.
|
|
166
|
+
setZoom(nextZoom);
|
|
167
|
+
setScrollX(nextScrollX);
|
|
168
|
+
_scrollX.current = nextScrollX;
|
|
169
|
+
}, style: { cursor: isDragging ? "grabbing" : "grab", userSelect: "none" } },
|
|
170
|
+
React.createElement("g", { id: "month-and-days" },
|
|
171
|
+
TIMELINE.days.map((day, index) => {
|
|
172
|
+
const xPos = (index + 1) * DAY_WIDTH;
|
|
173
|
+
const nextDay = new Date(day.date);
|
|
174
|
+
nextDay.setDate(nextDay.getDate() + 1);
|
|
175
|
+
const isLastDayOfMonth = day.date.getMonth() !== nextDay.getMonth();
|
|
176
|
+
const isSunday = day.date.getDay() === 0;
|
|
177
|
+
if (!isSunday && !isLastDayOfMonth)
|
|
178
|
+
return null;
|
|
179
|
+
const currentMonthNum = day.date.getMonth();
|
|
180
|
+
const currentDayNum = day.date.getDate();
|
|
181
|
+
if (index === 0) {
|
|
182
|
+
PREVMATCHMONT = 0;
|
|
183
|
+
PREVMATCHDAY = 0;
|
|
184
|
+
}
|
|
185
|
+
const dayDiff = currentDayNum - (currentMonthNum !== PREVMATCHMONT ? 0 : PREVMATCHDAY);
|
|
186
|
+
// Bir sonraki turda kullanabilmek için hafızayı güncelliyoruz.
|
|
187
|
+
PREVMATCHMONT = currentMonthNum;
|
|
188
|
+
PREVMATCHDAY = currentDayNum;
|
|
189
|
+
return (React.createElement("g", { key: index },
|
|
190
|
+
React.createElement("line", { x1: xPos, y1: -ROW_HEIGHT * 2, x2: xPos, y2: 0, opacity: 0.25, stroke: "var(--black)", strokeWidth: STROKE_WIDTH }),
|
|
191
|
+
React.createElement("text", { x: xPos - (dayDiff * DAY_WIDTH) / 2, y: -ROW_HEIGHT * 2 + ROW_HEIGHT / 2, fill: "var(--black)", fontSize: "12", textAnchor: "middle", dominantBaseline: "central" }, day.date.toLocaleDateString("tr-TR", { month: "long" }))));
|
|
192
|
+
}),
|
|
193
|
+
TIMELINE.days.map((day, index) => {
|
|
194
|
+
const xPos = (index + 1) * DAY_WIDTH; // 01:00 -> 60px, 02:00 -> 120px...
|
|
195
|
+
return (React.createElement("g", { key: index },
|
|
196
|
+
React.createElement("text", { x: (index + 1) * DAY_WIDTH - 30, y: -ROW_HEIGHT + ROW_HEIGHT / 2, fill: day.isWeekend ? "var(--red-500)" : "var(--black)", fontSize: "12", textAnchor: "middle", dominantBaseline: "central" },
|
|
197
|
+
String(day.number).padStart(2, "0"),
|
|
198
|
+
" ",
|
|
199
|
+
day.name),
|
|
200
|
+
day.isWeekend && (React.createElement("rect", { x: xPos - DAY_WIDTH, y: 0, width: DAY_WIDTH, height: SVG_HEIGHT, fill: "url(#weekend-stripes)" })),
|
|
201
|
+
React.createElement("line", { x1: xPos, y1: 0, x2: xPos, y2: SVG_HEIGHT, opacity: 0.25, stroke: "var(--black)", strokeWidth: STROKE_WIDTH, strokeDasharray: "5,5" })));
|
|
202
|
+
}),
|
|
203
|
+
React.createElement("line", { x1: 0, y1: -ROW_HEIGHT, x2: TIMELINE.days.length * DAY_WIDTH, y2: -ROW_HEIGHT, opacity: 0.25, stroke: "var(--black)", strokeWidth: 1 }),
|
|
204
|
+
React.createElement("line", { x1: 0, y1: 0, x2: TIMELINE.days.length * DAY_WIDTH, y2: 0, opacity: 0.25, stroke: "var(--black)", strokeWidth: 1 })),
|
|
205
|
+
React.createElement("g", { transform: `translate(0, 0)` }, getData.map((task, index) => {
|
|
206
|
+
const taskStart = new Date(task.start);
|
|
207
|
+
const taskEnd = new Date(task.end);
|
|
208
|
+
// 1. Proje başlangıcından bu görevin başlangıcına kadar geçen toplam milisaniye
|
|
209
|
+
const diffMsFromStart = taskStart.getTime() - Number(TIMELINE.timelineStart?.getTime());
|
|
210
|
+
// Milisaniyeyi DOĞRU şekilde saate çeviriyoruz (Sabit: 1000 * 60 * 60)
|
|
211
|
+
const hoursFromStart = diffMsFromStart / (1000 * 60 * 60);
|
|
212
|
+
// X Konumu: Geçen toplam saati, dinamik saat başına düşen pikselle çarpıyoruz
|
|
213
|
+
const x = hoursFromStart * (DAY_WIDTH / 24);
|
|
214
|
+
// 2. Görevin toplam süresini DOĞRU şekilde saat cinsinden buluyoruz
|
|
215
|
+
const durationHours = (taskEnd.getTime() - taskStart.getTime()) / (1000 * 60 * 60);
|
|
216
|
+
// Genişlik (Width): Süreyi dinamik saat başına düşen pikselle çarpıyoruz
|
|
217
|
+
const width = durationHours * (DAY_WIDTH / 24);
|
|
218
|
+
// 3. Dikey Konumlandırma
|
|
219
|
+
const height = ROW_HEIGHT / 1.5;
|
|
220
|
+
const y = index * ROW_HEIGHT + height / 4;
|
|
221
|
+
return (React.createElement("g", { key: task.id },
|
|
222
|
+
React.createElement("rect", { x: x, y: y, width: width, height: height, fill: colors[index % colors.length], rx: 3 }),
|
|
223
|
+
width > 60 && (React.createElement("text", { x: x + width / 2, y: y + height / 2 + 4, fontSize: 12, fontWeight: "600", fill: "var(--black)", textAnchor: "middle" }, task.name))));
|
|
224
|
+
})),
|
|
225
|
+
React.createElement("rect", { ref: _mapIsMoveField, x: 0, y: -ROW_HEIGHT, width: TIMELINE.days.length * DAY_WIDTH, height: SVG_HEIGHT, fill: "transparent", pointerEvents: "all" })),
|
|
226
|
+
React.createElement("g", { className: "left-axis" },
|
|
227
|
+
React.createElement("rect", { x: 0, y: -ROW_HEIGHT * 2 + 0.5, width: LABEL_WIDTH, height: SVG_HEIGHT, fill: "var(--gray-100)" }),
|
|
228
|
+
React.createElement("g", { className: "label-list" }, getData.map((item, index) => {
|
|
229
|
+
const y = index * ROW_HEIGHT;
|
|
230
|
+
const textContent = item.name;
|
|
231
|
+
const maxTextWidth = LABEL_WIDTH - 20;
|
|
232
|
+
return (React.createElement("g", { key: item.id, className: "label-row" },
|
|
233
|
+
React.createElement("text", { x: "10", y: y + ROW_HEIGHT / 2, className: "label-text" }, textContent.length * 7 > maxTextWidth
|
|
234
|
+
? `${textContent.substring(0, Math.floor(maxTextWidth / 7) - 3)}...`
|
|
235
|
+
: textContent),
|
|
236
|
+
React.createElement("line", { x1: "0", y1: y + ROW_HEIGHT, x2: LABEL_WIDTH, y2: y + ROW_HEIGHT, stroke: "var(--black)", strokeWidth: "0.5", opacity: 0.25 }),
|
|
237
|
+
React.createElement("line", { x1: LABEL_WIDTH, y1: y + ROW_HEIGHT, x2: LABEL_WIDTH + TIMELINE.days.length * DAY_WIDTH, y2: y + ROW_HEIGHT, opacity: 0.25, stroke: "var(--black)", strokeWidth: STROKE_WIDTH, strokeDasharray: "5,5" })));
|
|
238
|
+
}))))),
|
|
239
|
+
React.createElement("div", { className: "footer" },
|
|
240
|
+
React.createElement("span", null, isMobile ? (React.createElement(React.Fragment, null,
|
|
241
|
+
React.createElement("strong", null,
|
|
242
|
+
(currentPage - 1) * selectedPerPage + 1,
|
|
243
|
+
" -",
|
|
244
|
+
" ",
|
|
245
|
+
Math.min(currentPage * selectedPerPage, pagination?.totalRecords || data.length),
|
|
246
|
+
" of",
|
|
247
|
+
" ",
|
|
248
|
+
pagination?.totalRecords || data.length))) : (t("Table.Pagination.Information.Text", (currentPage - 1) * selectedPerPage + 1, Math.min(currentPage * selectedPerPage, pagination?.totalRecords || data.length), pagination?.totalRecords || data.length))),
|
|
249
|
+
pagination && (React.createElement(Pagination, { totalRecords: config.isServerSide ? pagination.totalRecords : (data.length ?? 0), currentPage: currentPage, perPage: selectedPerPage, onChange: (currentPage, perPage) => {
|
|
250
|
+
setCurrentPage(currentPage);
|
|
251
|
+
setSelectedPerPage(perPage);
|
|
252
|
+
pagination.onChange?.(currentPage, perPage);
|
|
253
|
+
} })))));
|
|
140
254
|
};
|
|
141
|
-
const generateGanttTimeline = (
|
|
142
|
-
if (!
|
|
255
|
+
const generateGanttTimeline = (data) => {
|
|
256
|
+
if (!data || data.length === 0) {
|
|
143
257
|
return { minDate: null, maxDate: null, months: [], days: [] };
|
|
144
258
|
}
|
|
145
259
|
// 1. En erken başlangıç ve en geç bitiş tarihlerini buluyoruz.
|
|
146
|
-
let minDate = new Date(
|
|
147
|
-
let maxDate = new Date(
|
|
148
|
-
|
|
260
|
+
let minDate = new Date(data[0].start);
|
|
261
|
+
let maxDate = new Date(data[0].end);
|
|
262
|
+
data.forEach((task) => {
|
|
149
263
|
const start = new Date(task.start);
|
|
150
264
|
const end = new Date(task.end);
|
|
151
265
|
if (start < minDate)
|