@esic-lab/data-core-ui 0.0.56 → 0.0.58

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.mjs CHANGED
@@ -4053,7 +4053,8 @@ function ProfileSelect({
4053
4053
  assignUser,
4054
4054
  mode,
4055
4055
  className,
4056
- onUpdateAssignUser
4056
+ onUpdateAssignUser,
4057
+ placeholder
4057
4058
  }) {
4058
4059
  const [maxVisible, setMaxVisible] = useState18(4);
4059
4060
  const [userNotAssign, setUserNotAssign] = useState18([]);
@@ -4099,9 +4100,7 @@ function ProfileSelect({
4099
4100
  const visibleUsers = showPlus ? assignUser.slice(0, maxVisible - 1) : assignUser;
4100
4101
  const extraCount = assignUser.length - (maxVisible - 1);
4101
4102
  const normalizedSearch = search.trim().toLowerCase();
4102
- const filteredAssigned = normalizedSearch ? assignUser.filter(
4103
- (u) => u.name.toLowerCase().includes(normalizedSearch)
4104
- ) : assignUser;
4103
+ const filteredAssigned = normalizedSearch ? assignUser.filter((u) => u.name.toLowerCase().includes(normalizedSearch)) : assignUser;
4105
4104
  const filteredUnassigned = normalizedSearch ? userNotAssign.filter(
4106
4105
  (u) => u.name.toLowerCase().includes(normalizedSearch)
4107
4106
  ) : userNotAssign;
@@ -4113,13 +4112,13 @@ function ProfileSelect({
4113
4112
  className: "p-2 border rounded cursor-pointer bg-white",
4114
4113
  onClick: () => setIsShowSelect(!isShowSelect)
4115
4114
  }
4116
- ) }) : mode === "showAssign" ? /* @__PURE__ */ jsxs36(
4115
+ ) }) : mode === "showAssign" ? /* @__PURE__ */ jsx42(
4117
4116
  "div",
4118
4117
  {
4119
- className: `w-full h-[40px] flex -space-x-2 p-2 cursor-pointer ${className}`,
4118
+ className: `w-full h-[40px] flex items-center p-2 cursor-pointer ${className}`,
4120
4119
  onClick: () => setIsShowSelect(!isShowSelect),
4121
- children: [
4122
- visibleUsers.map((user) => /* @__PURE__ */ jsx42("div", { className: "flex items-center cursor-point", children: /* @__PURE__ */ jsxs36("label", { className: "relative group cursor-pointer", children: [
4120
+ children: visibleUsers.length === 0 ? placeholder ? /* @__PURE__ */ jsx42("span", { className: "body-1 text-gray-400 select-none", children: placeholder }) : null : /* @__PURE__ */ jsxs36(Fragment8, { children: [
4121
+ /* @__PURE__ */ jsx42("div", { className: "flex -space-x-2", children: visibleUsers.map((user) => /* @__PURE__ */ jsx42("div", { className: "flex items-center", children: /* @__PURE__ */ jsxs36("label", { className: "relative group cursor-pointer", children: [
4123
4122
  /* @__PURE__ */ jsx42(
4124
4123
  "img",
4125
4124
  {
@@ -4136,16 +4135,19 @@ function ProfileSelect({
4136
4135
  /* @__PURE__ */ jsx42(
4137
4136
  "span",
4138
4137
  {
4139
- 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",
4140
- onClick: () => onUpdateAssignUser(user, "remove"),
4138
+ 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",
4139
+ onClick: (e) => {
4140
+ e.stopPropagation();
4141
+ onUpdateAssignUser(user, "remove");
4142
+ },
4141
4143
  children: /* @__PURE__ */ jsx42(IconX4, { className: "w-4 h-4 text-red-500" })
4142
4144
  }
4143
4145
  )
4144
- ] }) }, user.id)),
4146
+ ] }) }, user.id)) }),
4145
4147
  showPlus && /* @__PURE__ */ jsxs36(
4146
4148
  "div",
4147
4149
  {
4148
- className: "border border-white flex items-center justify-center bg-gray-300 text-black text-sm",
4150
+ className: "border border-white flex items-center justify-center bg-gray-300 text-black text-sm ml-2",
4149
4151
  style: {
4150
4152
  width: avatarSize,
4151
4153
  height: avatarSize,
@@ -4157,7 +4159,7 @@ function ProfileSelect({
4157
4159
  ]
4158
4160
  }
4159
4161
  )
4160
- ]
4162
+ ] })
4161
4163
  }
4162
4164
  ) : /* @__PURE__ */ jsxs36("div", { className: `w-full h-[40px] flex -space-x-2 p-2 ${className}`, children: [
4163
4165
  visibleUsers.map((user) => /* @__PURE__ */ jsx42("div", { className: "flex items-center ", children: /* @__PURE__ */ jsx42("label", { className: "relative group ", children: /* @__PURE__ */ jsx42(
@@ -4288,7 +4290,7 @@ var QRCodeGenerator = ({
4288
4290
  fileBaseName = "qr-code"
4289
4291
  }) => {
4290
4292
  const canvasRef = useRef7(null);
4291
- const [format4, setFormat] = useState19("png");
4293
+ const [format5, setFormat] = useState19("png");
4292
4294
  const [exportSize, setExportSize] = useState19(defaultExportSize);
4293
4295
  const sizeOption = [
4294
4296
  {
@@ -4342,7 +4344,7 @@ var QRCodeGenerator = ({
4342
4344
  }, [url, previewSize]);
4343
4345
  const download = async () => {
4344
4346
  try {
4345
- if (format4 === "svg") {
4347
+ if (format5 === "svg") {
4346
4348
  const svgString = await QRCode.toString(url, {
4347
4349
  type: "svg",
4348
4350
  width: exportSize,
@@ -4358,9 +4360,9 @@ var QRCodeGenerator = ({
4358
4360
  width: exportSize,
4359
4361
  margin: 1
4360
4362
  });
4361
- const mime = format4 === "png" ? "image/png" : "image/jpeg";
4362
- const dataURL = format4 === "jpeg" ? offscreen.toDataURL(mime, 0.92) : offscreen.toDataURL(mime);
4363
- triggerDownload(dataURL, `${fileBaseName}.${format4}`);
4363
+ const mime = format5 === "png" ? "image/png" : "image/jpeg";
4364
+ const dataURL = format5 === "jpeg" ? offscreen.toDataURL(mime, 0.92) : offscreen.toDataURL(mime);
4365
+ triggerDownload(dataURL, `${fileBaseName}.${format5}`);
4364
4366
  }
4365
4367
  } catch (err) {
4366
4368
  console.error("Failed to generate QR export:", err);
@@ -4393,7 +4395,7 @@ var QRCodeGenerator = ({
4393
4395
  SelectField,
4394
4396
  {
4395
4397
  label: "\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A",
4396
- value: format4,
4398
+ value: format5,
4397
4399
  onChange: (e) => setFormat(e.target.value),
4398
4400
  options: typeOption
4399
4401
  }
@@ -4531,11 +4533,32 @@ var BarChart = ({
4531
4533
  (enter) => enter.append("text").attr("class", "x-axis-label").attr("fill", "currentColor").attr("x", width).attr("y", height - margin.bottom).attr("text-anchor", "middle").style("font-size", "14px").style("font-family", "Arial").text((d) => d),
4532
4534
  (update) => update.text((d) => d).attr("x", width)
4533
4535
  );
4534
- const t = svg.transition().duration(400);
4536
+ const t = d3.transition().duration(400);
4535
4537
  const bars = g.selectAll("rect.bar").data(data, (d) => d.x);
4536
4538
  bars.join(
4537
- (enter) => enter.append("rect").attr("class", "bar").attr("x", (d) => margin.left).attr("width", x.bandwidth()).attr("y", () => margin.top + y(0)).attr("height", () => innerH - y(0)).attr("rx", 5).attr("ry", 5).style("fill", (d, i) => colorPalette[i % colorPalette.length]).append("title").text((d) => `${d.x}: ${d.y}`).merge(bars).attr("width", x.bandwidth()).attr("y", (d) => y(d.y)).attr("height", (d) => innerH - y(d.y)).call((enter2) => enter2.raise()),
4538
- // Bring the new bars to the front
4539
+ // (enter) =>
4540
+ // enter
4541
+ // .append("rect")
4542
+ // .attr("class", "bar")
4543
+ // .attr("x", (d) => margin.left)
4544
+ // .attr("width", x.bandwidth())
4545
+ // .attr("y", () => margin.top + y(0))
4546
+ // .attr("height", () => innerH - y(0))
4547
+ // .attr("rx", 5) // rounded corners
4548
+ // .attr("ry", 5) // rounded corners
4549
+ // .style("fill", (d, i) => colorPalette[i % colorPalette.length]) // Apply color based on index
4550
+ // .append("title")
4551
+ // .text((d) => `${d.x}: ${d.y}`)
4552
+ // .merge(bars) // merge the existing bars after joining
4553
+ // .attr("width", x.bandwidth())
4554
+ // .attr("y", (d) => y(d.y))
4555
+ // .attr("height", (d) => innerH - y(d.y))
4556
+ // .call((enter) => enter.raise()), // Bring the new bars to the front
4557
+ (enter) => {
4558
+ const rects = enter.append("rect").attr("class", "bar").attr("x", (d) => margin.left).attr("width", x.bandwidth()).attr("y", () => margin.top + y(0)).attr("height", () => innerH - y(0)).attr("rx", 5).attr("ry", 5).style("fill", (d, i) => colorPalette[i % colorPalette.length]);
4559
+ rects.append("title").text((d) => `${d.x}: ${d.y}`);
4560
+ return rects.merge(bars).attr("width", x.bandwidth()).attr("y", (d) => y(d.y)).attr("height", (d) => innerH - y(d.y)).call((g2) => g2.raise());
4561
+ },
4539
4562
  (update) => update.call((update2) => update2.raise()).transition(t).attr("x", (d) => x(d.x) ?? 0).attr("width", x.bandwidth()).attr("y", (d) => y(d.y)).attr("height", (d) => innerH - y(d.y)).style("fill", (d, i) => colorPalette[i % colorPalette.length]),
4540
4563
  // Update color on update
4541
4564
  (exit) => exit.transition(t).attr("y", margin.top + y(0)).attr("height", innerH - y(0)).remove()
@@ -4606,6 +4629,466 @@ var PieChart = ({
4606
4629
  ] })
4607
4630
  ] });
4608
4631
  };
4632
+
4633
+ // src/GanttChart/GanttChart.tsx
4634
+ import { useEffect as useEffect11, useRef as useRef10, useState as useState20 } from "react";
4635
+ import * as d33 from "d3";
4636
+ import {
4637
+ format as format4,
4638
+ getWeek,
4639
+ eachWeekOfInterval,
4640
+ startOfYear,
4641
+ endOfYear,
4642
+ startOfDay,
4643
+ addDays,
4644
+ eachHourOfInterval
4645
+ } from "date-fns";
4646
+ import { jsx as jsx47, jsxs as jsxs41 } from "react/jsx-runtime";
4647
+ var LAYOUT = {
4648
+ barHeight: 40,
4649
+ barSpacing: 10,
4650
+ headersGroupLayer1Height: 30,
4651
+ headersGroupLayer2Height: 35
4652
+ };
4653
+ var HEADER_FONTS = {
4654
+ layer1: 16,
4655
+ layer2: 12
4656
+ };
4657
+ var VIEW_MODE_SCALE = {
4658
+ year: 0.5,
4659
+ month: 2,
4660
+ week: 10
4661
+ };
4662
+ var STATUS_META = {
4663
+ pending: { color: "#F16965", label: "\u0E23\u0E2D\u0E14\u0E33\u0E40\u0E19\u0E34\u0E19\u0E01\u0E32\u0E23" },
4664
+ "in-progress": { color: "#FFBB33", label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E14\u0E33\u0E40\u0E19\u0E34\u0E19\u0E01\u0E32\u0E23" },
4665
+ completed: { color: "#69C57D", label: "\u0E40\u0E2A\u0E23\u0E47\u0E08\u0E2A\u0E34\u0E49\u0E19" }
4666
+ };
4667
+ var THAI_MONTHS = {
4668
+ January: "\u0E21.\u0E04.",
4669
+ February: "\u0E01.\u0E1E.",
4670
+ March: "\u0E21\u0E35.\u0E04.",
4671
+ April: "\u0E40\u0E21.\u0E22.",
4672
+ May: "\u0E1E.\u0E04.",
4673
+ June: "\u0E21\u0E34.\u0E22.",
4674
+ July: "\u0E01.\u0E04.",
4675
+ August: "\u0E2A.\u0E04.",
4676
+ September: "\u0E01.\u0E22.",
4677
+ October: "\u0E15.\u0E04.",
4678
+ November: "\u0E1E.\u0E22.",
4679
+ December: "\u0E18.\u0E04."
4680
+ };
4681
+ var THAI_DAYS = ["\u0E2D\u0E32.", "\u0E08.", "\u0E2D.", "\u0E1E.", "\u0E1E\u0E24.", "\u0E28.", "\u0E2A."];
4682
+ 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}`;
4683
+ var formatThaiMonth = (date) => {
4684
+ const monthName = format4(date, "MMMM");
4685
+ return THAI_MONTHS[monthName] || monthName;
4686
+ };
4687
+ var formatThaiDate = (date) => date ? date.toLocaleDateString("th-TH", { day: "numeric", month: "short" }) : "-";
4688
+ var isDateOnlyValue = (dt) => {
4689
+ const localZero = dt.getHours() === 0 && dt.getMinutes() === 0 && dt.getSeconds() === 0 && dt.getMilliseconds() === 0;
4690
+ const utcZero = dt.getUTCHours() === 0 && dt.getUTCMinutes() === 0 && dt.getUTCSeconds() === 0 && dt.getUTCMilliseconds() === 0;
4691
+ return localZero || utcZero;
4692
+ };
4693
+ var getAdjustedStart = (d) => {
4694
+ if (!d.startDate && d.endDate) {
4695
+ const endDate = d.endDate;
4696
+ const quarter = Math.floor(endDate.getMonth() / 3);
4697
+ const monthIndex = quarter * 3;
4698
+ return startOfDay(new Date(endDate.getFullYear(), monthIndex, 1));
4699
+ }
4700
+ if (d.startDate) {
4701
+ return isDateOnlyValue(d.startDate) ? startOfDay(d.startDate) : d.startDate;
4702
+ }
4703
+ return startOfDay(/* @__PURE__ */ new Date());
4704
+ };
4705
+ var getAdjustedEnd = (d) => {
4706
+ if (!d.startDate && d.endDate) {
4707
+ const endDate = d.endDate;
4708
+ const quarter = Math.floor(endDate.getMonth() / 3);
4709
+ const endMonth = (quarter + 1) * 3;
4710
+ return startOfDay(addDays(new Date(endDate.getFullYear(), endMonth, 0), 1));
4711
+ }
4712
+ if (d.endDate) {
4713
+ return isDateOnlyValue(d.endDate) ? startOfDay(addDays(d.endDate, 1)) : d.endDate;
4714
+ }
4715
+ return startOfDay(/* @__PURE__ */ new Date());
4716
+ };
4717
+ var getStatusColor = (status2) => STATUS_META[status2]?.color ?? STATUS_META.pending.color;
4718
+ var getStatusLabel = (status2) => STATUS_META[status2]?.label ?? STATUS_META.pending.label;
4719
+ var ProjectRow = ({ element, barHeight, barSpacing }) => {
4720
+ const safeStartDate = element.startDate instanceof Date ? element.startDate : null;
4721
+ const safeEndDate = element.endDate instanceof Date ? element.endDate : null;
4722
+ const safeStatus = element.status || "pending";
4723
+ const statusColor = getStatusColor(safeStatus);
4724
+ const statusLabel = getStatusLabel(safeStatus);
4725
+ return /* @__PURE__ */ jsxs41(
4726
+ "div",
4727
+ {
4728
+ style: {
4729
+ display: "grid",
4730
+ gridTemplateColumns: "200px 100px 100px 100px",
4731
+ alignItems: "center",
4732
+ height: `${barHeight}px`,
4733
+ fontSize: "12px",
4734
+ marginBottom: `${barSpacing}px`
4735
+ },
4736
+ children: [
4737
+ /* @__PURE__ */ jsxs41("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
4738
+ /* @__PURE__ */ jsx47(
4739
+ "span",
4740
+ {
4741
+ style: {
4742
+ display: "inline-block",
4743
+ width: "10px",
4744
+ height: "10px",
4745
+ borderRadius: "50%",
4746
+ backgroundColor: element.color || "#999"
4747
+ }
4748
+ }
4749
+ ),
4750
+ /* @__PURE__ */ jsx47("span", { style: { color: "#333" }, children: element.label })
4751
+ ] }),
4752
+ /* @__PURE__ */ jsx47("div", { style: { color: "#666" }, children: formatThaiDate(safeStartDate) }),
4753
+ /* @__PURE__ */ jsx47("div", { style: { color: "#666" }, children: formatThaiDate(safeEndDate) }),
4754
+ /* @__PURE__ */ jsx47(
4755
+ "button",
4756
+ {
4757
+ style: {
4758
+ height: `${barHeight}px`,
4759
+ minWidth: "100%",
4760
+ padding: "4px 8px",
4761
+ borderRadius: "4px",
4762
+ fontSize: "11px",
4763
+ backgroundColor: statusColor,
4764
+ color: "#000",
4765
+ border: "none",
4766
+ cursor: "pointer"
4767
+ },
4768
+ children: statusLabel
4769
+ }
4770
+ )
4771
+ ]
4772
+ },
4773
+ element.id
4774
+ );
4775
+ };
4776
+ var RowOverlay = ({ data, barHeight, barSpacing, totalHeaderHeight }) => /* @__PURE__ */ jsx47(
4777
+ "div",
4778
+ {
4779
+ style: {
4780
+ position: "absolute",
4781
+ top: `${totalHeaderHeight + 10}px`,
4782
+ left: 0,
4783
+ right: 0,
4784
+ height: `${data.length * (barHeight + barSpacing)}px`,
4785
+ pointerEvents: "none",
4786
+ zIndex: 5
4787
+ },
4788
+ children: data.map((d, i) => {
4789
+ if (i === 0) return null;
4790
+ const yPos = i * (barHeight + barSpacing) - barSpacing / 2;
4791
+ return /* @__PURE__ */ jsx47(
4792
+ "div",
4793
+ {
4794
+ style: {
4795
+ position: "absolute",
4796
+ top: `${yPos}px`,
4797
+ left: 0,
4798
+ right: 0,
4799
+ height: "1px",
4800
+ backgroundColor: "#d0d0d0"
4801
+ }
4802
+ },
4803
+ `row-line-${d.id}`
4804
+ );
4805
+ })
4806
+ }
4807
+ );
4808
+ var GanttChart = ({ data, width, height }) => {
4809
+ const svgRef = useRef10(null);
4810
+ const leftPanelRef = useRef10(null);
4811
+ const dataContainerRef = useRef10(null);
4812
+ const [viewMode] = useState20("year");
4813
+ const { barHeight, barSpacing, headersGroupLayer1Height, headersGroupLayer2Height } = LAYOUT;
4814
+ const totalHeaderHeight = headersGroupLayer1Height + headersGroupLayer2Height;
4815
+ useEffect11(() => {
4816
+ if (!data || !svgRef.current) return;
4817
+ const margin = { top: 0, right: 20, bottom: 20, left: 20 };
4818
+ const chartHeight = data.length * (barHeight + barSpacing) + margin.top + margin.bottom;
4819
+ const containerWidth = width * 2;
4820
+ const containerHeight = totalHeaderHeight + chartHeight;
4821
+ const borderRadius = 5;
4822
+ if (leftPanelRef.current) {
4823
+ leftPanelRef.current.style.height = `${containerHeight}px`;
4824
+ }
4825
+ if (dataContainerRef.current) {
4826
+ dataContainerRef.current.style.marginTop = "0px";
4827
+ }
4828
+ const svg = d33.select(svgRef.current).attr("width", containerWidth).attr("height", containerHeight).style("background-color", "white");
4829
+ svg.selectAll("*").remove();
4830
+ const chartGroup = svg.append("g").attr("transform", `translate(${margin.left}, ${totalHeaderHeight})`);
4831
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
4832
+ const domainStart = startOfDay(startOfYear(new Date(currentYear, 0, 1)));
4833
+ const domainEnd = startOfDay(addDays(endOfYear(new Date(currentYear, 0, 1)), 1));
4834
+ const xScale = d33.scaleTime().domain([domainStart, domainEnd]).range([0, containerWidth - margin.left - margin.right]);
4835
+ const buildTimeBuckets = (domain) => ({
4836
+ uniqueYears: d33.timeYear.every(1)?.range(domain[0], domain[1]) || [],
4837
+ uniqueMonths: d33.timeMonth.every(1)?.range(domain[0], domain[1]) || [],
4838
+ uniqueWeeks: eachWeekOfInterval({ start: domain[0], end: domain[1] }, { weekStartsOn: 1 }),
4839
+ uniqueDays: d33.timeDay.range(startOfDay(domain[0]), startOfDay(addDays(domain[1], 1))) || []
4840
+ });
4841
+ const renderYearHeaders = (layer1, layer2, newXScale, buckets) => {
4842
+ 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) => {
4843
+ const nextYearStart = startOfDay(new Date(d.getFullYear() + 1, 0, 1));
4844
+ return newXScale(nextYearStart) - newXScale(d);
4845
+ }).attr("height", headersGroupLayer1Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4846
+ layer1.selectAll(".year-label").data(buckets.uniqueYears).enter().append("text").attr("class", "year-label").attr("x", (d) => {
4847
+ const nextYear = new Date(d.getFullYear() + 1, 0, 1);
4848
+ return newXScale(d) + (newXScale(nextYear) - newXScale(d)) / 2;
4849
+ }).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}`);
4850
+ const quarters2 = [];
4851
+ buckets.uniqueYears.forEach((year) => {
4852
+ for (let q = 0; q < 4; q++) {
4853
+ quarters2.push(new Date(year.getFullYear(), q * 3, 1));
4854
+ }
4855
+ });
4856
+ layer2.selectAll(".quarter-bg").data(quarters2).enter().append("rect").attr("class", "quarter-bg").attr("x", (d) => newXScale(d)).attr("y", -headersGroupLayer2Height).attr("width", (d) => {
4857
+ const nextQuarter = startOfDay(new Date(d.getFullYear(), d.getMonth() + 3, 1));
4858
+ return newXScale(nextQuarter) - newXScale(d);
4859
+ }).attr("height", headersGroupLayer2Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4860
+ layer2.selectAll(".quarter-label").data(quarters2).enter().append("text").attr("class", "quarter-label").attr("x", (d) => {
4861
+ const nextQuarter = startOfDay(new Date(d.getFullYear(), d.getMonth() + 3, 1));
4862
+ return newXScale(d) + (newXScale(nextQuarter) - newXScale(d)) / 2;
4863
+ }).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}`);
4864
+ };
4865
+ const renderMonthHeaders = (layer1, layer2, newXScale, buckets) => {
4866
+ 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) => {
4867
+ const nextMonth = startOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 1));
4868
+ return newXScale(nextMonth) - newXScale(d) + 11;
4869
+ }).attr("height", headersGroupLayer1Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4870
+ layer1.selectAll(".month-label").data(buckets.uniqueMonths).enter().append("text").attr("class", "month-label").attr("x", (d) => {
4871
+ const nextMonth = startOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 1));
4872
+ return newXScale(d) + (newXScale(nextMonth) - newXScale(d)) / 2;
4873
+ }).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}`);
4874
+ 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) => {
4875
+ const weekEnd = startOfDay(addDays(d, 7));
4876
+ return newXScale(weekEnd) - newXScale(d) + 1;
4877
+ }).attr("height", headersGroupLayer2Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4878
+ layer2.selectAll(".week-label").data(buckets.uniqueWeeks).enter().append("text").attr("class", "week-label").attr("x", (d) => {
4879
+ const weekEnd = startOfDay(addDays(d, 7));
4880
+ return newXScale(d) + (newXScale(weekEnd) - newXScale(d)) / 2;
4881
+ }).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 ${getWeek(d, { weekStartsOn: 1 })}`);
4882
+ };
4883
+ const renderWeekHeaders = (layer1, layer2, newXScale, buckets) => {
4884
+ 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) => {
4885
+ const weekEnd = startOfDay(addDays(d, 7));
4886
+ return newXScale(weekEnd) - newXScale(d);
4887
+ }).attr("height", headersGroupLayer1Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4888
+ layer1.selectAll(".week-label").data(buckets.uniqueWeeks).enter().append("text").attr("class", "week-label").attr("x", (d) => {
4889
+ const weekEnd = startOfDay(addDays(d, 7));
4890
+ return newXScale(d) + (newXScale(weekEnd) - newXScale(d)) / 2;
4891
+ }).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 ${getWeek(d, { weekStartsOn: 1 })}`);
4892
+ 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) => {
4893
+ const nextDay = startOfDay(addDays(d, 1));
4894
+ return newXScale(nextDay) - newXScale(d);
4895
+ }).attr("height", headersGroupLayer2Height).style("fill", "#fff").style("stroke", "#999").style("stroke-width", 1);
4896
+ layer2.selectAll(".day-of-week").data(buckets.uniqueDays).enter().append("text").attr("class", "day-of-week").attr("x", (d) => {
4897
+ const nextDay = startOfDay(addDays(d, 1));
4898
+ return newXScale(d) + (newXScale(nextDay) - newXScale(d)) / 2;
4899
+ }).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) {
4900
+ const textElement = d33.select(this);
4901
+ textElement.text("");
4902
+ textElement.append("tspan").text(THAI_DAYS[d.getDay()]).attr("x", textElement.attr("x")).attr("dy", "-0.3em");
4903
+ textElement.append("tspan").text(format4(d, "d")).attr("x", textElement.attr("x")).attr("dy", "1.2em");
4904
+ });
4905
+ };
4906
+ const drawHeaders = (layer1, layer2, newXScale, buckets) => {
4907
+ if (viewMode === "year") return renderYearHeaders(layer1, layer2, newXScale, buckets);
4908
+ if (viewMode === "month") return renderMonthHeaders(layer1, layer2, newXScale, buckets);
4909
+ return renderWeekHeaders(layer1, layer2, newXScale, buckets);
4910
+ };
4911
+ const getGridlineData = (mode, buckets, domain) => {
4912
+ if (mode === "year") return buckets.uniqueMonths;
4913
+ if (mode === "month") return buckets.uniqueWeeks;
4914
+ if (mode === "week") return buckets.uniqueDays;
4915
+ return eachHourOfInterval({ start: domain[0], end: domain[1] });
4916
+ };
4917
+ const drawGridlines = (group, gridlineData, newXScale) => {
4918
+ 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);
4919
+ };
4920
+ const drawRowSeparators = (group, getYPosition) => {
4921
+ const rowSeparatorGroup = group.append("g").attr("class", "row-separators");
4922
+ data.forEach((_d, i) => {
4923
+ if (i === 0) return;
4924
+ const y = getYPosition(i) - barSpacing / 2;
4925
+ 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");
4926
+ });
4927
+ rowSeparatorGroup.lower();
4928
+ };
4929
+ const drawBarsAndLabels = (group, newXScale, getYPosition) => {
4930
+ const barsGroup = group.append("g").attr("class", "bars-group");
4931
+ const renderBarHeight = barHeight;
4932
+ const labelPadding = 10;
4933
+ 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);
4934
+ barsGroup.selectAll(".bar-head").data(data).enter().append("path").attr("class", "bar-head").attr("fill", (d) => d.color).attr("d", (d, i) => {
4935
+ const x = newXScale(getAdjustedStart(d));
4936
+ const y = getYPosition(i);
4937
+ const barWidth = newXScale(getAdjustedEnd(d)) - newXScale(getAdjustedStart(d));
4938
+ const headWidth = Math.min(barWidth * 0.5, 20);
4939
+ return roundedRectPath(x, y, headWidth, renderBarHeight, borderRadius);
4940
+ });
4941
+ const labels = group.append("g").attr("class", "labels");
4942
+ 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");
4943
+ labelSelection.each(function(d) {
4944
+ const barWidth = newXScale(getAdjustedEnd(d)) - newXScale(getAdjustedStart(d));
4945
+ const headWidth = Math.min(barWidth * 0.5, 20);
4946
+ const availableInsideWidth = barWidth - headWidth - labelPadding;
4947
+ const insideX = newXScale(getAdjustedStart(d)) + headWidth + labelPadding;
4948
+ const textElement = d33.select(this);
4949
+ const textWidth = this.getComputedTextLength();
4950
+ if (textWidth > availableInsideWidth) {
4951
+ textElement.attr("x", newXScale(getAdjustedEnd(d)) + labelPadding).attr("data-label-position", "outside");
4952
+ } else {
4953
+ textElement.attr("x", insideX).attr("data-label-position", "inside");
4954
+ }
4955
+ });
4956
+ };
4957
+ const renderChart = (transform) => {
4958
+ const headerMarginBottom = 10;
4959
+ const getYPosition = (index) => headerMarginBottom + index * (barHeight + barSpacing);
4960
+ const newXScale = transform.rescaleX(xScale);
4961
+ chartGroup.selectAll("*").remove();
4962
+ const timeBuckets = buildTimeBuckets(newXScale.domain());
4963
+ const headersGroupLayer1 = chartGroup.append("g");
4964
+ const headersGroupLayer2 = chartGroup.append("g");
4965
+ drawHeaders(headersGroupLayer1, headersGroupLayer2, newXScale, timeBuckets);
4966
+ const gridlineData = getGridlineData(viewMode, timeBuckets, newXScale.domain());
4967
+ drawGridlines(headersGroupLayer2, gridlineData, newXScale);
4968
+ drawRowSeparators(chartGroup, getYPosition);
4969
+ drawBarsAndLabels(chartGroup, newXScale, getYPosition);
4970
+ };
4971
+ const fixedScale = VIEW_MODE_SCALE[viewMode];
4972
+ const dragBehavior = d33.zoom().scaleExtent([fixedScale, fixedScale]).on("zoom", (event) => {
4973
+ const constrainedTransform = d33.zoomIdentity.translate(event.transform.x, event.transform.y).scale(fixedScale);
4974
+ renderChart(constrainedTransform);
4975
+ });
4976
+ svg.call(dragBehavior);
4977
+ const todayPosition = xScale(/* @__PURE__ */ new Date());
4978
+ const translateX = width + leftPanelRef.current.offsetWidth * 1.5 - todayPosition * fixedScale;
4979
+ const initialTransform = d33.zoomIdentity.translate(translateX, 0).scale(fixedScale);
4980
+ svg.call(dragBehavior.transform, initialTransform);
4981
+ }, [
4982
+ data,
4983
+ width,
4984
+ height,
4985
+ viewMode,
4986
+ barHeight,
4987
+ barSpacing,
4988
+ totalHeaderHeight,
4989
+ headersGroupLayer1Height,
4990
+ headersGroupLayer2Height
4991
+ ]);
4992
+ return /* @__PURE__ */ jsx47(
4993
+ "div",
4994
+ {
4995
+ style: {
4996
+ display: "flex",
4997
+ flexDirection: "column",
4998
+ padding: "20px",
4999
+ width: `${width}px`,
5000
+ height: `${height}px`,
5001
+ border: "1px solid #ccc",
5002
+ borderRadius: "10px",
5003
+ backgroundColor: "#fff",
5004
+ overflow: "hidden"
5005
+ },
5006
+ children: /* @__PURE__ */ jsxs41(
5007
+ "div",
5008
+ {
5009
+ style: {
5010
+ display: "flex",
5011
+ flex: 1,
5012
+ overflow: "auto",
5013
+ overflowX: "hidden",
5014
+ position: "relative"
5015
+ },
5016
+ children: [
5017
+ /* @__PURE__ */ jsx47(RowOverlay, { data, barHeight, barSpacing, totalHeaderHeight }),
5018
+ /* @__PURE__ */ jsxs41(
5019
+ "div",
5020
+ {
5021
+ ref: leftPanelRef,
5022
+ style: {
5023
+ width: "520px",
5024
+ minWidth: "500px",
5025
+ display: "flex",
5026
+ flexDirection: "column",
5027
+ backgroundColor: "#fff",
5028
+ borderRight: "1px solid #ddd",
5029
+ zIndex: 2
5030
+ },
5031
+ children: [
5032
+ /* @__PURE__ */ jsxs41(
5033
+ "div",
5034
+ {
5035
+ style: {
5036
+ display: "grid",
5037
+ gridTemplateColumns: "200px 100px 100px 100px",
5038
+ fontSize: "12px",
5039
+ fontWeight: "bold",
5040
+ color: "#666",
5041
+ backgroundColor: "#fafafa",
5042
+ borderBottom: "2px solid #ddd",
5043
+ padding: "0 8px",
5044
+ height: `${totalHeaderHeight}px`,
5045
+ marginBottom: "10px",
5046
+ position: "sticky",
5047
+ top: 0,
5048
+ alignItems: "center",
5049
+ boxSizing: "border-box"
5050
+ },
5051
+ children: [
5052
+ /* @__PURE__ */ jsx47("div", { children: "\u0E42\u0E04\u0E23\u0E07\u0E01\u0E32\u0E23" }),
5053
+ /* @__PURE__ */ jsx47("div", { children: "\u0E27\u0E31\u0E19\u0E40\u0E23\u0E34\u0E48\u0E21" }),
5054
+ /* @__PURE__ */ jsx47("div", { children: "\u0E01\u0E33\u0E2B\u0E19\u0E14\u0E2A\u0E48\u0E07" }),
5055
+ /* @__PURE__ */ jsx47("div", { children: "\u0E2A\u0E16\u0E32\u0E19\u0E30" })
5056
+ ]
5057
+ }
5058
+ ),
5059
+ /* @__PURE__ */ jsx47("div", { ref: dataContainerRef, children: data.map((element) => /* @__PURE__ */ jsx47(ProjectRow, { element, barHeight, barSpacing }, element.id)) })
5060
+ ]
5061
+ }
5062
+ ),
5063
+ /* @__PURE__ */ jsx47(
5064
+ "div",
5065
+ {
5066
+ style: {
5067
+ flex: 1,
5068
+ minWidth: 0,
5069
+ position: "relative",
5070
+ overflow: "visible"
5071
+ },
5072
+ children: /* @__PURE__ */ jsx47(
5073
+ "div",
5074
+ {
5075
+ style: {
5076
+ position: "absolute",
5077
+ right: 0,
5078
+ width: "max-content",
5079
+ zIndex: 1
5080
+ },
5081
+ children: /* @__PURE__ */ jsx47("svg", { ref: svgRef })
5082
+ }
5083
+ )
5084
+ }
5085
+ )
5086
+ ]
5087
+ }
5088
+ )
5089
+ }
5090
+ );
5091
+ };
4609
5092
  export {
4610
5093
  AntDModal,
4611
5094
  AntDataTable,
@@ -4621,6 +5104,7 @@ export {
4621
5104
  DatePickerRange,
4622
5105
  FileUploader,
4623
5106
  FilterPopUp,
5107
+ GanttChart,
4624
5108
  GhostButton,
4625
5109
  HeadingPage,
4626
5110
  Indicator,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esic-lab/data-core-ui",
3
- "version": "0.0.56",
3
+ "version": "0.0.58",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -41,8 +41,7 @@
41
41
  "test": "echo \"Error: no test specified\" && exit 1",
42
42
  "storybook": "storybook dev -p 6006",
43
43
  "build-storybook": "storybook build",
44
- "build": "tsup src/index.ts --format esm,cjs --out-dir dist && npm run copy-assets",
45
- "build:dts": "tsup src/index.ts --dts --dts-resolve --out-dir dist",
44
+ "build": "tsup src/index.ts --dts --dts-resolve --format esm,cjs --out-dir dist && npm run copy-assets",
46
45
  "copy-assets": "xcopy src\\assets dist\\assets /E /I /Y",
47
46
  "type-check": "tsc --noEmit"
48
47
  },
@@ -56,6 +55,7 @@
56
55
  "description": "STO-core-UI",
57
56
  "dependencies": {
58
57
  "@ant-design/icons": "^5.6.1",
58
+ "date-fns": "^4.1.0",
59
59
  "@fortawesome/free-solid-svg-icons": "^7.0.1",
60
60
  "@fortawesome/react-fontawesome": "^3.0.2",
61
61
  "@fullcalendar/core": "^6.1.19",
@@ -68,7 +68,6 @@
68
68
  "classnames": "^2.5.1",
69
69
  "cuid": "^3.0.0",
70
70
  "d3": "^7.9.0",
71
- "date-fns": "^4.1.0",
72
71
  "fullcalendar": "^6.1.19",
73
72
  "qrcode": "^1.5.4"
74
73
  },