@optifye/dashboard-core 6.11.36 → 6.11.38
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.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +229 -220
- package/dist/index.mjs +229 -220
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -58783,6 +58783,50 @@ var UserUsageStats = ({
|
|
|
58783
58783
|
] })
|
|
58784
58784
|
] });
|
|
58785
58785
|
};
|
|
58786
|
+
|
|
58787
|
+
// src/lib/utils/teamUsage.ts
|
|
58788
|
+
var ISO_DATE_KEY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
58789
|
+
function formatLocalDateKey(date) {
|
|
58790
|
+
const year = date.getFullYear();
|
|
58791
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
58792
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
58793
|
+
return `${year}-${month}-${day}`;
|
|
58794
|
+
}
|
|
58795
|
+
function parseUsageDate(dateLike) {
|
|
58796
|
+
if (ISO_DATE_KEY_REGEX.test(dateLike)) {
|
|
58797
|
+
const [year, month, day] = dateLike.split("-").map(Number);
|
|
58798
|
+
return new Date(year, month - 1, day);
|
|
58799
|
+
}
|
|
58800
|
+
return new Date(dateLike);
|
|
58801
|
+
}
|
|
58802
|
+
function normalizeUsageDateKey(dateLike) {
|
|
58803
|
+
if (!dateLike) return void 0;
|
|
58804
|
+
if (ISO_DATE_KEY_REGEX.test(dateLike)) return dateLike;
|
|
58805
|
+
const parsed = parseUsageDate(dateLike);
|
|
58806
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
58807
|
+
return dateLike.split("T")[0] || dateLike;
|
|
58808
|
+
}
|
|
58809
|
+
return formatLocalDateKey(parsed);
|
|
58810
|
+
}
|
|
58811
|
+
function getCurrentWeekToDateUsageRange(today = /* @__PURE__ */ new Date()) {
|
|
58812
|
+
const normalizedToday = new Date(today);
|
|
58813
|
+
normalizedToday.setHours(0, 0, 0, 0);
|
|
58814
|
+
const dayOfWeek = normalizedToday.getDay();
|
|
58815
|
+
const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
58816
|
+
const monday = new Date(normalizedToday);
|
|
58817
|
+
monday.setDate(normalizedToday.getDate() - daysSinceMonday);
|
|
58818
|
+
return {
|
|
58819
|
+
startDate: formatLocalDateKey(monday),
|
|
58820
|
+
endDate: formatLocalDateKey(normalizedToday),
|
|
58821
|
+
daysElapsed: daysSinceMonday + 1
|
|
58822
|
+
};
|
|
58823
|
+
}
|
|
58824
|
+
function calculateAverageDailyDuration(durationMs, dayCount) {
|
|
58825
|
+
if (!Number.isFinite(durationMs) || dayCount <= 0) {
|
|
58826
|
+
return 0;
|
|
58827
|
+
}
|
|
58828
|
+
return Math.round(durationMs / dayCount);
|
|
58829
|
+
}
|
|
58786
58830
|
var UserUsageDetailModal = ({
|
|
58787
58831
|
userId,
|
|
58788
58832
|
userName,
|
|
@@ -58792,7 +58836,7 @@ var UserUsageDetailModal = ({
|
|
|
58792
58836
|
endDate
|
|
58793
58837
|
}) => {
|
|
58794
58838
|
const [currentWeekStart, setCurrentWeekStart] = useState(() => {
|
|
58795
|
-
if (startDate) return
|
|
58839
|
+
if (startDate) return parseUsageDate(startDate);
|
|
58796
58840
|
const { start } = getCurrentWeekRange();
|
|
58797
58841
|
return start;
|
|
58798
58842
|
});
|
|
@@ -58802,8 +58846,8 @@ var UserUsageDetailModal = ({
|
|
|
58802
58846
|
return end;
|
|
58803
58847
|
}, [currentWeekStart]);
|
|
58804
58848
|
const { data, isLoading, error } = useUserUsage(userId, {
|
|
58805
|
-
startDate: currentWeekStart
|
|
58806
|
-
endDate: currentWeekEnd
|
|
58849
|
+
startDate: formatLocalDateKey(currentWeekStart),
|
|
58850
|
+
endDate: formatLocalDateKey(currentWeekEnd)
|
|
58807
58851
|
});
|
|
58808
58852
|
const handlePrevWeek = () => {
|
|
58809
58853
|
setCurrentWeekStart((prev) => {
|
|
@@ -58896,8 +58940,8 @@ function UsageContent({
|
|
|
58896
58940
|
} else {
|
|
58897
58941
|
daysToCount = 7;
|
|
58898
58942
|
}
|
|
58899
|
-
const
|
|
58900
|
-
const weekAvgMs =
|
|
58943
|
+
const weekActiveMs = weekData.reduce((sum, d) => sum + d.active_duration_ms, 0);
|
|
58944
|
+
const weekAvgMs = calculateAverageDailyDuration(weekActiveMs, daysToCount);
|
|
58901
58945
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
58902
58946
|
/* @__PURE__ */ jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 bg-gray-50 rounded-lg p-1 border border-gray-100", children: [
|
|
58903
58947
|
/* @__PURE__ */ jsx(
|
|
@@ -58909,7 +58953,7 @@ function UsageContent({
|
|
|
58909
58953
|
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-4 h-4" })
|
|
58910
58954
|
}
|
|
58911
58955
|
),
|
|
58912
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 min-w-[140px] text-center", children: formatDateRange(currentWeekStart
|
|
58956
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 min-w-[140px] text-center", children: formatDateRange(formatLocalDateKey(currentWeekStart), formatLocalDateKey(currentWeekEnd)) }),
|
|
58913
58957
|
/* @__PURE__ */ jsx(
|
|
58914
58958
|
"button",
|
|
58915
58959
|
{
|
|
@@ -58941,7 +58985,7 @@ function DailyBarChart({
|
|
|
58941
58985
|
const weekData = useMemo(() => buildWeekData(dailyData, getDateStringsInRange(weekStart, weekEnd)), [dailyData, weekStart, weekEnd]);
|
|
58942
58986
|
const chartData = useMemo(() => {
|
|
58943
58987
|
return weekData.map((day, index) => {
|
|
58944
|
-
const date =
|
|
58988
|
+
const date = parseUsageDate(day.date);
|
|
58945
58989
|
return {
|
|
58946
58990
|
...day,
|
|
58947
58991
|
index,
|
|
@@ -58951,7 +58995,7 @@ function DailyBarChart({
|
|
|
58951
58995
|
// green-500
|
|
58952
58996
|
passiveColor: "#fbbf24",
|
|
58953
58997
|
// amber-400
|
|
58954
|
-
isToday: day.date === (/* @__PURE__ */ new Date())
|
|
58998
|
+
isToday: day.date === formatLocalDateKey(/* @__PURE__ */ new Date())
|
|
58955
58999
|
};
|
|
58956
59000
|
});
|
|
58957
59001
|
}, [weekData]);
|
|
@@ -58971,7 +59015,7 @@ function DailyBarChart({
|
|
|
58971
59015
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
58972
59016
|
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-gray-300" }),
|
|
58973
59017
|
/* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-gray-600", children: [
|
|
58974
|
-
"Average Daily usage: ",
|
|
59018
|
+
"Average Daily active usage: ",
|
|
58975
59019
|
/* @__PURE__ */ jsx("span", { className: "text-gray-900 font-semibold", children: avgDailyMs > 0 ? formatDuration2(avgDailyMs) : "0m" })
|
|
58976
59020
|
] })
|
|
58977
59021
|
] }),
|
|
@@ -59136,17 +59180,14 @@ function getCurrentWeekRange() {
|
|
|
59136
59180
|
function getDateStringsInRange(start, end) {
|
|
59137
59181
|
const result = [];
|
|
59138
59182
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
|
59139
|
-
|
|
59140
|
-
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
59141
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
59142
|
-
result.push(`${year}-${month}-${day}`);
|
|
59183
|
+
result.push(formatLocalDateKey(d));
|
|
59143
59184
|
}
|
|
59144
59185
|
return result;
|
|
59145
59186
|
}
|
|
59146
59187
|
function buildWeekData(dailyData, weekDates) {
|
|
59147
59188
|
const dataMap = new Map(
|
|
59148
59189
|
(dailyData || []).map((d) => {
|
|
59149
|
-
const key =
|
|
59190
|
+
const key = normalizeUsageDateKey(d.date);
|
|
59150
59191
|
return [key, { ...d, date: key }];
|
|
59151
59192
|
})
|
|
59152
59193
|
);
|
|
@@ -59161,22 +59202,10 @@ function buildWeekData(dailyData, weekDates) {
|
|
|
59161
59202
|
};
|
|
59162
59203
|
});
|
|
59163
59204
|
}
|
|
59164
|
-
function normalizeDateKey(dateLike) {
|
|
59165
|
-
if (!dateLike) return void 0;
|
|
59166
|
-
try {
|
|
59167
|
-
const date = new Date(dateLike);
|
|
59168
|
-
const year = date.getFullYear();
|
|
59169
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
59170
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
59171
|
-
return `${year}-${month}-${day}`;
|
|
59172
|
-
} catch {
|
|
59173
|
-
return dateLike.split("T")[0] || dateLike;
|
|
59174
|
-
}
|
|
59175
|
-
}
|
|
59176
59205
|
function formatDateRange(startDate, endDate) {
|
|
59177
59206
|
try {
|
|
59178
|
-
const start =
|
|
59179
|
-
const end =
|
|
59207
|
+
const start = parseUsageDate(startDate);
|
|
59208
|
+
const end = parseUsageDate(endDate);
|
|
59180
59209
|
const opts = { month: "short", day: "numeric" };
|
|
59181
59210
|
const yearOpts = { year: "numeric" };
|
|
59182
59211
|
return `${start.toLocaleDateString("en-US", opts)} - ${end.toLocaleDateString("en-US", opts)}, ${end.toLocaleDateString("en-US", yearOpts)}`;
|
|
@@ -59187,7 +59216,7 @@ function formatDateRange(startDate, endDate) {
|
|
|
59187
59216
|
function formatDate(dateStr) {
|
|
59188
59217
|
if (!dateStr) return "";
|
|
59189
59218
|
try {
|
|
59190
|
-
const date =
|
|
59219
|
+
const date = parseUsageDate(dateStr);
|
|
59191
59220
|
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
59192
59221
|
} catch {
|
|
59193
59222
|
return dateStr;
|
|
@@ -59465,7 +59494,7 @@ var UserManagementTable = ({
|
|
|
59465
59494
|
}
|
|
59466
59495
|
),
|
|
59467
59496
|
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
|
|
59468
|
-
showUsageStats && /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily
|
|
59497
|
+
showUsageStats && /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily Active Usage" }),
|
|
59469
59498
|
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
|
|
59470
59499
|
] }) }),
|
|
59471
59500
|
/* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: filteredAndSortedUsers.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: showUsageStats ? 5 : 4, className: "px-6 py-12", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center text-gray-400 gap-2", children: [
|
|
@@ -73762,39 +73791,8 @@ var TeamManagementView = ({
|
|
|
73762
73791
|
const [users, setUsers] = useState([]);
|
|
73763
73792
|
const [availableLines, setAvailableLines] = useState([]);
|
|
73764
73793
|
const [availableFactories, setAvailableFactories] = useState([]);
|
|
73765
|
-
const [
|
|
73766
|
-
totalUsers: 0,
|
|
73767
|
-
owners: 0,
|
|
73768
|
-
it: 0,
|
|
73769
|
-
plantHeads: 0,
|
|
73770
|
-
industrialEngineers: 0,
|
|
73771
|
-
supervisors: 0,
|
|
73772
|
-
optifye: 0
|
|
73773
|
-
});
|
|
73794
|
+
const [usageSummaryByUser, setUsageSummaryByUser] = useState({});
|
|
73774
73795
|
const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
|
|
73775
|
-
const normalizeIds = useCallback((value) => {
|
|
73776
|
-
if (Array.isArray(value)) {
|
|
73777
|
-
return value.filter((id3) => typeof id3 === "string" && id3.length > 0);
|
|
73778
|
-
}
|
|
73779
|
-
if (typeof value === "string" && value.length > 0) {
|
|
73780
|
-
return [value];
|
|
73781
|
-
}
|
|
73782
|
-
return [];
|
|
73783
|
-
}, []);
|
|
73784
|
-
const factoryScopedRoleFactoryIds = useMemo(() => {
|
|
73785
|
-
if (!isFactoryScopedRole(user?.role_level)) return [];
|
|
73786
|
-
const scopedFactoryIds = normalizeIds(user?.access_scope?.factory_ids);
|
|
73787
|
-
if (scopedFactoryIds.length > 0) {
|
|
73788
|
-
return Array.from(new Set(scopedFactoryIds));
|
|
73789
|
-
}
|
|
73790
|
-
const propertyFactoryIds = normalizeIds(
|
|
73791
|
-
user?.properties?.factory_ids ?? user?.properties?.factory_id
|
|
73792
|
-
);
|
|
73793
|
-
if (propertyFactoryIds.length > 0) {
|
|
73794
|
-
return Array.from(new Set(propertyFactoryIds));
|
|
73795
|
-
}
|
|
73796
|
-
return entityConfig?.factoryId ? [entityConfig.factoryId] : [];
|
|
73797
|
-
}, [user, entityConfig?.factoryId, normalizeIds]);
|
|
73798
73796
|
const notifyScopeRefresh = useCallback(() => {
|
|
73799
73797
|
if (typeof window !== "undefined") {
|
|
73800
73798
|
window.dispatchEvent(new Event("rbac:refresh-scope"));
|
|
@@ -73803,37 +73801,17 @@ var TeamManagementView = ({
|
|
|
73803
73801
|
const canAddUsers = canRoleManageUsers(user?.role_level);
|
|
73804
73802
|
const canViewUsageStats = canRoleViewUsageStats(user?.role_level);
|
|
73805
73803
|
const pageCompanyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
73806
|
-
const
|
|
73807
|
-
const usageDateRange = useMemo(() => {
|
|
73808
|
-
const today = /* @__PURE__ */ new Date();
|
|
73809
|
-
const dayOfWeek = today.getDay();
|
|
73810
|
-
const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
73811
|
-
const monday = new Date(today);
|
|
73812
|
-
monday.setDate(today.getDate() - daysSinceMonday);
|
|
73813
|
-
return {
|
|
73814
|
-
startDate: monday.toISOString().slice(0, 10),
|
|
73815
|
-
endDate: today.toISOString().slice(0, 10),
|
|
73816
|
-
daysElapsed: daysSinceMonday + 1
|
|
73817
|
-
// Include today
|
|
73818
|
-
};
|
|
73819
|
-
}, []);
|
|
73820
|
-
const {
|
|
73821
|
-
data: usageData,
|
|
73822
|
-
isLoading: isUsageLoading
|
|
73823
|
-
} = useCompanyUsersUsage(canViewUsageStats ? companyIdForUsage : void 0, {
|
|
73824
|
-
startDate: usageDateRange.startDate,
|
|
73825
|
-
endDate: usageDateRange.endDate
|
|
73826
|
-
});
|
|
73804
|
+
const usageDateRange = useMemo(() => getCurrentWeekToDateUsageRange(), []);
|
|
73827
73805
|
const avgDailyUsageMap = useMemo(() => {
|
|
73828
|
-
if (!
|
|
73806
|
+
if (!canViewUsageStats) {
|
|
73829
73807
|
return {};
|
|
73830
73808
|
}
|
|
73831
73809
|
const daysElapsed = usageDateRange.daysElapsed;
|
|
73832
|
-
return
|
|
73833
|
-
acc[
|
|
73810
|
+
return Object.entries(usageSummaryByUser).reduce((acc, [userId, usage]) => {
|
|
73811
|
+
acc[userId] = calculateAverageDailyDuration(usage.active_duration_ms, daysElapsed);
|
|
73834
73812
|
return acc;
|
|
73835
73813
|
}, {});
|
|
73836
|
-
}, [
|
|
73814
|
+
}, [canViewUsageStats, usageSummaryByUser, usageDateRange.daysElapsed]);
|
|
73837
73815
|
const pageTitle = "Team Management";
|
|
73838
73816
|
const pageDescription = canViewUsageStats ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
|
|
73839
73817
|
const hasAccess = user ? user.role_level === void 0 || canRoleAccessTeamManagement(user.role_level) : false;
|
|
@@ -73857,7 +73835,6 @@ var TeamManagementView = ({
|
|
|
73857
73835
|
setIsLoading(true);
|
|
73858
73836
|
setError(void 0);
|
|
73859
73837
|
try {
|
|
73860
|
-
const userManagementService = createUserManagementService(supabase);
|
|
73861
73838
|
const { data: { session } } = await supabase.auth.getSession();
|
|
73862
73839
|
if (!session?.access_token) {
|
|
73863
73840
|
throw new Error("No authentication token available");
|
|
@@ -73867,116 +73844,31 @@ var TeamManagementView = ({
|
|
|
73867
73844
|
if (!backendUrl) {
|
|
73868
73845
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
73869
73846
|
}
|
|
73870
|
-
|
|
73871
|
-
|
|
73872
|
-
|
|
73847
|
+
const params = new URLSearchParams({
|
|
73848
|
+
start_date: usageDateRange.startDate,
|
|
73849
|
+
end_date: usageDateRange.endDate
|
|
73850
|
+
});
|
|
73851
|
+
const bootstrapResponse = await fetch(
|
|
73852
|
+
`${backendUrl}/api/users/company/${companyId}/bootstrap?${params.toString()}`,
|
|
73853
|
+
{
|
|
73873
73854
|
headers: {
|
|
73874
73855
|
"Authorization": `Bearer ${token}`,
|
|
73875
73856
|
"Content-Type": "application/json"
|
|
73876
73857
|
}
|
|
73877
|
-
});
|
|
73878
|
-
if (!linesResponse.ok) {
|
|
73879
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
73880
|
-
}
|
|
73881
|
-
const linesData = await linesResponse.json();
|
|
73882
|
-
const lines = linesData.lines || [];
|
|
73883
|
-
setAvailableFactories(factories || []);
|
|
73884
|
-
setAvailableLines(lines);
|
|
73885
|
-
console.log("[TeamManagementView] Company-scoped team view - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
|
|
73886
|
-
} else if (isFactoryScopedRole(user?.role_level)) {
|
|
73887
|
-
if (factoryScopedRoleFactoryIds.length > 0) {
|
|
73888
|
-
if (companyId) {
|
|
73889
|
-
const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
|
|
73890
|
-
headers: {
|
|
73891
|
-
"Authorization": `Bearer ${token}`,
|
|
73892
|
-
"Content-Type": "application/json"
|
|
73893
|
-
}
|
|
73894
|
-
});
|
|
73895
|
-
if (!linesResponse.ok) {
|
|
73896
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
73897
|
-
}
|
|
73898
|
-
const linesData = await linesResponse.json();
|
|
73899
|
-
const allLines = linesData.lines || [];
|
|
73900
|
-
const lines = allLines.filter((line) => factoryScopedRoleFactoryIds.includes(line.factory_id));
|
|
73901
|
-
setAvailableLines(lines);
|
|
73902
|
-
console.log("[TeamManagementView] Factory-scoped team view - Factories:", factoryScopedRoleFactoryIds, "Loaded lines:", lines?.length);
|
|
73903
|
-
}
|
|
73904
|
-
} else if (entityConfig?.factoryId) {
|
|
73905
|
-
const linesResponse = await fetch(`${backendUrl}/api/lines?factory_id=${entityConfig.factoryId}`, {
|
|
73906
|
-
headers: {
|
|
73907
|
-
"Authorization": `Bearer ${token}`,
|
|
73908
|
-
"Content-Type": "application/json"
|
|
73909
|
-
}
|
|
73910
|
-
});
|
|
73911
|
-
if (!linesResponse.ok) {
|
|
73912
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
73913
|
-
}
|
|
73914
|
-
const linesData = await linesResponse.json();
|
|
73915
|
-
const lines = linesData.lines || [];
|
|
73916
|
-
setAvailableLines(lines);
|
|
73917
|
-
console.log("[TeamManagementView] Plant Head - Using entityConfig factory:", entityConfig.factoryId, "Loaded lines:", lines?.length);
|
|
73918
|
-
} else {
|
|
73919
|
-
setAvailableLines([]);
|
|
73920
|
-
console.warn("[TeamManagementView] Plant Head has no factory assignments");
|
|
73921
73858
|
}
|
|
73922
|
-
|
|
73923
|
-
|
|
73924
|
-
const
|
|
73925
|
-
|
|
73926
|
-
|
|
73927
|
-
|
|
73928
|
-
|
|
73929
|
-
|
|
73930
|
-
|
|
73931
|
-
|
|
73932
|
-
|
|
73933
|
-
|
|
73934
|
-
|
|
73935
|
-
setAvailableLines(lines);
|
|
73936
|
-
setAvailableFactories([]);
|
|
73937
|
-
console.log("[TeamManagementView] Fallback - Company:", companyId, "Loaded lines:", lines?.length);
|
|
73938
|
-
}
|
|
73939
|
-
const usersPromise = isFactoryScopedRole(user?.role_level) ? (async () => {
|
|
73940
|
-
if (factoryScopedRoleFactoryIds.length === 0) {
|
|
73941
|
-
return [];
|
|
73942
|
-
}
|
|
73943
|
-
const results = await Promise.allSettled(
|
|
73944
|
-
factoryScopedRoleFactoryIds.map((factoryId) => userManagementService.getFactoryUsers(factoryId))
|
|
73945
|
-
);
|
|
73946
|
-
const successful = results.filter(
|
|
73947
|
-
(result) => result.status === "fulfilled"
|
|
73948
|
-
).flatMap((result) => result.value || []);
|
|
73949
|
-
if (successful.length > 0) {
|
|
73950
|
-
const byUserId = /* @__PURE__ */ new Map();
|
|
73951
|
-
successful.forEach((u) => {
|
|
73952
|
-
if (u?.user_id) {
|
|
73953
|
-
byUserId.set(u.user_id, u);
|
|
73954
|
-
}
|
|
73955
|
-
});
|
|
73956
|
-
return Array.from(byUserId.values());
|
|
73957
|
-
}
|
|
73958
|
-
const firstRejected = results.find(
|
|
73959
|
-
(result) => result.status === "rejected"
|
|
73960
|
-
);
|
|
73961
|
-
if (firstRejected) {
|
|
73962
|
-
throw firstRejected.reason;
|
|
73963
|
-
}
|
|
73964
|
-
return [];
|
|
73965
|
-
})() : userManagementService.getCompanyUsers(companyId);
|
|
73966
|
-
const [usersData, userStats] = await Promise.all([
|
|
73967
|
-
usersPromise,
|
|
73968
|
-
userManagementService.getUserStats(companyId)
|
|
73969
|
-
]);
|
|
73970
|
-
setUsers(usersData);
|
|
73971
|
-
setStats({
|
|
73972
|
-
totalUsers: userStats.total,
|
|
73973
|
-
owners: userStats.owners,
|
|
73974
|
-
it: userStats.it,
|
|
73975
|
-
plantHeads: userStats.plant_heads,
|
|
73976
|
-
industrialEngineers: userStats.industrial_engineers,
|
|
73977
|
-
supervisors: userStats.supervisors,
|
|
73978
|
-
optifye: 0
|
|
73979
|
-
});
|
|
73859
|
+
);
|
|
73860
|
+
if (!bootstrapResponse.ok) {
|
|
73861
|
+
const errorData = await bootstrapResponse.json().catch(() => ({}));
|
|
73862
|
+
throw new Error(errorData.detail || `Failed to fetch team bootstrap: ${bootstrapResponse.statusText}`);
|
|
73863
|
+
}
|
|
73864
|
+
const bootstrapData = await bootstrapResponse.json();
|
|
73865
|
+
setAvailableFactories((bootstrapData.factories || []).map((factory) => ({
|
|
73866
|
+
id: factory.id,
|
|
73867
|
+
factory_name: factory.factory_name
|
|
73868
|
+
})));
|
|
73869
|
+
setAvailableLines(bootstrapData.lines || []);
|
|
73870
|
+
setUsers(bootstrapData.users || []);
|
|
73871
|
+
setUsageSummaryByUser(bootstrapData.usage_summary_by_user || {});
|
|
73980
73872
|
} catch (err) {
|
|
73981
73873
|
console.error("Error loading team management data:", err);
|
|
73982
73874
|
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
@@ -73984,7 +73876,7 @@ var TeamManagementView = ({
|
|
|
73984
73876
|
} finally {
|
|
73985
73877
|
setIsLoading(false);
|
|
73986
73878
|
}
|
|
73987
|
-
}, [supabase, user, pageCompanyId,
|
|
73879
|
+
}, [supabase, user, pageCompanyId, usageDateRange.endDate, usageDateRange.startDate]);
|
|
73988
73880
|
useEffect(() => {
|
|
73989
73881
|
const companyId = pageCompanyId;
|
|
73990
73882
|
const canLoad = hasAccess && user && !!companyId;
|
|
@@ -74165,7 +74057,7 @@ var TeamManagementView = ({
|
|
|
74165
74057
|
availableLines,
|
|
74166
74058
|
availableFactories,
|
|
74167
74059
|
avgDailyUsage: avgDailyUsageMap,
|
|
74168
|
-
isUsageLoading,
|
|
74060
|
+
isUsageLoading: false,
|
|
74169
74061
|
showUsageStats: canViewUsageStats
|
|
74170
74062
|
}
|
|
74171
74063
|
) }),
|
|
@@ -78988,6 +78880,12 @@ var buildDeltaBadge = (delta, options) => {
|
|
|
78988
78880
|
text: `${options.formatter(delta)} vs ${options.comparisonLabel}`
|
|
78989
78881
|
};
|
|
78990
78882
|
};
|
|
78883
|
+
var parseSpecificShiftId = (shiftMode) => {
|
|
78884
|
+
const rawMode = String(shiftMode || "").trim().toLowerCase();
|
|
78885
|
+
if (!rawMode.startsWith("shift:")) return null;
|
|
78886
|
+
const parsed = Number(rawMode.split(":", 2)[1]);
|
|
78887
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
78888
|
+
};
|
|
78991
78889
|
var normalizeShiftLabel = (shiftName, shiftMode) => {
|
|
78992
78890
|
if (shiftMode === "all") {
|
|
78993
78891
|
return "All Shifts";
|
|
@@ -78999,6 +78897,10 @@ var normalizeShiftLabel = (shiftName, shiftMode) => {
|
|
|
78999
78897
|
if (normalizedName === "night") return "Night Shift";
|
|
79000
78898
|
return /shift/i.test(trimmedName) ? trimmedName : `${trimmedName} Shift`;
|
|
79001
78899
|
}
|
|
78900
|
+
const specificShiftId = parseSpecificShiftId(shiftMode);
|
|
78901
|
+
if (specificShiftId !== null) {
|
|
78902
|
+
return `Shift ${specificShiftId}`;
|
|
78903
|
+
}
|
|
79002
78904
|
if (shiftMode === "night") return "Night Shift";
|
|
79003
78905
|
return "Day Shift";
|
|
79004
78906
|
};
|
|
@@ -79071,6 +78973,7 @@ var OperationsOverviewHeader = React143__default.memo(({
|
|
|
79071
78973
|
dateRange,
|
|
79072
78974
|
displayDateRange,
|
|
79073
78975
|
trendMode,
|
|
78976
|
+
shiftFilterOptions,
|
|
79074
78977
|
isLiveScope,
|
|
79075
78978
|
liveShiftName,
|
|
79076
78979
|
lineOptions,
|
|
@@ -79324,7 +79227,7 @@ var OperationsOverviewHeader = React143__default.memo(({
|
|
|
79324
79227
|
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
79325
79228
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
79326
79229
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Shift" }),
|
|
79327
|
-
/* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */
|
|
79230
|
+
/* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
|
|
79328
79231
|
"select",
|
|
79329
79232
|
{
|
|
79330
79233
|
value: trendMode,
|
|
@@ -79336,11 +79239,7 @@ var OperationsOverviewHeader = React143__default.memo(({
|
|
|
79336
79239
|
backgroundRepeat: "no-repeat",
|
|
79337
79240
|
backgroundSize: "1.2em 1.2em"
|
|
79338
79241
|
},
|
|
79339
|
-
children:
|
|
79340
|
-
/* @__PURE__ */ jsx("option", { value: "all", children: "All Shifts" }),
|
|
79341
|
-
/* @__PURE__ */ jsx("option", { value: "day", children: "Day Shift" }),
|
|
79342
|
-
/* @__PURE__ */ jsx("option", { value: "night", children: "Night Shift" })
|
|
79343
|
-
]
|
|
79242
|
+
children: shiftFilterOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
|
|
79344
79243
|
}
|
|
79345
79244
|
) })
|
|
79346
79245
|
] }),
|
|
@@ -80410,6 +80309,24 @@ var normalizeShiftId = (value) => {
|
|
|
80410
80309
|
}
|
|
80411
80310
|
return null;
|
|
80412
80311
|
};
|
|
80312
|
+
var buildSpecificShiftMode = (value) => {
|
|
80313
|
+
const normalizedShiftId = normalizeShiftId(value);
|
|
80314
|
+
return normalizedShiftId === null ? null : `shift:${normalizedShiftId}`;
|
|
80315
|
+
};
|
|
80316
|
+
var parseSpecificShiftMode = (value) => {
|
|
80317
|
+
const rawValue = String(value || "").trim().toLowerCase();
|
|
80318
|
+
if (!rawValue.startsWith("shift:")) return null;
|
|
80319
|
+
const parsed = Number(rawValue.split(":", 2)[1]);
|
|
80320
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
80321
|
+
};
|
|
80322
|
+
var formatShiftOptionLabel = (shiftName, shiftId) => {
|
|
80323
|
+
const trimmedName = shiftName?.trim();
|
|
80324
|
+
if (trimmedName) {
|
|
80325
|
+
return /shift/i.test(trimmedName) ? trimmedName : `${trimmedName} Shift`;
|
|
80326
|
+
}
|
|
80327
|
+
const normalizedShiftId = normalizeShiftId(shiftId);
|
|
80328
|
+
return normalizedShiftId === null ? "Unknown Shift" : `Shift ${normalizedShiftId}`;
|
|
80329
|
+
};
|
|
80413
80330
|
var classifyShiftBucket = ({
|
|
80414
80331
|
shiftName,
|
|
80415
80332
|
shiftId,
|
|
@@ -80496,6 +80413,26 @@ var normalizeShiftWindowMinutes = (startTime, endTime) => {
|
|
|
80496
80413
|
}
|
|
80497
80414
|
return { startMinutes, endMinutes };
|
|
80498
80415
|
};
|
|
80416
|
+
var getOperationalSortStartMinutes = (startTime, endTime) => {
|
|
80417
|
+
const normalizedWindow = normalizeShiftWindowMinutes(startTime, endTime);
|
|
80418
|
+
if (!normalizedWindow) return null;
|
|
80419
|
+
return normalizedWindow.startMinutes < 6 * 60 ? normalizedWindow.startMinutes + 24 * 60 : normalizedWindow.startMinutes;
|
|
80420
|
+
};
|
|
80421
|
+
var doesShiftMatchTrendMode = ({
|
|
80422
|
+
trendMode,
|
|
80423
|
+
shiftName,
|
|
80424
|
+
shiftId,
|
|
80425
|
+
startTime,
|
|
80426
|
+
endTime
|
|
80427
|
+
}) => {
|
|
80428
|
+
if (trendMode === "all") return true;
|
|
80429
|
+
const specificShiftId = parseSpecificShiftMode(trendMode);
|
|
80430
|
+
if (specificShiftId !== null) {
|
|
80431
|
+
return normalizeShiftId(shiftId) === specificShiftId;
|
|
80432
|
+
}
|
|
80433
|
+
const bucket = classifyShiftBucket({ shiftName, shiftId, startTime, endTime });
|
|
80434
|
+
return bucket === trendMode;
|
|
80435
|
+
};
|
|
80499
80436
|
var PlantHeadView = () => {
|
|
80500
80437
|
const supabase = useSupabase();
|
|
80501
80438
|
const entityConfig = useEntityConfig();
|
|
@@ -80626,6 +80563,45 @@ var PlantHeadView = () => {
|
|
|
80626
80563
|
shiftConfigMap,
|
|
80627
80564
|
isLoading: isShiftConfigLoading
|
|
80628
80565
|
} = useMultiLineShiftConfigs(scopedLineIds, staticShiftConfig);
|
|
80566
|
+
const shiftFilterOptions = React143__default.useMemo(() => {
|
|
80567
|
+
const optionsById = /* @__PURE__ */ new Map();
|
|
80568
|
+
scopedLineIds.forEach((lineId) => {
|
|
80569
|
+
const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
|
|
80570
|
+
getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
|
|
80571
|
+
const shiftId = normalizeShiftId(shift.shiftId);
|
|
80572
|
+
if (shiftId === null) return;
|
|
80573
|
+
const sortKey = getOperationalSortStartMinutes(shift.startTime, shift.endTime) ?? 24 * 60 + shiftId;
|
|
80574
|
+
const existing = optionsById.get(shiftId);
|
|
80575
|
+
const label = formatShiftOptionLabel(shift.shiftName, shiftId);
|
|
80576
|
+
if (!existing) {
|
|
80577
|
+
optionsById.set(shiftId, {
|
|
80578
|
+
value: buildSpecificShiftMode(shiftId),
|
|
80579
|
+
label,
|
|
80580
|
+
shiftId,
|
|
80581
|
+
shiftName: shift.shiftName || null,
|
|
80582
|
+
startTime: shift.startTime || null,
|
|
80583
|
+
endTime: shift.endTime || null,
|
|
80584
|
+
sortKey
|
|
80585
|
+
});
|
|
80586
|
+
return;
|
|
80587
|
+
}
|
|
80588
|
+
if ((!existing.shiftName || existing.shiftName === `Shift ${shiftId}`) && shift.shiftName) {
|
|
80589
|
+
existing.shiftName = shift.shiftName;
|
|
80590
|
+
existing.label = label;
|
|
80591
|
+
}
|
|
80592
|
+
if (sortKey < existing.sortKey) {
|
|
80593
|
+
existing.sortKey = sortKey;
|
|
80594
|
+
existing.startTime = shift.startTime || null;
|
|
80595
|
+
existing.endTime = shift.endTime || null;
|
|
80596
|
+
}
|
|
80597
|
+
});
|
|
80598
|
+
});
|
|
80599
|
+
const dynamicOptions = Array.from(optionsById.values()).sort((a, b) => a.sortKey - b.sortKey || (a.shiftId || 0) - (b.shiftId || 0)).map(({ sortKey, ...option }) => option);
|
|
80600
|
+
return [
|
|
80601
|
+
{ value: "all", label: "All Shifts" },
|
|
80602
|
+
...dynamicOptions
|
|
80603
|
+
];
|
|
80604
|
+
}, [appTimezone, scopedLineIds, shiftConfigMap, staticShiftConfig]);
|
|
80629
80605
|
React143__default.useEffect(() => {
|
|
80630
80606
|
if (scopedLineIds.length === 0 || isShiftConfigLoading) {
|
|
80631
80607
|
return;
|
|
@@ -80688,18 +80664,20 @@ var PlantHeadView = () => {
|
|
|
80688
80664
|
if (!activeShift) {
|
|
80689
80665
|
return [];
|
|
80690
80666
|
}
|
|
80691
|
-
const
|
|
80667
|
+
const bucketTrendMode = classifyShiftBucket({
|
|
80692
80668
|
shiftName: activeShift.shiftName,
|
|
80693
80669
|
shiftId: activeShift.shiftId,
|
|
80694
80670
|
startTime: activeShift.startTime,
|
|
80695
80671
|
endTime: activeShift.endTime
|
|
80696
80672
|
});
|
|
80697
|
-
|
|
80673
|
+
const exactTrendMode = buildSpecificShiftMode(activeShift.shiftId);
|
|
80674
|
+
if (!bucketTrendMode && !exactTrendMode) {
|
|
80698
80675
|
return [];
|
|
80699
80676
|
}
|
|
80700
80677
|
return [{
|
|
80701
80678
|
lineId,
|
|
80702
|
-
|
|
80679
|
+
exactTrendMode,
|
|
80680
|
+
bucketTrendMode,
|
|
80703
80681
|
shiftId: activeShift.shiftId,
|
|
80704
80682
|
shiftName: activeShift.shiftName || null,
|
|
80705
80683
|
startTime: activeShift.startTime || null,
|
|
@@ -80708,14 +80686,12 @@ var PlantHeadView = () => {
|
|
|
80708
80686
|
}];
|
|
80709
80687
|
});
|
|
80710
80688
|
}, [appTimezone, scopedLineIds, shiftConfigMap, shiftResolutionNow, staticShiftConfig]);
|
|
80711
|
-
const
|
|
80712
|
-
|
|
80713
|
-
|
|
80714
|
-
|
|
80715
|
-
|
|
80716
|
-
|
|
80717
|
-
[activeLineShiftStates, resolvedOperationalToday]
|
|
80718
|
-
);
|
|
80689
|
+
const uniformActiveTrendMode = React143__default.useMemo(() => {
|
|
80690
|
+
const activeModes = Array.from(new Set(
|
|
80691
|
+
activeLineShiftStates.filter((shift) => shift.date === resolvedOperationalToday).map((shift) => shift.exactTrendMode).filter((mode) => !!mode)
|
|
80692
|
+
));
|
|
80693
|
+
return activeModes.length === 1 ? activeModes[0] : null;
|
|
80694
|
+
}, [activeLineShiftStates, resolvedOperationalToday]);
|
|
80719
80695
|
const resolvedTrendMode = isInitialScopeReady ? trendMode : "all";
|
|
80720
80696
|
const hourlyWindowStartTime = React143__default.useMemo(() => {
|
|
80721
80697
|
if (scopedLineIds.length === 0) {
|
|
@@ -80726,13 +80702,19 @@ var PlantHeadView = () => {
|
|
|
80726
80702
|
scopedLineIds.forEach((lineId) => {
|
|
80727
80703
|
const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
|
|
80728
80704
|
getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
|
|
80729
|
-
|
|
80705
|
+
classifyShiftBucket({
|
|
80730
80706
|
shiftName: shift.shiftName,
|
|
80731
80707
|
shiftId: shift.shiftId,
|
|
80732
80708
|
startTime: shift.startTime,
|
|
80733
80709
|
endTime: shift.endTime
|
|
80734
80710
|
});
|
|
80735
|
-
if (
|
|
80711
|
+
if (!doesShiftMatchTrendMode({
|
|
80712
|
+
trendMode: resolvedTrendMode,
|
|
80713
|
+
shiftName: shift.shiftName,
|
|
80714
|
+
shiftId: shift.shiftId,
|
|
80715
|
+
startTime: shift.startTime,
|
|
80716
|
+
endTime: shift.endTime
|
|
80717
|
+
})) {
|
|
80736
80718
|
return;
|
|
80737
80719
|
}
|
|
80738
80720
|
const normalizedWindow = normalizeShiftWindowMinutes(shift.startTime, shift.endTime);
|
|
@@ -80806,11 +80788,11 @@ var PlantHeadView = () => {
|
|
|
80806
80788
|
endKey: nextStartKey
|
|
80807
80789
|
};
|
|
80808
80790
|
});
|
|
80809
|
-
setTrendMode("all");
|
|
80791
|
+
setTrendMode(uniformActiveTrendMode || "all");
|
|
80810
80792
|
setUsesThisWeekComparison(false);
|
|
80811
80793
|
hasAutoInitializedScopeRef.current = true;
|
|
80812
80794
|
setIsInitialScopeReady(true);
|
|
80813
|
-
}, [fallbackOperationalDate, isShiftScopeResolved, resolvedOperationalToday, scopedLineIds.length]);
|
|
80795
|
+
}, [fallbackOperationalDate, isShiftScopeResolved, resolvedOperationalToday, scopedLineIds.length, uniformActiveTrendMode]);
|
|
80814
80796
|
const handleDateRangeChange = React143__default.useCallback((range, meta) => {
|
|
80815
80797
|
hasUserAdjustedScopeRef.current = true;
|
|
80816
80798
|
setIsInitialScopeReady(true);
|
|
@@ -80897,6 +80879,33 @@ var PlantHeadView = () => {
|
|
|
80897
80879
|
() => resolvedTrendMode,
|
|
80898
80880
|
[resolvedTrendMode]
|
|
80899
80881
|
);
|
|
80882
|
+
const hasActiveSelectedShiftLine = React143__default.useMemo(
|
|
80883
|
+
() => activeLineShiftStates.some((shift) => {
|
|
80884
|
+
if (shift.date !== resolvedOperationalToday) return false;
|
|
80885
|
+
if (effectiveTrendMode === "all") return true;
|
|
80886
|
+
const specificShiftId = parseSpecificShiftMode(effectiveTrendMode);
|
|
80887
|
+
if (specificShiftId !== null) {
|
|
80888
|
+
return shift.exactTrendMode === effectiveTrendMode;
|
|
80889
|
+
}
|
|
80890
|
+
return shift.bucketTrendMode === effectiveTrendMode;
|
|
80891
|
+
}),
|
|
80892
|
+
[activeLineShiftStates, effectiveTrendMode, resolvedOperationalToday]
|
|
80893
|
+
);
|
|
80894
|
+
const activeLiveShiftName = React143__default.useMemo(
|
|
80895
|
+
() => {
|
|
80896
|
+
if (effectiveTrendMode === "all") return null;
|
|
80897
|
+
const matchingShift = activeLineShiftStates.find((shift) => {
|
|
80898
|
+
if (shift.date !== resolvedOperationalToday) return false;
|
|
80899
|
+
const specificShiftId = parseSpecificShiftMode(effectiveTrendMode);
|
|
80900
|
+
if (specificShiftId !== null) {
|
|
80901
|
+
return shift.exactTrendMode === effectiveTrendMode;
|
|
80902
|
+
}
|
|
80903
|
+
return shift.bucketTrendMode === effectiveTrendMode;
|
|
80904
|
+
});
|
|
80905
|
+
return matchingShift?.shiftName || null;
|
|
80906
|
+
},
|
|
80907
|
+
[activeLineShiftStates, effectiveTrendMode, resolvedOperationalToday]
|
|
80908
|
+
);
|
|
80900
80909
|
const hourlyLabelStartTime = React143__default.useMemo(() => {
|
|
80901
80910
|
if (scopedLineIds.length === 0) {
|
|
80902
80911
|
return null;
|
|
@@ -80908,12 +80917,11 @@ var PlantHeadView = () => {
|
|
|
80908
80917
|
[effectiveDateRange.endKey, effectiveDateRange.startKey]
|
|
80909
80918
|
);
|
|
80910
80919
|
const isLiveScope = React143__default.useMemo(
|
|
80911
|
-
() => isSingleDayScope && effectiveDateRange.startKey === resolvedOperationalToday &&
|
|
80920
|
+
() => isSingleDayScope && effectiveDateRange.startKey === resolvedOperationalToday && hasActiveSelectedShiftLine,
|
|
80912
80921
|
[
|
|
80913
80922
|
effectiveDateRange.startKey,
|
|
80914
80923
|
effectiveTrendMode,
|
|
80915
|
-
|
|
80916
|
-
hasActiveNightShiftLine,
|
|
80924
|
+
hasActiveSelectedShiftLine,
|
|
80917
80925
|
isSingleDayScope,
|
|
80918
80926
|
resolvedOperationalToday
|
|
80919
80927
|
]
|
|
@@ -80950,8 +80958,9 @@ var PlantHeadView = () => {
|
|
|
80950
80958
|
dateRange,
|
|
80951
80959
|
displayDateRange: headerDateRange,
|
|
80952
80960
|
trendMode,
|
|
80961
|
+
shiftFilterOptions,
|
|
80953
80962
|
isLiveScope,
|
|
80954
|
-
liveShiftName: isLiveScope && trendMode !== "all" ?
|
|
80963
|
+
liveShiftName: isLiveScope && trendMode !== "all" ? activeLiveShiftName : null,
|
|
80955
80964
|
lineOptions,
|
|
80956
80965
|
supervisorOptions,
|
|
80957
80966
|
selectedSupervisorId,
|