@esic-lab/data-core-ui 0.0.57 → 0.0.59

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/index.js CHANGED
@@ -369,6 +369,7 @@ __export(index_exports, {
369
369
  DatePickerRange: () => DatePickerRange,
370
370
  FileUploader: () => FileUploader,
371
371
  FilterPopUp: () => FilterPopUp,
372
+ GanttChart: () => GanttChart,
372
373
  GhostButton: () => GhostButton,
373
374
  HeadingPage: () => HeadingPage,
374
375
  Indicator: () => Indicator,
@@ -4110,7 +4111,8 @@ function ProfileSelect({
4110
4111
  assignUser,
4111
4112
  mode,
4112
4113
  className,
4113
- onUpdateAssignUser
4114
+ onUpdateAssignUser,
4115
+ placeholder
4114
4116
  }) {
4115
4117
  const [maxVisible, setMaxVisible] = (0, import_react20.useState)(4);
4116
4118
  const [userNotAssign, setUserNotAssign] = (0, import_react20.useState)([]);
@@ -4156,9 +4158,7 @@ function ProfileSelect({
4156
4158
  const visibleUsers = showPlus ? assignUser.slice(0, maxVisible - 1) : assignUser;
4157
4159
  const extraCount = assignUser.length - (maxVisible - 1);
4158
4160
  const normalizedSearch = search.trim().toLowerCase();
4159
- const filteredAssigned = normalizedSearch ? assignUser.filter(
4160
- (u) => u.name.toLowerCase().includes(normalizedSearch)
4161
- ) : assignUser;
4161
+ const filteredAssigned = normalizedSearch ? assignUser.filter((u) => u.name.toLowerCase().includes(normalizedSearch)) : assignUser;
4162
4162
  const filteredUnassigned = normalizedSearch ? userNotAssign.filter(
4163
4163
  (u) => u.name.toLowerCase().includes(normalizedSearch)
4164
4164
  ) : userNotAssign;
@@ -4170,13 +4170,13 @@ function ProfileSelect({
4170
4170
  className: "p-2 border rounded cursor-pointer bg-white",
4171
4171
  onClick: () => setIsShowSelect(!isShowSelect)
4172
4172
  }
4173
- ) }) : mode === "showAssign" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
4173
+ ) }) : mode === "showAssign" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
4174
4174
  "div",
4175
4175
  {
4176
- className: `w-full h-[40px] flex -space-x-2 p-2 cursor-pointer ${className}`,
4176
+ className: `w-full h-[40px] flex items-center p-2 cursor-pointer ${className}`,
4177
4177
  onClick: () => setIsShowSelect(!isShowSelect),
4178
- children: [
4179
- visibleUsers.map((user) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex items-center cursor-point", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "relative group cursor-pointer", children: [
4178
+ children: visibleUsers.length === 0 ? placeholder ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "body-1 text-gray-400 select-none", children: placeholder }) : null : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
4179
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex -space-x-2", children: visibleUsers.map((user) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex items-center", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "relative group cursor-pointer", children: [
4180
4180
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
4181
4181
  "img",
4182
4182
  {
@@ -4193,16 +4193,19 @@ function ProfileSelect({
4193
4193
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
4194
4194
  "span",
4195
4195
  {
4196
- className: "absolute top-0 right-0 -translate-y-2 translate-x-2 \r\n rounded-full bg-white opacity-0 group-hover:opacity-100 \r\n border-3 z-10 border-red-500 flex items-center justify-center transition",
4197
- onClick: () => onUpdateAssignUser(user, "remove"),
4196
+ className: "absolute top-0 right-0 -translate-y-2 translate-x-2 \r\n rounded-full bg-white opacity-0 group-hover:opacity-100 \r\n border-3 z-10 border-red-500 flex items-center justify-center transition",
4197
+ onClick: (e) => {
4198
+ e.stopPropagation();
4199
+ onUpdateAssignUser(user, "remove");
4200
+ },
4198
4201
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_icons_react14.IconX, { className: "w-4 h-4 text-red-500" })
4199
4202
  }
4200
4203
  )
4201
- ] }) }, user.id)),
4204
+ ] }) }, user.id)) }),
4202
4205
  showPlus && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
4203
4206
  "div",
4204
4207
  {
4205
- className: "border border-white flex items-center justify-center bg-gray-300 text-black text-sm",
4208
+ className: "border border-white flex items-center justify-center bg-gray-300 text-black text-sm ml-2",
4206
4209
  style: {
4207
4210
  width: avatarSize,
4208
4211
  height: avatarSize,
@@ -4214,7 +4217,7 @@ function ProfileSelect({
4214
4217
  ]
4215
4218
  }
4216
4219
  )
4217
- ]
4220
+ ] })
4218
4221
  }
4219
4222
  ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: `w-full h-[40px] flex -space-x-2 p-2 ${className}`, children: [
4220
4223
  visibleUsers.map((user) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex items-center ", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("label", { className: "relative group ", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
@@ -4345,7 +4348,7 @@ var QRCodeGenerator = ({
4345
4348
  fileBaseName = "qr-code"
4346
4349
  }) => {
4347
4350
  const canvasRef = (0, import_react21.useRef)(null);
4348
- const [format4, setFormat] = (0, import_react21.useState)("png");
4351
+ const [format5, setFormat] = (0, import_react21.useState)("png");
4349
4352
  const [exportSize, setExportSize] = (0, import_react21.useState)(defaultExportSize);
4350
4353
  const sizeOption = [
4351
4354
  {
@@ -4399,7 +4402,7 @@ var QRCodeGenerator = ({
4399
4402
  }, [url, previewSize]);
4400
4403
  const download = async () => {
4401
4404
  try {
4402
- if (format4 === "svg") {
4405
+ if (format5 === "svg") {
4403
4406
  const svgString = await import_qrcode.default.toString(url, {
4404
4407
  type: "svg",
4405
4408
  width: exportSize,
@@ -4415,9 +4418,9 @@ var QRCodeGenerator = ({
4415
4418
  width: exportSize,
4416
4419
  margin: 1
4417
4420
  });
4418
- const mime = format4 === "png" ? "image/png" : "image/jpeg";
4419
- const dataURL = format4 === "jpeg" ? offscreen.toDataURL(mime, 0.92) : offscreen.toDataURL(mime);
4420
- triggerDownload(dataURL, `${fileBaseName}.${format4}`);
4421
+ const mime = format5 === "png" ? "image/png" : "image/jpeg";
4422
+ const dataURL = format5 === "jpeg" ? offscreen.toDataURL(mime, 0.92) : offscreen.toDataURL(mime);
4423
+ triggerDownload(dataURL, `${fileBaseName}.${format5}`);
4421
4424
  }
4422
4425
  } catch (err) {
4423
4426
  console.error("Failed to generate QR export:", err);
@@ -4450,7 +4453,7 @@ var QRCodeGenerator = ({
4450
4453
  SelectField,
4451
4454
  {
4452
4455
  label: "\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A",
4453
- value: format4,
4456
+ value: format5,
4454
4457
  onChange: (e) => setFormat(e.target.value),
4455
4458
  options: typeOption
4456
4459
  }
@@ -4684,6 +4687,457 @@ var PieChart = ({
4684
4687
  ] })
4685
4688
  ] });
4686
4689
  };
4690
+
4691
+ // src/GanttChart/GanttChart.tsx
4692
+ var import_react24 = require("react");
4693
+ var d33 = __toESM(require("d3"));
4694
+ var import_date_fns3 = require("date-fns");
4695
+ var import_jsx_runtime47 = require("react/jsx-runtime");
4696
+ var LAYOUT = {
4697
+ barHeight: 40,
4698
+ barSpacing: 10,
4699
+ headersGroupLayer1Height: 30,
4700
+ headersGroupLayer2Height: 35
4701
+ };
4702
+ var HEADER_FONTS = {
4703
+ layer1: 16,
4704
+ layer2: 12
4705
+ };
4706
+ var VIEW_MODE_SCALE = {
4707
+ year: 0.5,
4708
+ month: 2,
4709
+ week: 10
4710
+ };
4711
+ var STATUS_META = {
4712
+ pending: { color: "#F16965", label: "\u0E23\u0E2D\u0E14\u0E33\u0E40\u0E19\u0E34\u0E19\u0E01\u0E32\u0E23" },
4713
+ "in-progress": { color: "#FFBB33", label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E14\u0E33\u0E40\u0E19\u0E34\u0E19\u0E01\u0E32\u0E23" },
4714
+ completed: { color: "#69C57D", label: "\u0E40\u0E2A\u0E23\u0E47\u0E08\u0E2A\u0E34\u0E49\u0E19" }
4715
+ };
4716
+ var THAI_MONTHS = {
4717
+ January: "\u0E21.\u0E04.",
4718
+ February: "\u0E01.\u0E1E.",
4719
+ March: "\u0E21\u0E35.\u0E04.",
4720
+ April: "\u0E40\u0E21.\u0E22.",
4721
+ May: "\u0E1E.\u0E04.",
4722
+ June: "\u0E21\u0E34.\u0E22.",
4723
+ July: "\u0E01.\u0E04.",
4724
+ August: "\u0E2A.\u0E04.",
4725
+ September: "\u0E01.\u0E22.",
4726
+ October: "\u0E15.\u0E04.",
4727
+ November: "\u0E1E.\u0E22.",
4728
+ December: "\u0E18.\u0E04."
4729
+ };
4730
+ var THAI_DAYS = ["\u0E2D\u0E32.", "\u0E08.", "\u0E2D.", "\u0E1E.", "\u0E1E\u0E24.", "\u0E28.", "\u0E2A."];
4731
+ var roundedRectPath = (x, y, width, height, radius) => `M${x + radius},${y} H${x + width} V${y + height} H${x + radius} A${radius},${radius},0,0,1,${x},${y + height - radius} V${y + radius} A${radius},${radius},0,0,1,${x + radius},${y}`;
4732
+ var formatThaiMonth = (date) => {
4733
+ const monthName = (0, import_date_fns3.format)(date, "MMMM");
4734
+ return THAI_MONTHS[monthName] || monthName;
4735
+ };
4736
+ var formatThaiDate = (date) => date ? date.toLocaleDateString("th-TH", { day: "numeric", month: "short" }) : "-";
4737
+ var isDateOnlyValue = (dt) => {
4738
+ const localZero = dt.getHours() === 0 && dt.getMinutes() === 0 && dt.getSeconds() === 0 && dt.getMilliseconds() === 0;
4739
+ const utcZero = dt.getUTCHours() === 0 && dt.getUTCMinutes() === 0 && dt.getUTCSeconds() === 0 && dt.getUTCMilliseconds() === 0;
4740
+ return localZero || utcZero;
4741
+ };
4742
+ var getAdjustedStart = (d) => {
4743
+ if (!d.startDate && d.endDate) {
4744
+ const endDate = d.endDate;
4745
+ const quarter = Math.floor(endDate.getMonth() / 3);
4746
+ const monthIndex = quarter * 3;
4747
+ return (0, import_date_fns3.startOfDay)(new Date(endDate.getFullYear(), monthIndex, 1));
4748
+ }
4749
+ if (d.startDate) {
4750
+ return isDateOnlyValue(d.startDate) ? (0, import_date_fns3.startOfDay)(d.startDate) : d.startDate;
4751
+ }
4752
+ return (0, import_date_fns3.startOfDay)(/* @__PURE__ */ new Date());
4753
+ };
4754
+ var getAdjustedEnd = (d) => {
4755
+ if (!d.startDate && d.endDate) {
4756
+ const endDate = d.endDate;
4757
+ const quarter = Math.floor(endDate.getMonth() / 3);
4758
+ const endMonth = (quarter + 1) * 3;
4759
+ return (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(new Date(endDate.getFullYear(), endMonth, 0), 1));
4760
+ }
4761
+ if (d.endDate) {
4762
+ return isDateOnlyValue(d.endDate) ? (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d.endDate, 1)) : d.endDate;
4763
+ }
4764
+ return (0, import_date_fns3.startOfDay)(/* @__PURE__ */ new Date());
4765
+ };
4766
+ var getStatusColor = (status2) => STATUS_META[status2]?.color ?? STATUS_META.pending.color;
4767
+ var getStatusLabel = (status2) => STATUS_META[status2]?.label ?? STATUS_META.pending.label;
4768
+ var ProjectRow = ({ element, barHeight, barSpacing }) => {
4769
+ const safeStartDate = element.startDate instanceof Date ? element.startDate : null;
4770
+ const safeEndDate = element.endDate instanceof Date ? element.endDate : null;
4771
+ const safeStatus = element.status || "pending";
4772
+ const statusColor = getStatusColor(safeStatus);
4773
+ const statusLabel = getStatusLabel(safeStatus);
4774
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
4775
+ "div",
4776
+ {
4777
+ style: {
4778
+ display: "grid",
4779
+ gridTemplateColumns: "200px 100px 100px 100px",
4780
+ alignItems: "center",
4781
+ height: `${barHeight}px`,
4782
+ fontSize: "12px",
4783
+ marginBottom: `${barSpacing}px`
4784
+ },
4785
+ children: [
4786
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
4787
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
4788
+ "span",
4789
+ {
4790
+ style: {
4791
+ display: "inline-block",
4792
+ width: "10px",
4793
+ height: "10px",
4794
+ borderRadius: "50%",
4795
+ backgroundColor: element.color || "#999"
4796
+ }
4797
+ }
4798
+ ),
4799
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { style: { color: "#333" }, children: element.label })
4800
+ ] }),
4801
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { style: { color: "#666" }, children: formatThaiDate(safeStartDate) }),
4802
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { style: { color: "#666" }, children: formatThaiDate(safeEndDate) }),
4803
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
4804
+ "button",
4805
+ {
4806
+ style: {
4807
+ height: `${barHeight}px`,
4808
+ minWidth: "100%",
4809
+ padding: "4px 8px",
4810
+ borderRadius: "4px",
4811
+ fontSize: "11px",
4812
+ backgroundColor: statusColor,
4813
+ color: "#000",
4814
+ border: "none",
4815
+ cursor: "pointer"
4816
+ },
4817
+ children: statusLabel
4818
+ }
4819
+ )
4820
+ ]
4821
+ },
4822
+ element.id
4823
+ );
4824
+ };
4825
+ var RowOverlay = ({ data, barHeight, barSpacing, totalHeaderHeight }) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
4826
+ "div",
4827
+ {
4828
+ style: {
4829
+ position: "absolute",
4830
+ top: `${totalHeaderHeight + 10}px`,
4831
+ left: 0,
4832
+ right: 0,
4833
+ height: `${data.length * (barHeight + barSpacing)}px`,
4834
+ pointerEvents: "none",
4835
+ zIndex: 5
4836
+ },
4837
+ children: data.map((d, i) => {
4838
+ if (i === 0) return null;
4839
+ const yPos = i * (barHeight + barSpacing) - barSpacing / 2;
4840
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
4841
+ "div",
4842
+ {
4843
+ style: {
4844
+ position: "absolute",
4845
+ top: `${yPos}px`,
4846
+ left: 0,
4847
+ right: 0,
4848
+ height: "1px",
4849
+ backgroundColor: "#d0d0d0"
4850
+ }
4851
+ },
4852
+ `row-line-${d.id}`
4853
+ );
4854
+ })
4855
+ }
4856
+ );
4857
+ var GanttChart = ({ data, width, height }) => {
4858
+ const svgRef = (0, import_react24.useRef)(null);
4859
+ const leftPanelRef = (0, import_react24.useRef)(null);
4860
+ const dataContainerRef = (0, import_react24.useRef)(null);
4861
+ const [viewMode] = (0, import_react24.useState)("year");
4862
+ const { barHeight, barSpacing, headersGroupLayer1Height, headersGroupLayer2Height } = LAYOUT;
4863
+ const totalHeaderHeight = headersGroupLayer1Height + headersGroupLayer2Height;
4864
+ (0, import_react24.useEffect)(() => {
4865
+ if (!data || !svgRef.current) return;
4866
+ const margin = { top: 0, right: 20, bottom: 20, left: 20 };
4867
+ const chartHeight = data.length * (barHeight + barSpacing) + margin.top + margin.bottom;
4868
+ const containerWidth = width * 2;
4869
+ const containerHeight = totalHeaderHeight + chartHeight;
4870
+ const borderRadius = 5;
4871
+ if (leftPanelRef.current) {
4872
+ leftPanelRef.current.style.height = `${containerHeight}px`;
4873
+ }
4874
+ if (dataContainerRef.current) {
4875
+ dataContainerRef.current.style.marginTop = "0px";
4876
+ }
4877
+ const svg = d33.select(svgRef.current).attr("width", containerWidth).attr("height", containerHeight).style("background-color", "white");
4878
+ svg.selectAll("*").remove();
4879
+ const chartGroup = svg.append("g").attr("transform", `translate(${margin.left}, ${totalHeaderHeight})`);
4880
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
4881
+ const domainStart = (0, import_date_fns3.startOfDay)((0, import_date_fns3.startOfYear)(new Date(currentYear, 0, 1)));
4882
+ const domainEnd = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)((0, import_date_fns3.endOfYear)(new Date(currentYear, 0, 1)), 1));
4883
+ const xScale = d33.scaleTime().domain([domainStart, domainEnd]).range([0, containerWidth - margin.left - margin.right]);
4884
+ const buildTimeBuckets = (domain) => ({
4885
+ uniqueYears: d33.timeYear.every(1)?.range(domain[0], domain[1]) || [],
4886
+ uniqueMonths: d33.timeMonth.every(1)?.range(domain[0], domain[1]) || [],
4887
+ uniqueWeeks: (0, import_date_fns3.eachWeekOfInterval)({ start: domain[0], end: domain[1] }, { weekStartsOn: 1 }),
4888
+ uniqueDays: d33.timeDay.range((0, import_date_fns3.startOfDay)(domain[0]), (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(domain[1], 1))) || []
4889
+ });
4890
+ const renderYearHeaders = (layer1, layer2, newXScale, buckets) => {
4891
+ layer1.selectAll(".year-bg").data(buckets.uniqueYears).enter().append("rect").attr("class", "year-bg").attr("x", (d) => newXScale(d)).attr("y", -totalHeaderHeight).attr("width", (d) => {
4892
+ const nextYearStart = (0, import_date_fns3.startOfDay)(new Date(d.getFullYear() + 1, 0, 1));
4893
+ return newXScale(nextYearStart) - newXScale(d);
4894
+ }).attr("height", headersGroupLayer1Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4895
+ layer1.selectAll(".year-label").data(buckets.uniqueYears).enter().append("text").attr("class", "year-label").attr("x", (d) => {
4896
+ const nextYear = new Date(d.getFullYear() + 1, 0, 1);
4897
+ return newXScale(d) + (newXScale(nextYear) - newXScale(d)) / 2;
4898
+ }).attr("y", -totalHeaderHeight + headersGroupLayer1Height / 2).attr("dy", "0.35em").attr("text-anchor", "middle").style("font-size", `${HEADER_FONTS.layer1}px`).style("font-weight", "bold").style("fill", "#0066cc").text((d) => `${d.getFullYear() + 543}`);
4899
+ const quarters2 = [];
4900
+ buckets.uniqueYears.forEach((year) => {
4901
+ for (let q = 0; q < 4; q++) {
4902
+ quarters2.push(new Date(year.getFullYear(), q * 3, 1));
4903
+ }
4904
+ });
4905
+ layer2.selectAll(".quarter-bg").data(quarters2).enter().append("rect").attr("class", "quarter-bg").attr("x", (d) => newXScale(d)).attr("y", -headersGroupLayer2Height).attr("width", (d) => {
4906
+ const nextQuarter = (0, import_date_fns3.startOfDay)(new Date(d.getFullYear(), d.getMonth() + 3, 1));
4907
+ return newXScale(nextQuarter) - newXScale(d);
4908
+ }).attr("height", headersGroupLayer2Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4909
+ layer2.selectAll(".quarter-label").data(quarters2).enter().append("text").attr("class", "quarter-label").attr("x", (d) => {
4910
+ const nextQuarter = (0, import_date_fns3.startOfDay)(new Date(d.getFullYear(), d.getMonth() + 3, 1));
4911
+ return newXScale(d) + (newXScale(nextQuarter) - newXScale(d)) / 2;
4912
+ }).attr("y", -headersGroupLayer2Height / 2).attr("dy", "0.35em").attr("text-anchor", "middle").style("font-size", `${HEADER_FONTS.layer2}px`).style("font-weight", "bold").style("fill", "#0088cc").text((d) => `\u0E44\u0E15\u0E23\u0E21\u0E32\u0E2A ${Math.floor(d.getMonth() / 3) + 1}`);
4913
+ };
4914
+ const renderMonthHeaders = (layer1, layer2, newXScale, buckets) => {
4915
+ layer1.selectAll(".month-bg").data(buckets.uniqueMonths).enter().append("rect").attr("class", "month-bg").attr("x", (d) => newXScale(d)).attr("y", -totalHeaderHeight).attr("width", (d) => {
4916
+ const nextMonth = (0, import_date_fns3.startOfDay)(new Date(d.getFullYear(), d.getMonth() + 1, 1));
4917
+ return newXScale(nextMonth) - newXScale(d) + 11;
4918
+ }).attr("height", headersGroupLayer1Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4919
+ layer1.selectAll(".month-label").data(buckets.uniqueMonths).enter().append("text").attr("class", "month-label").attr("x", (d) => {
4920
+ const nextMonth = (0, import_date_fns3.startOfDay)(new Date(d.getFullYear(), d.getMonth() + 1, 1));
4921
+ return newXScale(d) + (newXScale(nextMonth) - newXScale(d)) / 2;
4922
+ }).attr("y", -totalHeaderHeight + headersGroupLayer1Height / 2).attr("dy", "0.35em").attr("text-anchor", "middle").style("font-size", `${HEADER_FONTS.layer1}px`).style("font-weight", "bold").style("fill", "#0066cc").text((d) => `${formatThaiMonth(d)} ${d.getFullYear() + 543}`);
4923
+ layer2.selectAll(".week-bg").data(buckets.uniqueWeeks).enter().append("rect").attr("class", "week-bg").attr("x", (d) => newXScale(d)).attr("y", -headersGroupLayer2Height).attr("width", (d) => {
4924
+ const weekEnd = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d, 7));
4925
+ return newXScale(weekEnd) - newXScale(d) + 1;
4926
+ }).attr("height", headersGroupLayer2Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4927
+ layer2.selectAll(".week-label").data(buckets.uniqueWeeks).enter().append("text").attr("class", "week-label").attr("x", (d) => {
4928
+ const weekEnd = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d, 7));
4929
+ return newXScale(d) + (newXScale(weekEnd) - newXScale(d)) / 2;
4930
+ }).attr("y", -headersGroupLayer2Height / 2).attr("dy", "0.35em").attr("text-anchor", "middle").style("font-size", `${HEADER_FONTS.layer2}px`).style("font-weight", "bold").style("fill", "#0088cc").text((d) => `\u0E2A\u0E31\u0E1B\u0E14\u0E32\u0E2B\u0E4C\u0E17\u0E35\u0E48 ${(0, import_date_fns3.getWeek)(d, { weekStartsOn: 1 })}`);
4931
+ };
4932
+ const renderWeekHeaders = (layer1, layer2, newXScale, buckets) => {
4933
+ layer1.selectAll(".week-bg").data(buckets.uniqueWeeks).enter().append("rect").attr("class", "week-bg").attr("x", (d) => newXScale(d)).attr("y", -totalHeaderHeight).attr("width", (d) => {
4934
+ const weekEnd = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d, 7));
4935
+ return newXScale(weekEnd) - newXScale(d);
4936
+ }).attr("height", headersGroupLayer1Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4937
+ layer1.selectAll(".week-label").data(buckets.uniqueWeeks).enter().append("text").attr("class", "week-label").attr("x", (d) => {
4938
+ const weekEnd = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d, 7));
4939
+ return newXScale(d) + (newXScale(weekEnd) - newXScale(d)) / 2;
4940
+ }).attr("y", -totalHeaderHeight + headersGroupLayer1Height / 2).attr("dy", "0.35em").attr("text-anchor", "middle").style("font-size", `${HEADER_FONTS.layer1}px`).style("font-weight", "bold").style("fill", "#0066cc").text((d) => `\u0E2A\u0E31\u0E1B\u0E14\u0E32\u0E2B\u0E4C\u0E17\u0E35\u0E48 ${(0, import_date_fns3.getWeek)(d, { weekStartsOn: 1 })}`);
4941
+ layer2.selectAll(".day-bg").data(buckets.uniqueDays).enter().append("rect").attr("class", "day-bg").attr("x", (d) => newXScale(d)).attr("y", -headersGroupLayer2Height).attr("width", (d) => {
4942
+ const nextDay = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d, 1));
4943
+ return newXScale(nextDay) - newXScale(d);
4944
+ }).attr("height", headersGroupLayer2Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4945
+ layer2.selectAll(".day-of-week").data(buckets.uniqueDays).enter().append("text").attr("class", "day-of-week").attr("x", (d) => {
4946
+ const nextDay = (0, import_date_fns3.startOfDay)((0, import_date_fns3.addDays)(d, 1));
4947
+ return newXScale(d) + (newXScale(nextDay) - newXScale(d)) / 2;
4948
+ }).attr("y", -headersGroupLayer2Height / 2).attr("text-anchor", "middle").style("font-size", `${HEADER_FONTS.layer2}px`).style("font-weight", "bold").style("fill", "#0088cc").each(function(d) {
4949
+ const textElement = d33.select(this);
4950
+ textElement.text("");
4951
+ textElement.append("tspan").text(THAI_DAYS[d.getDay()]).attr("x", textElement.attr("x")).attr("dy", "-0.3em");
4952
+ textElement.append("tspan").text((0, import_date_fns3.format)(d, "d")).attr("x", textElement.attr("x")).attr("dy", "1.2em");
4953
+ });
4954
+ };
4955
+ const drawHeaders = (layer1, layer2, newXScale, buckets) => {
4956
+ if (viewMode === "year") return renderYearHeaders(layer1, layer2, newXScale, buckets);
4957
+ if (viewMode === "month") return renderMonthHeaders(layer1, layer2, newXScale, buckets);
4958
+ return renderWeekHeaders(layer1, layer2, newXScale, buckets);
4959
+ };
4960
+ const getGridlineData = (mode, buckets, domain) => {
4961
+ if (mode === "year") return buckets.uniqueMonths;
4962
+ if (mode === "month") return buckets.uniqueWeeks;
4963
+ if (mode === "week") return buckets.uniqueDays;
4964
+ return (0, import_date_fns3.eachHourOfInterval)({ start: domain[0], end: domain[1] });
4965
+ };
4966
+ const drawGridlines = (group, gridlineData, newXScale) => {
4967
+ group.selectAll(".gridline").data(gridlineData).enter().append("line").attr("class", "gridline").attr("x1", (d) => newXScale(d)).attr("x2", (d) => newXScale(d)).attr("y1", 0).attr("y2", chartHeight - margin.top - margin.bottom).attr("stroke", "#000").attr("stroke-width", 1).style("opacity", 0.3);
4968
+ };
4969
+ const drawRowSeparators = (group, getYPosition) => {
4970
+ const rowSeparatorGroup = group.append("g").attr("class", "row-separators");
4971
+ data.forEach((_d, i) => {
4972
+ if (i === 0) return;
4973
+ const y = getYPosition(i) - barSpacing / 2;
4974
+ rowSeparatorGroup.append("line").attr("x1", 0).attr("x2", containerWidth - margin.left - margin.right).attr("y1", y).attr("y2", y).attr("stroke", "#d0d0d0").attr("stroke-width", 1).style("pointer-events", "none");
4975
+ });
4976
+ rowSeparatorGroup.lower();
4977
+ };
4978
+ const drawBarsAndLabels = (group, newXScale, getYPosition) => {
4979
+ const barsGroup = group.append("g").attr("class", "bars-group");
4980
+ const renderBarHeight = barHeight;
4981
+ const labelPadding = 10;
4982
+ barsGroup.selectAll(".bar-background").data(data).enter().append("rect").attr("class", "bar-background").attr("x", (d) => newXScale(getAdjustedStart(d))).attr("y", (_d, i) => getYPosition(i)).attr("height", renderBarHeight).attr("width", (d) => newXScale(getAdjustedEnd(d)) - newXScale(getAdjustedStart(d))).attr("fill", (d) => getStatusColor(d.status)).attr("rx", borderRadius).attr("ry", borderRadius);
4983
+ barsGroup.selectAll(".bar-head").data(data).enter().append("path").attr("class", "bar-head").attr("fill", (d) => d.color).attr("d", (d, i) => {
4984
+ const x = newXScale(getAdjustedStart(d));
4985
+ const y = getYPosition(i);
4986
+ const barWidth = newXScale(getAdjustedEnd(d)) - newXScale(getAdjustedStart(d));
4987
+ const headWidth = Math.min(barWidth * 0.5, 20);
4988
+ return roundedRectPath(x, y, headWidth, renderBarHeight, borderRadius);
4989
+ });
4990
+ const labels = group.append("g").attr("class", "labels");
4991
+ const labelSelection = labels.selectAll(".label").data(data).enter().append("text").attr("class", "label").attr("y", (_d, i) => getYPosition(i) + renderBarHeight / 2).attr("dy", "0.35em").text((d) => d.label).style("fill", "black").style("font-size", "12px").style("pointer-events", "none");
4992
+ labelSelection.each(function(d) {
4993
+ const barWidth = newXScale(getAdjustedEnd(d)) - newXScale(getAdjustedStart(d));
4994
+ const headWidth = Math.min(barWidth * 0.5, 20);
4995
+ const availableInsideWidth = barWidth - headWidth - labelPadding;
4996
+ const insideX = newXScale(getAdjustedStart(d)) + headWidth + labelPadding;
4997
+ const textElement = d33.select(this);
4998
+ const textWidth = this.getComputedTextLength();
4999
+ if (textWidth > availableInsideWidth) {
5000
+ textElement.attr("x", newXScale(getAdjustedEnd(d)) + labelPadding).attr("data-label-position", "outside");
5001
+ } else {
5002
+ textElement.attr("x", insideX).attr("data-label-position", "inside");
5003
+ }
5004
+ });
5005
+ };
5006
+ const renderChart = (transform) => {
5007
+ const headerMarginBottom = 10;
5008
+ const getYPosition = (index) => headerMarginBottom + index * (barHeight + barSpacing);
5009
+ const newXScale = transform.rescaleX(xScale);
5010
+ chartGroup.selectAll("*").remove();
5011
+ const timeBuckets = buildTimeBuckets(newXScale.domain());
5012
+ const headersGroupLayer1 = chartGroup.append("g");
5013
+ const headersGroupLayer2 = chartGroup.append("g");
5014
+ drawHeaders(headersGroupLayer1, headersGroupLayer2, newXScale, timeBuckets);
5015
+ const gridlineData = getGridlineData(viewMode, timeBuckets, newXScale.domain());
5016
+ drawGridlines(headersGroupLayer2, gridlineData, newXScale);
5017
+ drawRowSeparators(chartGroup, getYPosition);
5018
+ drawBarsAndLabels(chartGroup, newXScale, getYPosition);
5019
+ };
5020
+ const fixedScale = VIEW_MODE_SCALE[viewMode];
5021
+ const dragBehavior = d33.zoom().scaleExtent([fixedScale, fixedScale]).on("zoom", (event) => {
5022
+ const constrainedTransform = d33.zoomIdentity.translate(event.transform.x, event.transform.y).scale(fixedScale);
5023
+ renderChart(constrainedTransform);
5024
+ });
5025
+ svg.call(dragBehavior);
5026
+ const todayPosition = xScale(/* @__PURE__ */ new Date());
5027
+ const translateX = width + leftPanelRef.current.offsetWidth * 1.5 - todayPosition * fixedScale;
5028
+ const initialTransform = d33.zoomIdentity.translate(translateX, 0).scale(fixedScale);
5029
+ svg.call(dragBehavior.transform, initialTransform);
5030
+ }, [
5031
+ data,
5032
+ width,
5033
+ height,
5034
+ viewMode,
5035
+ barHeight,
5036
+ barSpacing,
5037
+ totalHeaderHeight,
5038
+ headersGroupLayer1Height,
5039
+ headersGroupLayer2Height
5040
+ ]);
5041
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
5042
+ "div",
5043
+ {
5044
+ style: {
5045
+ display: "flex",
5046
+ flexDirection: "column",
5047
+ padding: "20px",
5048
+ width: `${width}px`,
5049
+ height: `${height}px`,
5050
+ border: "1px solid #ccc",
5051
+ borderRadius: "10px",
5052
+ backgroundColor: "#fff",
5053
+ overflow: "hidden"
5054
+ },
5055
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
5056
+ "div",
5057
+ {
5058
+ style: {
5059
+ display: "flex",
5060
+ flex: 1,
5061
+ overflow: "auto",
5062
+ overflowX: "hidden",
5063
+ position: "relative"
5064
+ },
5065
+ children: [
5066
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(RowOverlay, { data, barHeight, barSpacing, totalHeaderHeight }),
5067
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
5068
+ "div",
5069
+ {
5070
+ ref: leftPanelRef,
5071
+ style: {
5072
+ width: "520px",
5073
+ minWidth: "500px",
5074
+ display: "flex",
5075
+ flexDirection: "column",
5076
+ backgroundColor: "#fff",
5077
+ borderRight: "1px solid #ddd",
5078
+ zIndex: 2
5079
+ },
5080
+ children: [
5081
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
5082
+ "div",
5083
+ {
5084
+ style: {
5085
+ display: "grid",
5086
+ gridTemplateColumns: "200px 100px 100px 100px",
5087
+ fontSize: "12px",
5088
+ fontWeight: "bold",
5089
+ color: "#666",
5090
+ backgroundColor: "#fafafa",
5091
+ borderBottom: "2px solid #ddd",
5092
+ padding: "0 8px",
5093
+ height: `${totalHeaderHeight}px`,
5094
+ marginBottom: "10px",
5095
+ position: "sticky",
5096
+ top: 0,
5097
+ alignItems: "center",
5098
+ boxSizing: "border-box"
5099
+ },
5100
+ children: [
5101
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { children: "\u0E42\u0E04\u0E23\u0E07\u0E01\u0E32\u0E23" }),
5102
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { children: "\u0E27\u0E31\u0E19\u0E40\u0E23\u0E34\u0E48\u0E21" }),
5103
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { children: "\u0E01\u0E33\u0E2B\u0E19\u0E14\u0E2A\u0E48\u0E07" }),
5104
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { children: "\u0E2A\u0E16\u0E32\u0E19\u0E30" })
5105
+ ]
5106
+ }
5107
+ ),
5108
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { ref: dataContainerRef, children: data.map((element) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ProjectRow, { element, barHeight, barSpacing }, element.id)) })
5109
+ ]
5110
+ }
5111
+ ),
5112
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
5113
+ "div",
5114
+ {
5115
+ style: {
5116
+ flex: 1,
5117
+ minWidth: 0,
5118
+ position: "relative",
5119
+ overflow: "visible"
5120
+ },
5121
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
5122
+ "div",
5123
+ {
5124
+ style: {
5125
+ position: "absolute",
5126
+ right: 0,
5127
+ width: "max-content",
5128
+ zIndex: 1
5129
+ },
5130
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("svg", { ref: svgRef })
5131
+ }
5132
+ )
5133
+ }
5134
+ )
5135
+ ]
5136
+ }
5137
+ )
5138
+ }
5139
+ );
5140
+ };
4687
5141
  // Annotate the CommonJS export names for ESM import in node:
4688
5142
  0 && (module.exports = {
4689
5143
  AntDModal,
@@ -4700,6 +5154,7 @@ var PieChart = ({
4700
5154
  DatePickerRange,
4701
5155
  FileUploader,
4702
5156
  FilterPopUp,
5157
+ GanttChart,
4703
5158
  GhostButton,
4704
5159
  HeadingPage,
4705
5160
  Indicator,