@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.js
CHANGED
|
@@ -58812,6 +58812,50 @@ var UserUsageStats = ({
|
|
|
58812
58812
|
] })
|
|
58813
58813
|
] });
|
|
58814
58814
|
};
|
|
58815
|
+
|
|
58816
|
+
// src/lib/utils/teamUsage.ts
|
|
58817
|
+
var ISO_DATE_KEY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
58818
|
+
function formatLocalDateKey(date) {
|
|
58819
|
+
const year = date.getFullYear();
|
|
58820
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
58821
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
58822
|
+
return `${year}-${month}-${day}`;
|
|
58823
|
+
}
|
|
58824
|
+
function parseUsageDate(dateLike) {
|
|
58825
|
+
if (ISO_DATE_KEY_REGEX.test(dateLike)) {
|
|
58826
|
+
const [year, month, day] = dateLike.split("-").map(Number);
|
|
58827
|
+
return new Date(year, month - 1, day);
|
|
58828
|
+
}
|
|
58829
|
+
return new Date(dateLike);
|
|
58830
|
+
}
|
|
58831
|
+
function normalizeUsageDateKey(dateLike) {
|
|
58832
|
+
if (!dateLike) return void 0;
|
|
58833
|
+
if (ISO_DATE_KEY_REGEX.test(dateLike)) return dateLike;
|
|
58834
|
+
const parsed = parseUsageDate(dateLike);
|
|
58835
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
58836
|
+
return dateLike.split("T")[0] || dateLike;
|
|
58837
|
+
}
|
|
58838
|
+
return formatLocalDateKey(parsed);
|
|
58839
|
+
}
|
|
58840
|
+
function getCurrentWeekToDateUsageRange(today = /* @__PURE__ */ new Date()) {
|
|
58841
|
+
const normalizedToday = new Date(today);
|
|
58842
|
+
normalizedToday.setHours(0, 0, 0, 0);
|
|
58843
|
+
const dayOfWeek = normalizedToday.getDay();
|
|
58844
|
+
const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
58845
|
+
const monday = new Date(normalizedToday);
|
|
58846
|
+
monday.setDate(normalizedToday.getDate() - daysSinceMonday);
|
|
58847
|
+
return {
|
|
58848
|
+
startDate: formatLocalDateKey(monday),
|
|
58849
|
+
endDate: formatLocalDateKey(normalizedToday),
|
|
58850
|
+
daysElapsed: daysSinceMonday + 1
|
|
58851
|
+
};
|
|
58852
|
+
}
|
|
58853
|
+
function calculateAverageDailyDuration(durationMs, dayCount) {
|
|
58854
|
+
if (!Number.isFinite(durationMs) || dayCount <= 0) {
|
|
58855
|
+
return 0;
|
|
58856
|
+
}
|
|
58857
|
+
return Math.round(durationMs / dayCount);
|
|
58858
|
+
}
|
|
58815
58859
|
var UserUsageDetailModal = ({
|
|
58816
58860
|
userId,
|
|
58817
58861
|
userName,
|
|
@@ -58821,7 +58865,7 @@ var UserUsageDetailModal = ({
|
|
|
58821
58865
|
endDate
|
|
58822
58866
|
}) => {
|
|
58823
58867
|
const [currentWeekStart, setCurrentWeekStart] = React143.useState(() => {
|
|
58824
|
-
if (startDate) return
|
|
58868
|
+
if (startDate) return parseUsageDate(startDate);
|
|
58825
58869
|
const { start } = getCurrentWeekRange();
|
|
58826
58870
|
return start;
|
|
58827
58871
|
});
|
|
@@ -58831,8 +58875,8 @@ var UserUsageDetailModal = ({
|
|
|
58831
58875
|
return end;
|
|
58832
58876
|
}, [currentWeekStart]);
|
|
58833
58877
|
const { data, isLoading, error } = useUserUsage(userId, {
|
|
58834
|
-
startDate: currentWeekStart
|
|
58835
|
-
endDate: currentWeekEnd
|
|
58878
|
+
startDate: formatLocalDateKey(currentWeekStart),
|
|
58879
|
+
endDate: formatLocalDateKey(currentWeekEnd)
|
|
58836
58880
|
});
|
|
58837
58881
|
const handlePrevWeek = () => {
|
|
58838
58882
|
setCurrentWeekStart((prev) => {
|
|
@@ -58925,8 +58969,8 @@ function UsageContent({
|
|
|
58925
58969
|
} else {
|
|
58926
58970
|
daysToCount = 7;
|
|
58927
58971
|
}
|
|
58928
|
-
const
|
|
58929
|
-
const weekAvgMs =
|
|
58972
|
+
const weekActiveMs = weekData.reduce((sum, d) => sum + d.active_duration_ms, 0);
|
|
58973
|
+
const weekAvgMs = calculateAverageDailyDuration(weekActiveMs, daysToCount);
|
|
58930
58974
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
58931
58975
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 bg-gray-50 rounded-lg p-1 border border-gray-100", children: [
|
|
58932
58976
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -58938,7 +58982,7 @@ function UsageContent({
|
|
|
58938
58982
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-4 h-4" })
|
|
58939
58983
|
}
|
|
58940
58984
|
),
|
|
58941
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 min-w-[140px] text-center", children: formatDateRange(currentWeekStart
|
|
58985
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 min-w-[140px] text-center", children: formatDateRange(formatLocalDateKey(currentWeekStart), formatLocalDateKey(currentWeekEnd)) }),
|
|
58942
58986
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58943
58987
|
"button",
|
|
58944
58988
|
{
|
|
@@ -58970,7 +59014,7 @@ function DailyBarChart({
|
|
|
58970
59014
|
const weekData = React143.useMemo(() => buildWeekData(dailyData, getDateStringsInRange(weekStart, weekEnd)), [dailyData, weekStart, weekEnd]);
|
|
58971
59015
|
const chartData = React143.useMemo(() => {
|
|
58972
59016
|
return weekData.map((day, index) => {
|
|
58973
|
-
const date =
|
|
59017
|
+
const date = parseUsageDate(day.date);
|
|
58974
59018
|
return {
|
|
58975
59019
|
...day,
|
|
58976
59020
|
index,
|
|
@@ -58980,7 +59024,7 @@ function DailyBarChart({
|
|
|
58980
59024
|
// green-500
|
|
58981
59025
|
passiveColor: "#fbbf24",
|
|
58982
59026
|
// amber-400
|
|
58983
|
-
isToday: day.date === (/* @__PURE__ */ new Date())
|
|
59027
|
+
isToday: day.date === formatLocalDateKey(/* @__PURE__ */ new Date())
|
|
58984
59028
|
};
|
|
58985
59029
|
});
|
|
58986
59030
|
}, [weekData]);
|
|
@@ -59000,7 +59044,7 @@ function DailyBarChart({
|
|
|
59000
59044
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
59001
59045
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 rounded-full bg-gray-300" }),
|
|
59002
59046
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium text-gray-600", children: [
|
|
59003
|
-
"Average Daily usage: ",
|
|
59047
|
+
"Average Daily active usage: ",
|
|
59004
59048
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-900 font-semibold", children: avgDailyMs > 0 ? formatDuration2(avgDailyMs) : "0m" })
|
|
59005
59049
|
] })
|
|
59006
59050
|
] }),
|
|
@@ -59165,17 +59209,14 @@ function getCurrentWeekRange() {
|
|
|
59165
59209
|
function getDateStringsInRange(start, end) {
|
|
59166
59210
|
const result = [];
|
|
59167
59211
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
|
59168
|
-
|
|
59169
|
-
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
59170
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
59171
|
-
result.push(`${year}-${month}-${day}`);
|
|
59212
|
+
result.push(formatLocalDateKey(d));
|
|
59172
59213
|
}
|
|
59173
59214
|
return result;
|
|
59174
59215
|
}
|
|
59175
59216
|
function buildWeekData(dailyData, weekDates) {
|
|
59176
59217
|
const dataMap = new Map(
|
|
59177
59218
|
(dailyData || []).map((d) => {
|
|
59178
|
-
const key =
|
|
59219
|
+
const key = normalizeUsageDateKey(d.date);
|
|
59179
59220
|
return [key, { ...d, date: key }];
|
|
59180
59221
|
})
|
|
59181
59222
|
);
|
|
@@ -59190,22 +59231,10 @@ function buildWeekData(dailyData, weekDates) {
|
|
|
59190
59231
|
};
|
|
59191
59232
|
});
|
|
59192
59233
|
}
|
|
59193
|
-
function normalizeDateKey(dateLike) {
|
|
59194
|
-
if (!dateLike) return void 0;
|
|
59195
|
-
try {
|
|
59196
|
-
const date = new Date(dateLike);
|
|
59197
|
-
const year = date.getFullYear();
|
|
59198
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
59199
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
59200
|
-
return `${year}-${month}-${day}`;
|
|
59201
|
-
} catch {
|
|
59202
|
-
return dateLike.split("T")[0] || dateLike;
|
|
59203
|
-
}
|
|
59204
|
-
}
|
|
59205
59234
|
function formatDateRange(startDate, endDate) {
|
|
59206
59235
|
try {
|
|
59207
|
-
const start =
|
|
59208
|
-
const end =
|
|
59236
|
+
const start = parseUsageDate(startDate);
|
|
59237
|
+
const end = parseUsageDate(endDate);
|
|
59209
59238
|
const opts = { month: "short", day: "numeric" };
|
|
59210
59239
|
const yearOpts = { year: "numeric" };
|
|
59211
59240
|
return `${start.toLocaleDateString("en-US", opts)} - ${end.toLocaleDateString("en-US", opts)}, ${end.toLocaleDateString("en-US", yearOpts)}`;
|
|
@@ -59216,7 +59245,7 @@ function formatDateRange(startDate, endDate) {
|
|
|
59216
59245
|
function formatDate(dateStr) {
|
|
59217
59246
|
if (!dateStr) return "";
|
|
59218
59247
|
try {
|
|
59219
|
-
const date =
|
|
59248
|
+
const date = parseUsageDate(dateStr);
|
|
59220
59249
|
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
59221
59250
|
} catch {
|
|
59222
59251
|
return dateStr;
|
|
@@ -59494,7 +59523,7 @@ var UserManagementTable = ({
|
|
|
59494
59523
|
}
|
|
59495
59524
|
),
|
|
59496
59525
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
|
|
59497
|
-
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily
|
|
59526
|
+
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily Active Usage" }),
|
|
59498
59527
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
|
|
59499
59528
|
] }) }),
|
|
59500
59529
|
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: filteredAndSortedUsers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: showUsageStats ? 5 : 4, className: "px-6 py-12", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center text-gray-400 gap-2", children: [
|
|
@@ -73791,39 +73820,8 @@ var TeamManagementView = ({
|
|
|
73791
73820
|
const [users, setUsers] = React143.useState([]);
|
|
73792
73821
|
const [availableLines, setAvailableLines] = React143.useState([]);
|
|
73793
73822
|
const [availableFactories, setAvailableFactories] = React143.useState([]);
|
|
73794
|
-
const [
|
|
73795
|
-
totalUsers: 0,
|
|
73796
|
-
owners: 0,
|
|
73797
|
-
it: 0,
|
|
73798
|
-
plantHeads: 0,
|
|
73799
|
-
industrialEngineers: 0,
|
|
73800
|
-
supervisors: 0,
|
|
73801
|
-
optifye: 0
|
|
73802
|
-
});
|
|
73823
|
+
const [usageSummaryByUser, setUsageSummaryByUser] = React143.useState({});
|
|
73803
73824
|
const [isAddUserDialogOpen, setIsAddUserDialogOpen] = React143.useState(false);
|
|
73804
|
-
const normalizeIds = React143.useCallback((value) => {
|
|
73805
|
-
if (Array.isArray(value)) {
|
|
73806
|
-
return value.filter((id3) => typeof id3 === "string" && id3.length > 0);
|
|
73807
|
-
}
|
|
73808
|
-
if (typeof value === "string" && value.length > 0) {
|
|
73809
|
-
return [value];
|
|
73810
|
-
}
|
|
73811
|
-
return [];
|
|
73812
|
-
}, []);
|
|
73813
|
-
const factoryScopedRoleFactoryIds = React143.useMemo(() => {
|
|
73814
|
-
if (!isFactoryScopedRole(user?.role_level)) return [];
|
|
73815
|
-
const scopedFactoryIds = normalizeIds(user?.access_scope?.factory_ids);
|
|
73816
|
-
if (scopedFactoryIds.length > 0) {
|
|
73817
|
-
return Array.from(new Set(scopedFactoryIds));
|
|
73818
|
-
}
|
|
73819
|
-
const propertyFactoryIds = normalizeIds(
|
|
73820
|
-
user?.properties?.factory_ids ?? user?.properties?.factory_id
|
|
73821
|
-
);
|
|
73822
|
-
if (propertyFactoryIds.length > 0) {
|
|
73823
|
-
return Array.from(new Set(propertyFactoryIds));
|
|
73824
|
-
}
|
|
73825
|
-
return entityConfig?.factoryId ? [entityConfig.factoryId] : [];
|
|
73826
|
-
}, [user, entityConfig?.factoryId, normalizeIds]);
|
|
73827
73825
|
const notifyScopeRefresh = React143.useCallback(() => {
|
|
73828
73826
|
if (typeof window !== "undefined") {
|
|
73829
73827
|
window.dispatchEvent(new Event("rbac:refresh-scope"));
|
|
@@ -73832,37 +73830,17 @@ var TeamManagementView = ({
|
|
|
73832
73830
|
const canAddUsers = canRoleManageUsers(user?.role_level);
|
|
73833
73831
|
const canViewUsageStats = canRoleViewUsageStats(user?.role_level);
|
|
73834
73832
|
const pageCompanyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
73835
|
-
const
|
|
73836
|
-
const usageDateRange = React143.useMemo(() => {
|
|
73837
|
-
const today = /* @__PURE__ */ new Date();
|
|
73838
|
-
const dayOfWeek = today.getDay();
|
|
73839
|
-
const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
73840
|
-
const monday = new Date(today);
|
|
73841
|
-
monday.setDate(today.getDate() - daysSinceMonday);
|
|
73842
|
-
return {
|
|
73843
|
-
startDate: monday.toISOString().slice(0, 10),
|
|
73844
|
-
endDate: today.toISOString().slice(0, 10),
|
|
73845
|
-
daysElapsed: daysSinceMonday + 1
|
|
73846
|
-
// Include today
|
|
73847
|
-
};
|
|
73848
|
-
}, []);
|
|
73849
|
-
const {
|
|
73850
|
-
data: usageData,
|
|
73851
|
-
isLoading: isUsageLoading
|
|
73852
|
-
} = useCompanyUsersUsage(canViewUsageStats ? companyIdForUsage : void 0, {
|
|
73853
|
-
startDate: usageDateRange.startDate,
|
|
73854
|
-
endDate: usageDateRange.endDate
|
|
73855
|
-
});
|
|
73833
|
+
const usageDateRange = React143.useMemo(() => getCurrentWeekToDateUsageRange(), []);
|
|
73856
73834
|
const avgDailyUsageMap = React143.useMemo(() => {
|
|
73857
|
-
if (!
|
|
73835
|
+
if (!canViewUsageStats) {
|
|
73858
73836
|
return {};
|
|
73859
73837
|
}
|
|
73860
73838
|
const daysElapsed = usageDateRange.daysElapsed;
|
|
73861
|
-
return
|
|
73862
|
-
acc[
|
|
73839
|
+
return Object.entries(usageSummaryByUser).reduce((acc, [userId, usage]) => {
|
|
73840
|
+
acc[userId] = calculateAverageDailyDuration(usage.active_duration_ms, daysElapsed);
|
|
73863
73841
|
return acc;
|
|
73864
73842
|
}, {});
|
|
73865
|
-
}, [
|
|
73843
|
+
}, [canViewUsageStats, usageSummaryByUser, usageDateRange.daysElapsed]);
|
|
73866
73844
|
const pageTitle = "Team Management";
|
|
73867
73845
|
const pageDescription = canViewUsageStats ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
|
|
73868
73846
|
const hasAccess = user ? user.role_level === void 0 || canRoleAccessTeamManagement(user.role_level) : false;
|
|
@@ -73886,7 +73864,6 @@ var TeamManagementView = ({
|
|
|
73886
73864
|
setIsLoading(true);
|
|
73887
73865
|
setError(void 0);
|
|
73888
73866
|
try {
|
|
73889
|
-
const userManagementService = createUserManagementService(supabase);
|
|
73890
73867
|
const { data: { session } } = await supabase.auth.getSession();
|
|
73891
73868
|
if (!session?.access_token) {
|
|
73892
73869
|
throw new Error("No authentication token available");
|
|
@@ -73896,116 +73873,31 @@ var TeamManagementView = ({
|
|
|
73896
73873
|
if (!backendUrl) {
|
|
73897
73874
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
73898
73875
|
}
|
|
73899
|
-
|
|
73900
|
-
|
|
73901
|
-
|
|
73876
|
+
const params = new URLSearchParams({
|
|
73877
|
+
start_date: usageDateRange.startDate,
|
|
73878
|
+
end_date: usageDateRange.endDate
|
|
73879
|
+
});
|
|
73880
|
+
const bootstrapResponse = await fetch(
|
|
73881
|
+
`${backendUrl}/api/users/company/${companyId}/bootstrap?${params.toString()}`,
|
|
73882
|
+
{
|
|
73902
73883
|
headers: {
|
|
73903
73884
|
"Authorization": `Bearer ${token}`,
|
|
73904
73885
|
"Content-Type": "application/json"
|
|
73905
73886
|
}
|
|
73906
|
-
});
|
|
73907
|
-
if (!linesResponse.ok) {
|
|
73908
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
73909
|
-
}
|
|
73910
|
-
const linesData = await linesResponse.json();
|
|
73911
|
-
const lines = linesData.lines || [];
|
|
73912
|
-
setAvailableFactories(factories || []);
|
|
73913
|
-
setAvailableLines(lines);
|
|
73914
|
-
console.log("[TeamManagementView] Company-scoped team view - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
|
|
73915
|
-
} else if (isFactoryScopedRole(user?.role_level)) {
|
|
73916
|
-
if (factoryScopedRoleFactoryIds.length > 0) {
|
|
73917
|
-
if (companyId) {
|
|
73918
|
-
const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
|
|
73919
|
-
headers: {
|
|
73920
|
-
"Authorization": `Bearer ${token}`,
|
|
73921
|
-
"Content-Type": "application/json"
|
|
73922
|
-
}
|
|
73923
|
-
});
|
|
73924
|
-
if (!linesResponse.ok) {
|
|
73925
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
73926
|
-
}
|
|
73927
|
-
const linesData = await linesResponse.json();
|
|
73928
|
-
const allLines = linesData.lines || [];
|
|
73929
|
-
const lines = allLines.filter((line) => factoryScopedRoleFactoryIds.includes(line.factory_id));
|
|
73930
|
-
setAvailableLines(lines);
|
|
73931
|
-
console.log("[TeamManagementView] Factory-scoped team view - Factories:", factoryScopedRoleFactoryIds, "Loaded lines:", lines?.length);
|
|
73932
|
-
}
|
|
73933
|
-
} else if (entityConfig?.factoryId) {
|
|
73934
|
-
const linesResponse = await fetch(`${backendUrl}/api/lines?factory_id=${entityConfig.factoryId}`, {
|
|
73935
|
-
headers: {
|
|
73936
|
-
"Authorization": `Bearer ${token}`,
|
|
73937
|
-
"Content-Type": "application/json"
|
|
73938
|
-
}
|
|
73939
|
-
});
|
|
73940
|
-
if (!linesResponse.ok) {
|
|
73941
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
73942
|
-
}
|
|
73943
|
-
const linesData = await linesResponse.json();
|
|
73944
|
-
const lines = linesData.lines || [];
|
|
73945
|
-
setAvailableLines(lines);
|
|
73946
|
-
console.log("[TeamManagementView] Plant Head - Using entityConfig factory:", entityConfig.factoryId, "Loaded lines:", lines?.length);
|
|
73947
|
-
} else {
|
|
73948
|
-
setAvailableLines([]);
|
|
73949
|
-
console.warn("[TeamManagementView] Plant Head has no factory assignments");
|
|
73950
73887
|
}
|
|
73951
|
-
|
|
73952
|
-
|
|
73953
|
-
const
|
|
73954
|
-
|
|
73955
|
-
|
|
73956
|
-
|
|
73957
|
-
|
|
73958
|
-
|
|
73959
|
-
|
|
73960
|
-
|
|
73961
|
-
|
|
73962
|
-
|
|
73963
|
-
|
|
73964
|
-
setAvailableLines(lines);
|
|
73965
|
-
setAvailableFactories([]);
|
|
73966
|
-
console.log("[TeamManagementView] Fallback - Company:", companyId, "Loaded lines:", lines?.length);
|
|
73967
|
-
}
|
|
73968
|
-
const usersPromise = isFactoryScopedRole(user?.role_level) ? (async () => {
|
|
73969
|
-
if (factoryScopedRoleFactoryIds.length === 0) {
|
|
73970
|
-
return [];
|
|
73971
|
-
}
|
|
73972
|
-
const results = await Promise.allSettled(
|
|
73973
|
-
factoryScopedRoleFactoryIds.map((factoryId) => userManagementService.getFactoryUsers(factoryId))
|
|
73974
|
-
);
|
|
73975
|
-
const successful = results.filter(
|
|
73976
|
-
(result) => result.status === "fulfilled"
|
|
73977
|
-
).flatMap((result) => result.value || []);
|
|
73978
|
-
if (successful.length > 0) {
|
|
73979
|
-
const byUserId = /* @__PURE__ */ new Map();
|
|
73980
|
-
successful.forEach((u) => {
|
|
73981
|
-
if (u?.user_id) {
|
|
73982
|
-
byUserId.set(u.user_id, u);
|
|
73983
|
-
}
|
|
73984
|
-
});
|
|
73985
|
-
return Array.from(byUserId.values());
|
|
73986
|
-
}
|
|
73987
|
-
const firstRejected = results.find(
|
|
73988
|
-
(result) => result.status === "rejected"
|
|
73989
|
-
);
|
|
73990
|
-
if (firstRejected) {
|
|
73991
|
-
throw firstRejected.reason;
|
|
73992
|
-
}
|
|
73993
|
-
return [];
|
|
73994
|
-
})() : userManagementService.getCompanyUsers(companyId);
|
|
73995
|
-
const [usersData, userStats] = await Promise.all([
|
|
73996
|
-
usersPromise,
|
|
73997
|
-
userManagementService.getUserStats(companyId)
|
|
73998
|
-
]);
|
|
73999
|
-
setUsers(usersData);
|
|
74000
|
-
setStats({
|
|
74001
|
-
totalUsers: userStats.total,
|
|
74002
|
-
owners: userStats.owners,
|
|
74003
|
-
it: userStats.it,
|
|
74004
|
-
plantHeads: userStats.plant_heads,
|
|
74005
|
-
industrialEngineers: userStats.industrial_engineers,
|
|
74006
|
-
supervisors: userStats.supervisors,
|
|
74007
|
-
optifye: 0
|
|
74008
|
-
});
|
|
73888
|
+
);
|
|
73889
|
+
if (!bootstrapResponse.ok) {
|
|
73890
|
+
const errorData = await bootstrapResponse.json().catch(() => ({}));
|
|
73891
|
+
throw new Error(errorData.detail || `Failed to fetch team bootstrap: ${bootstrapResponse.statusText}`);
|
|
73892
|
+
}
|
|
73893
|
+
const bootstrapData = await bootstrapResponse.json();
|
|
73894
|
+
setAvailableFactories((bootstrapData.factories || []).map((factory) => ({
|
|
73895
|
+
id: factory.id,
|
|
73896
|
+
factory_name: factory.factory_name
|
|
73897
|
+
})));
|
|
73898
|
+
setAvailableLines(bootstrapData.lines || []);
|
|
73899
|
+
setUsers(bootstrapData.users || []);
|
|
73900
|
+
setUsageSummaryByUser(bootstrapData.usage_summary_by_user || {});
|
|
74009
73901
|
} catch (err) {
|
|
74010
73902
|
console.error("Error loading team management data:", err);
|
|
74011
73903
|
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
@@ -74013,7 +73905,7 @@ var TeamManagementView = ({
|
|
|
74013
73905
|
} finally {
|
|
74014
73906
|
setIsLoading(false);
|
|
74015
73907
|
}
|
|
74016
|
-
}, [supabase, user, pageCompanyId,
|
|
73908
|
+
}, [supabase, user, pageCompanyId, usageDateRange.endDate, usageDateRange.startDate]);
|
|
74017
73909
|
React143.useEffect(() => {
|
|
74018
73910
|
const companyId = pageCompanyId;
|
|
74019
73911
|
const canLoad = hasAccess && user && !!companyId;
|
|
@@ -74194,7 +74086,7 @@ var TeamManagementView = ({
|
|
|
74194
74086
|
availableLines,
|
|
74195
74087
|
availableFactories,
|
|
74196
74088
|
avgDailyUsage: avgDailyUsageMap,
|
|
74197
|
-
isUsageLoading,
|
|
74089
|
+
isUsageLoading: false,
|
|
74198
74090
|
showUsageStats: canViewUsageStats
|
|
74199
74091
|
}
|
|
74200
74092
|
) }),
|
|
@@ -79017,6 +78909,12 @@ var buildDeltaBadge = (delta, options) => {
|
|
|
79017
78909
|
text: `${options.formatter(delta)} vs ${options.comparisonLabel}`
|
|
79018
78910
|
};
|
|
79019
78911
|
};
|
|
78912
|
+
var parseSpecificShiftId = (shiftMode) => {
|
|
78913
|
+
const rawMode = String(shiftMode || "").trim().toLowerCase();
|
|
78914
|
+
if (!rawMode.startsWith("shift:")) return null;
|
|
78915
|
+
const parsed = Number(rawMode.split(":", 2)[1]);
|
|
78916
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
78917
|
+
};
|
|
79020
78918
|
var normalizeShiftLabel = (shiftName, shiftMode) => {
|
|
79021
78919
|
if (shiftMode === "all") {
|
|
79022
78920
|
return "All Shifts";
|
|
@@ -79028,6 +78926,10 @@ var normalizeShiftLabel = (shiftName, shiftMode) => {
|
|
|
79028
78926
|
if (normalizedName === "night") return "Night Shift";
|
|
79029
78927
|
return /shift/i.test(trimmedName) ? trimmedName : `${trimmedName} Shift`;
|
|
79030
78928
|
}
|
|
78929
|
+
const specificShiftId = parseSpecificShiftId(shiftMode);
|
|
78930
|
+
if (specificShiftId !== null) {
|
|
78931
|
+
return `Shift ${specificShiftId}`;
|
|
78932
|
+
}
|
|
79031
78933
|
if (shiftMode === "night") return "Night Shift";
|
|
79032
78934
|
return "Day Shift";
|
|
79033
78935
|
};
|
|
@@ -79100,6 +79002,7 @@ var OperationsOverviewHeader = React143__namespace.default.memo(({
|
|
|
79100
79002
|
dateRange,
|
|
79101
79003
|
displayDateRange,
|
|
79102
79004
|
trendMode,
|
|
79005
|
+
shiftFilterOptions,
|
|
79103
79006
|
isLiveScope,
|
|
79104
79007
|
liveShiftName,
|
|
79105
79008
|
lineOptions,
|
|
@@ -79353,7 +79256,7 @@ var OperationsOverviewHeader = React143__namespace.default.memo(({
|
|
|
79353
79256
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
79354
79257
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
79355
79258
|
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Shift" }),
|
|
79356
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.
|
|
79259
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
79357
79260
|
"select",
|
|
79358
79261
|
{
|
|
79359
79262
|
value: trendMode,
|
|
@@ -79365,11 +79268,7 @@ var OperationsOverviewHeader = React143__namespace.default.memo(({
|
|
|
79365
79268
|
backgroundRepeat: "no-repeat",
|
|
79366
79269
|
backgroundSize: "1.2em 1.2em"
|
|
79367
79270
|
},
|
|
79368
|
-
children:
|
|
79369
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Shifts" }),
|
|
79370
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day Shift" }),
|
|
79371
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "night", children: "Night Shift" })
|
|
79372
|
-
]
|
|
79271
|
+
children: shiftFilterOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
|
|
79373
79272
|
}
|
|
79374
79273
|
) })
|
|
79375
79274
|
] }),
|
|
@@ -80439,6 +80338,24 @@ var normalizeShiftId = (value) => {
|
|
|
80439
80338
|
}
|
|
80440
80339
|
return null;
|
|
80441
80340
|
};
|
|
80341
|
+
var buildSpecificShiftMode = (value) => {
|
|
80342
|
+
const normalizedShiftId = normalizeShiftId(value);
|
|
80343
|
+
return normalizedShiftId === null ? null : `shift:${normalizedShiftId}`;
|
|
80344
|
+
};
|
|
80345
|
+
var parseSpecificShiftMode = (value) => {
|
|
80346
|
+
const rawValue = String(value || "").trim().toLowerCase();
|
|
80347
|
+
if (!rawValue.startsWith("shift:")) return null;
|
|
80348
|
+
const parsed = Number(rawValue.split(":", 2)[1]);
|
|
80349
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
80350
|
+
};
|
|
80351
|
+
var formatShiftOptionLabel = (shiftName, shiftId) => {
|
|
80352
|
+
const trimmedName = shiftName?.trim();
|
|
80353
|
+
if (trimmedName) {
|
|
80354
|
+
return /shift/i.test(trimmedName) ? trimmedName : `${trimmedName} Shift`;
|
|
80355
|
+
}
|
|
80356
|
+
const normalizedShiftId = normalizeShiftId(shiftId);
|
|
80357
|
+
return normalizedShiftId === null ? "Unknown Shift" : `Shift ${normalizedShiftId}`;
|
|
80358
|
+
};
|
|
80442
80359
|
var classifyShiftBucket = ({
|
|
80443
80360
|
shiftName,
|
|
80444
80361
|
shiftId,
|
|
@@ -80525,6 +80442,26 @@ var normalizeShiftWindowMinutes = (startTime, endTime) => {
|
|
|
80525
80442
|
}
|
|
80526
80443
|
return { startMinutes, endMinutes };
|
|
80527
80444
|
};
|
|
80445
|
+
var getOperationalSortStartMinutes = (startTime, endTime) => {
|
|
80446
|
+
const normalizedWindow = normalizeShiftWindowMinutes(startTime, endTime);
|
|
80447
|
+
if (!normalizedWindow) return null;
|
|
80448
|
+
return normalizedWindow.startMinutes < 6 * 60 ? normalizedWindow.startMinutes + 24 * 60 : normalizedWindow.startMinutes;
|
|
80449
|
+
};
|
|
80450
|
+
var doesShiftMatchTrendMode = ({
|
|
80451
|
+
trendMode,
|
|
80452
|
+
shiftName,
|
|
80453
|
+
shiftId,
|
|
80454
|
+
startTime,
|
|
80455
|
+
endTime
|
|
80456
|
+
}) => {
|
|
80457
|
+
if (trendMode === "all") return true;
|
|
80458
|
+
const specificShiftId = parseSpecificShiftMode(trendMode);
|
|
80459
|
+
if (specificShiftId !== null) {
|
|
80460
|
+
return normalizeShiftId(shiftId) === specificShiftId;
|
|
80461
|
+
}
|
|
80462
|
+
const bucket = classifyShiftBucket({ shiftName, shiftId, startTime, endTime });
|
|
80463
|
+
return bucket === trendMode;
|
|
80464
|
+
};
|
|
80528
80465
|
var PlantHeadView = () => {
|
|
80529
80466
|
const supabase = useSupabase();
|
|
80530
80467
|
const entityConfig = useEntityConfig();
|
|
@@ -80655,6 +80592,45 @@ var PlantHeadView = () => {
|
|
|
80655
80592
|
shiftConfigMap,
|
|
80656
80593
|
isLoading: isShiftConfigLoading
|
|
80657
80594
|
} = useMultiLineShiftConfigs(scopedLineIds, staticShiftConfig);
|
|
80595
|
+
const shiftFilterOptions = React143__namespace.default.useMemo(() => {
|
|
80596
|
+
const optionsById = /* @__PURE__ */ new Map();
|
|
80597
|
+
scopedLineIds.forEach((lineId) => {
|
|
80598
|
+
const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
|
|
80599
|
+
getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
|
|
80600
|
+
const shiftId = normalizeShiftId(shift.shiftId);
|
|
80601
|
+
if (shiftId === null) return;
|
|
80602
|
+
const sortKey = getOperationalSortStartMinutes(shift.startTime, shift.endTime) ?? 24 * 60 + shiftId;
|
|
80603
|
+
const existing = optionsById.get(shiftId);
|
|
80604
|
+
const label = formatShiftOptionLabel(shift.shiftName, shiftId);
|
|
80605
|
+
if (!existing) {
|
|
80606
|
+
optionsById.set(shiftId, {
|
|
80607
|
+
value: buildSpecificShiftMode(shiftId),
|
|
80608
|
+
label,
|
|
80609
|
+
shiftId,
|
|
80610
|
+
shiftName: shift.shiftName || null,
|
|
80611
|
+
startTime: shift.startTime || null,
|
|
80612
|
+
endTime: shift.endTime || null,
|
|
80613
|
+
sortKey
|
|
80614
|
+
});
|
|
80615
|
+
return;
|
|
80616
|
+
}
|
|
80617
|
+
if ((!existing.shiftName || existing.shiftName === `Shift ${shiftId}`) && shift.shiftName) {
|
|
80618
|
+
existing.shiftName = shift.shiftName;
|
|
80619
|
+
existing.label = label;
|
|
80620
|
+
}
|
|
80621
|
+
if (sortKey < existing.sortKey) {
|
|
80622
|
+
existing.sortKey = sortKey;
|
|
80623
|
+
existing.startTime = shift.startTime || null;
|
|
80624
|
+
existing.endTime = shift.endTime || null;
|
|
80625
|
+
}
|
|
80626
|
+
});
|
|
80627
|
+
});
|
|
80628
|
+
const dynamicOptions = Array.from(optionsById.values()).sort((a, b) => a.sortKey - b.sortKey || (a.shiftId || 0) - (b.shiftId || 0)).map(({ sortKey, ...option }) => option);
|
|
80629
|
+
return [
|
|
80630
|
+
{ value: "all", label: "All Shifts" },
|
|
80631
|
+
...dynamicOptions
|
|
80632
|
+
];
|
|
80633
|
+
}, [appTimezone, scopedLineIds, shiftConfigMap, staticShiftConfig]);
|
|
80658
80634
|
React143__namespace.default.useEffect(() => {
|
|
80659
80635
|
if (scopedLineIds.length === 0 || isShiftConfigLoading) {
|
|
80660
80636
|
return;
|
|
@@ -80717,18 +80693,20 @@ var PlantHeadView = () => {
|
|
|
80717
80693
|
if (!activeShift) {
|
|
80718
80694
|
return [];
|
|
80719
80695
|
}
|
|
80720
|
-
const
|
|
80696
|
+
const bucketTrendMode = classifyShiftBucket({
|
|
80721
80697
|
shiftName: activeShift.shiftName,
|
|
80722
80698
|
shiftId: activeShift.shiftId,
|
|
80723
80699
|
startTime: activeShift.startTime,
|
|
80724
80700
|
endTime: activeShift.endTime
|
|
80725
80701
|
});
|
|
80726
|
-
|
|
80702
|
+
const exactTrendMode = buildSpecificShiftMode(activeShift.shiftId);
|
|
80703
|
+
if (!bucketTrendMode && !exactTrendMode) {
|
|
80727
80704
|
return [];
|
|
80728
80705
|
}
|
|
80729
80706
|
return [{
|
|
80730
80707
|
lineId,
|
|
80731
|
-
|
|
80708
|
+
exactTrendMode,
|
|
80709
|
+
bucketTrendMode,
|
|
80732
80710
|
shiftId: activeShift.shiftId,
|
|
80733
80711
|
shiftName: activeShift.shiftName || null,
|
|
80734
80712
|
startTime: activeShift.startTime || null,
|
|
@@ -80737,14 +80715,12 @@ var PlantHeadView = () => {
|
|
|
80737
80715
|
}];
|
|
80738
80716
|
});
|
|
80739
80717
|
}, [appTimezone, scopedLineIds, shiftConfigMap, shiftResolutionNow, staticShiftConfig]);
|
|
80740
|
-
const
|
|
80741
|
-
|
|
80742
|
-
|
|
80743
|
-
|
|
80744
|
-
|
|
80745
|
-
|
|
80746
|
-
[activeLineShiftStates, resolvedOperationalToday]
|
|
80747
|
-
);
|
|
80718
|
+
const uniformActiveTrendMode = React143__namespace.default.useMemo(() => {
|
|
80719
|
+
const activeModes = Array.from(new Set(
|
|
80720
|
+
activeLineShiftStates.filter((shift) => shift.date === resolvedOperationalToday).map((shift) => shift.exactTrendMode).filter((mode) => !!mode)
|
|
80721
|
+
));
|
|
80722
|
+
return activeModes.length === 1 ? activeModes[0] : null;
|
|
80723
|
+
}, [activeLineShiftStates, resolvedOperationalToday]);
|
|
80748
80724
|
const resolvedTrendMode = isInitialScopeReady ? trendMode : "all";
|
|
80749
80725
|
const hourlyWindowStartTime = React143__namespace.default.useMemo(() => {
|
|
80750
80726
|
if (scopedLineIds.length === 0) {
|
|
@@ -80755,13 +80731,19 @@ var PlantHeadView = () => {
|
|
|
80755
80731
|
scopedLineIds.forEach((lineId) => {
|
|
80756
80732
|
const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
|
|
80757
80733
|
getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
|
|
80758
|
-
|
|
80734
|
+
classifyShiftBucket({
|
|
80759
80735
|
shiftName: shift.shiftName,
|
|
80760
80736
|
shiftId: shift.shiftId,
|
|
80761
80737
|
startTime: shift.startTime,
|
|
80762
80738
|
endTime: shift.endTime
|
|
80763
80739
|
});
|
|
80764
|
-
if (
|
|
80740
|
+
if (!doesShiftMatchTrendMode({
|
|
80741
|
+
trendMode: resolvedTrendMode,
|
|
80742
|
+
shiftName: shift.shiftName,
|
|
80743
|
+
shiftId: shift.shiftId,
|
|
80744
|
+
startTime: shift.startTime,
|
|
80745
|
+
endTime: shift.endTime
|
|
80746
|
+
})) {
|
|
80765
80747
|
return;
|
|
80766
80748
|
}
|
|
80767
80749
|
const normalizedWindow = normalizeShiftWindowMinutes(shift.startTime, shift.endTime);
|
|
@@ -80835,11 +80817,11 @@ var PlantHeadView = () => {
|
|
|
80835
80817
|
endKey: nextStartKey
|
|
80836
80818
|
};
|
|
80837
80819
|
});
|
|
80838
|
-
setTrendMode("all");
|
|
80820
|
+
setTrendMode(uniformActiveTrendMode || "all");
|
|
80839
80821
|
setUsesThisWeekComparison(false);
|
|
80840
80822
|
hasAutoInitializedScopeRef.current = true;
|
|
80841
80823
|
setIsInitialScopeReady(true);
|
|
80842
|
-
}, [fallbackOperationalDate, isShiftScopeResolved, resolvedOperationalToday, scopedLineIds.length]);
|
|
80824
|
+
}, [fallbackOperationalDate, isShiftScopeResolved, resolvedOperationalToday, scopedLineIds.length, uniformActiveTrendMode]);
|
|
80843
80825
|
const handleDateRangeChange = React143__namespace.default.useCallback((range, meta) => {
|
|
80844
80826
|
hasUserAdjustedScopeRef.current = true;
|
|
80845
80827
|
setIsInitialScopeReady(true);
|
|
@@ -80926,6 +80908,33 @@ var PlantHeadView = () => {
|
|
|
80926
80908
|
() => resolvedTrendMode,
|
|
80927
80909
|
[resolvedTrendMode]
|
|
80928
80910
|
);
|
|
80911
|
+
const hasActiveSelectedShiftLine = React143__namespace.default.useMemo(
|
|
80912
|
+
() => activeLineShiftStates.some((shift) => {
|
|
80913
|
+
if (shift.date !== resolvedOperationalToday) return false;
|
|
80914
|
+
if (effectiveTrendMode === "all") return true;
|
|
80915
|
+
const specificShiftId = parseSpecificShiftMode(effectiveTrendMode);
|
|
80916
|
+
if (specificShiftId !== null) {
|
|
80917
|
+
return shift.exactTrendMode === effectiveTrendMode;
|
|
80918
|
+
}
|
|
80919
|
+
return shift.bucketTrendMode === effectiveTrendMode;
|
|
80920
|
+
}),
|
|
80921
|
+
[activeLineShiftStates, effectiveTrendMode, resolvedOperationalToday]
|
|
80922
|
+
);
|
|
80923
|
+
const activeLiveShiftName = React143__namespace.default.useMemo(
|
|
80924
|
+
() => {
|
|
80925
|
+
if (effectiveTrendMode === "all") return null;
|
|
80926
|
+
const matchingShift = activeLineShiftStates.find((shift) => {
|
|
80927
|
+
if (shift.date !== resolvedOperationalToday) return false;
|
|
80928
|
+
const specificShiftId = parseSpecificShiftMode(effectiveTrendMode);
|
|
80929
|
+
if (specificShiftId !== null) {
|
|
80930
|
+
return shift.exactTrendMode === effectiveTrendMode;
|
|
80931
|
+
}
|
|
80932
|
+
return shift.bucketTrendMode === effectiveTrendMode;
|
|
80933
|
+
});
|
|
80934
|
+
return matchingShift?.shiftName || null;
|
|
80935
|
+
},
|
|
80936
|
+
[activeLineShiftStates, effectiveTrendMode, resolvedOperationalToday]
|
|
80937
|
+
);
|
|
80929
80938
|
const hourlyLabelStartTime = React143__namespace.default.useMemo(() => {
|
|
80930
80939
|
if (scopedLineIds.length === 0) {
|
|
80931
80940
|
return null;
|
|
@@ -80937,12 +80946,11 @@ var PlantHeadView = () => {
|
|
|
80937
80946
|
[effectiveDateRange.endKey, effectiveDateRange.startKey]
|
|
80938
80947
|
);
|
|
80939
80948
|
const isLiveScope = React143__namespace.default.useMemo(
|
|
80940
|
-
() => isSingleDayScope && effectiveDateRange.startKey === resolvedOperationalToday &&
|
|
80949
|
+
() => isSingleDayScope && effectiveDateRange.startKey === resolvedOperationalToday && hasActiveSelectedShiftLine,
|
|
80941
80950
|
[
|
|
80942
80951
|
effectiveDateRange.startKey,
|
|
80943
80952
|
effectiveTrendMode,
|
|
80944
|
-
|
|
80945
|
-
hasActiveNightShiftLine,
|
|
80953
|
+
hasActiveSelectedShiftLine,
|
|
80946
80954
|
isSingleDayScope,
|
|
80947
80955
|
resolvedOperationalToday
|
|
80948
80956
|
]
|
|
@@ -80979,8 +80987,9 @@ var PlantHeadView = () => {
|
|
|
80979
80987
|
dateRange,
|
|
80980
80988
|
displayDateRange: headerDateRange,
|
|
80981
80989
|
trendMode,
|
|
80990
|
+
shiftFilterOptions,
|
|
80982
80991
|
isLiveScope,
|
|
80983
|
-
liveShiftName: isLiveScope && trendMode !== "all" ?
|
|
80992
|
+
liveShiftName: isLiveScope && trendMode !== "all" ? activeLiveShiftName : null,
|
|
80984
80993
|
lineOptions,
|
|
80985
80994
|
supervisorOptions,
|
|
80986
80995
|
selectedSupervisorId,
|