first commit

This commit is contained in:
Your NamebaishaliHolocron
2026-06-15 12:57:03 +05:30
commit b9ac5ae0b2
398 changed files with 49583 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
TextButton,
FormComboboxInput,
Input,
Button
} from "ikoncomponents";
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
export default function ResourceModal({ tasks, setResourceModal ,addMultipleRows}: { tasks: any[], setResourceModal: any, addMultipleRows: any }) {
const form = useForm({
defaultValues: {
rows: 1,
selectAll: false,
sheets: []
}
});
const { register, watch, setValue } = form;
const selectAll = watch("selectAll");
const sheets = watch("sheets");
const tasksLength = tasks?.length || 0;
// 1. Single Effect: Sync "Select All" checkbox UI when individual items change
useEffect(() => {
const allSelected = sheets.length === tasksLength && tasksLength > 0;
// Use { shouldValidate: true } if you have validation rules
setValue("selectAll", allSelected);
}, [sheets, tasksLength, setValue]);
// 2. Manual Handler: When "Select All" is clicked
const handleSelectAllChange = (e) => {
const isChecked = e.target.checked;
setValue("selectAll", isChecked);
if (isChecked) {
setValue("sheets", tasks.map((t) => t.taskName));
} else {
setValue("sheets", []);
}
};
const handleOnSubmit = async (data) => {
console.log(data);
// for(var i in data.sheets)
await addMultipleRows(data.rows, data.sheets);
setResourceModal(false);
};
const setClose = () => {
// Logic to close the modal
form.reset(); // Reset form values when closing
setResourceModal(false);
}
return (
<Dialog open={true} onOpenChange={() => {setClose()}}>
<DialogContent className="w-[600px]">
<DialogHeader>
<DialogTitle className="text-xl font-bold">Add multiple rows</DialogTitle>
</DialogHeader>
<form
onSubmit={form.handleSubmit(handleOnSubmit)}
id="resource-form"
className="space-y-4"
>
{/* Row count */}
<div className="flex gap-4 flex-col ">
<label className="font-medium ">
No. of Row(s) to be Inserted
</label>
{/* <Input
form={form}
placeholder="No. of Row(s) to be Inserted"
name="rows"
type="number"
/> */}
<Input
type="number"
{...form.register("rows")}
className="border rounded px-2 py-1 "
/>
</div>
<hr className="border-gray-200" />
{/* Select All */}
<div className="flex items-center gap-2">
<input type="checkbox" {...form.register("selectAll")} onChange={handleSelectAllChange}/>
<label>Select All</label>
</div>
{/* Sheet list */}
<div className="pl-6 space-y-2">
{tasks.map((task) => (
<div key={task.id} className="flex items-center gap-2">
<input
type="checkbox"
value={task.taskName}
{...form.register("sheets")}
/>
<label>{task.taskName}</label>
</div>
))}
</div>
</form>
<DialogFooter className="flex gap-2">
<Button variant={"outline"} type="submit" form="resource-form">
Add
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,359 @@
import moment from "moment";
// types.ts (Optional but recommended)
export interface Cell {
index: number;
value?: string | number;
}
export interface Row {
index: number;
cells: Cell[];
}
export interface ResourceAllocation {
allocation: Record<string, number>;
detailedAllocation: Record<string, any>;
resourceType: string;
role: string;
gradeId: number | string | null;
employeeName: string;
taskName: string;
resourceId: string;
taskId: number | null;
}
/**
* Transforms the spreadsheet-like ds1 structure into the flat ds2 allocation list.
*/
export function transformDs1ToDs2(ds1: any[]){
if (!ds1 || ds1.length === 0) return [];
const rows = ds1[0].rows;
const ds2: any[] = [];
// 1. Map columns for months from header (Index 0)
const monthMap: Record<number, string> = {};
rows[0].cells.forEach((cell: any) => {
if (cell.index >= 5 && typeof cell.value === "string") {
monthMap[cell.index] = cell.value.replace(/\s+/g, "_");
}
});
// Variables to track the current task context
let currentTaskName = "";
let currentTaskId: number | null = null;
rows.slice(1).forEach((row: any) => {
const cells = row.cells;
// Extract Index 0 (Column A) - Used for Task context or Subtotal checks
const colA = cells.find((c: any) => c.index === 0)?.value?.toString() || "";
// Extract Index 1 (Column B) - Staff/Employee
const colB = cells.find((c: any) => c.index === 1)?.value?.toString() || "";
// --- LOGIC A: Detect Group Header (Task Info) ---
// If it starts with '˄', it defines the Task for the following rows
if (colA.startsWith("˄")) {
currentTaskName = colA.replace("˄", "").trim();
// Using row index or a hash as a placeholder for taskId if not explicitly in ds1
currentTaskId = cells[1]?.textAlign; // Example heuristic for taskId
return;
}
// --- LOGIC B: Skip Totals/Subtotals ---
if (colA.toLowerCase().includes("subtotal") || colA.toLowerCase() === "total" || !colB) {
return;
}
// --- LOGIC C: Process Resource Row ---
// Handle "Abhishek Show - K2408137" splitting
let employeeName = colB;
let resourceId = "";
if (colB.includes("-")) {
const parts = colB.split("-");
employeeName = parts[0].trim();
resourceId = parts[1].trim();
}
const record: any = {
allocation: {},
detailedAllocation: {},
resourceType: "Named",
role: cells.find((c: any) => c.index === 3)?.value || "",
gradeId: cells.find((c: any) => c.index === 2)?.value || null,
employeeName: employeeName,
resourceId: resourceId,
taskName: currentTaskName, // Taken from the last seen Group Header
taskId: currentTaskId, // Taken from the last seen Group Header
};
// Extract allocations
cells.forEach((cell: any) => {
if (monthMap[cell.index]) {
const monthKey = monthMap[cell.index];
if (typeof cell.value === "number") {
record.allocation[monthKey] = cell.value;
}
record.detailedAllocation[monthKey] = {};
}
});
ds2.push(record);
});
return ds2;
}
export function transformDataToSheet(ds2: any[]) {
if (!ds2 || ds2.length === 0) return {};
return ds2.reduce((acc: any, curr: any) => {
const { taskName, employeeName, resourceId, gradeId, role, allocation, id } = curr;
// Initialize task group if it doesn't exist
if (!acc[taskName]) {
acc[taskName] = [];
}
// Push simplified resource object
acc[taskName].push({
staff: `${employeeName} - ${resourceId}`,
grade: gradeId,
role: role,
id: id,
...allocation // Spreads monthwise FTE (e.g., Dec_2025: 0.12)
});
return acc;
}, {});
}
export async function addMultipleRows(spreadsheetRef: any, rows: number, tasks: {}, taskCells: {}, taskMonths: []) {
let newTaskCellsWithHeader = { headerTitle: "", taskCells: taskCells };
for (var t = 0; t < tasks.length; t++) {
console.log("Batching multiple row addition");
for (var r = 0; r < rows; r++) {
let subtotalRow = 0;
for (var tc of newTaskCellsWithHeader.taskCells) {
if (tc.headerTitle == tasks[t]) {
subtotalRow = tc.subtotalRow;
break;
}
}
if (subtotalRow == 0) {
continue;
}
// await addRowBelow(spreadsheetRef, {row: , taskCells: taskCells})
const ss = spreadsheetRef.current;
const sheet = ss.activeSheet();
const row = subtotalRow - 2;
// Insert row below
sheet.insertRow(row + 1);
const data = (await ss.saveJSON())?.sheets;
const rows = data[0].rows
let newRows = []
rows.forEach((element, i) => {
if (i == row) {
let elm = structuredClone(element)
const frm = element.cells[4].formula;
element.cells[4].value = 0;
element.cells[4].formula = frm?.replaceAll(i + 1 + "", i + 2 + "")
// element.cells[4].formula = frm.replaceAll(row+1+"",row+2+"" )
if (element.cells[5].validation)
element.cells[5].validation.from = element.cells[5].validation.from.replaceAll(row + 1 + "", row + 2 + "")
for (var t in taskMonths) {
element.cells[5 + parseInt(t)].value = ""
element.cells[5 + parseInt(t)].enable = false
}
newRows.push(elm);
}
if (i >= row) {
element.index = i + 1;
if (i == row) {
element.cells[1].value = ""
element.cells[2].value = ""
element.cells[3].value = ""
}
}
newRows.push(element); // 👈 keep existing row
});
data[0].rows = newRows
ss.fromJSON({ sheets: data })
const totalCols = sheet._columns._count;
newTaskCellsWithHeader = await updateHeaderAndSubtotal(newTaskCellsWithHeader.taskCells, row)
// setTaskCells(newTaskCellsWithHeader.taskCells)
// subtotalRow++;
}
for (var i of newTaskCellsWithHeader.taskCells) {
const sheet = spreadsheetRef.current.activeSheet();
const monthsArr = [4]
taskMonths.forEach((t, i) => { monthsArr.push(i + 5) })
// if(i.headerTitle = taskName){
monthsArr.forEach((t) => {
const cell = sheet.range(i.subtotalRow - 1, t);
let sumF = ""
for (let v = i.headerRow + 1; v < i.subtotalRow; v++) {
if (sumF == "") {
sumF += columnName(t) + v;
continue
}
sumF += "," + columnName(t) + v
}
// sumF.slice(sumF.lastIndexOf(","), sumF.length)
// let formula = cell.formula();
// formula = formula.replaceAll(formula.charAt(5),Number(formula.charAt(5))-1)
cell.formula(`SUM(${sumF})`);
})
// const row = sheet.range(i.subtotalRow, c);
// cells = .map(r => `E${r}`).join(",")
// }
}
}
return newTaskCellsWithHeader;
}
export async function updateHeaderAndSubtotal(taskCells = [], row = 0) {
// 1. Find the target section
const targetIndex = taskCells.findIndex(
({ headerRow, subtotalRow }) => row >= headerRow && row <= subtotalRow
);
// If no section found, return the original data structure
if (targetIndex === -1) {
return { headerTitle: null, taskCells };
}
const targetName = taskCells[targetIndex].headerTitle;
// 2. Map through the array to create a NEW updated version
const updated = taskCells.map((item, index) => {
// If it's the section we are currently in, increment the subtotal
if (index === targetIndex) {
return {
...item,
subtotalRow: item.subtotalRow + 1,
};
}
// If it's a section AFTER the current one, shift everything down by 1
if (index > targetIndex) {
return {
...item,
headerRow: item.headerRow + 1,
subtotalRow: item.subtotalRow + 1,
};
}
// Otherwise, leave it as is
return item;
});
return { headerTitle: targetName, taskCells: updated };
}
export function getTaskTimeline(tasks: any[]) {
if (!tasks || tasks.length === 0) return [];
// 1. Calculate End Dates for all tasks
const tasksWithEnds = tasks.map(t => {
const taskStart = moment(t.taskStart);
const taskDuration = parseFloat(t.taskDuration);
// Calculate logical end date
let endDate = taskStart.clone().add(Math.floor(taskDuration), "months");
const daysInLastMonth = endDate.daysInMonth();
endDate.subtract(1, "day");
endDate.add(daysInLastMonth * (taskDuration % 1), "days");
if (endDate.isBefore(taskStart)) endDate = taskStart.clone();
return {
...t,
start: taskStart,
end: endDate
};
});
// 2. Define the global range based on tasks
const minStart = moment.min(tasksWithEnds.map(t => t.start)).clone().startOf('month');
const maxEnd = moment.max(tasksWithEnds.map(t => t.end)).clone().endOf('month');
const timeline = [];
let currentMoment = minStart.clone();
// 3. Iterate through each month
while (currentMoment.isSameOrBefore(maxEnd, 'month')) {
const monthStart = currentMoment.clone().startOf('month');
const monthEnd = currentMoment.clone().endOf('month');
const daysInMonth = currentMoment.daysInMonth();
// Filter and calculate duration for tasks in THIS specific month
const monthTasks = tasksWithEnds
.filter(t => t.start.isSameOrBefore(monthEnd) && t.end.isSameOrAfter(monthStart))
.map(t => {
const overlapStart = moment.max(t.start, monthStart);
const overlapEnd = moment.min(t.end, monthEnd);
const overlapDays = overlapEnd.diff(overlapStart, 'days') + 1;
const taskMonthDuration = parseFloat((overlapDays / daysInMonth).toFixed(2));
return {
taskName: t.taskName,
taskDuration: taskMonthDuration
};
});
// Generate the month name string as requested: "Jan 2024"
const dateObj = currentMoment.toDate();
const monthName = dateObj.toLocaleString("en-US", { month: "long" });
const year = dateObj.getFullYear();
timeline.push({
month: `${monthName.slice(0, 3)} ${year}`,
tasks: monthTasks
});
currentMoment.add(1, 'month');
}
return timeline;
}
function columnName(n) {
let s = "";
while (n >= 0) {
s = String.fromCharCode((n % 26) + 65) + s;
n = Math.floor(n / 26) - 1;
}
return s;
}