mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 04:08:11 +00:00
user management ui
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
const LOG_PREFIX = "[BulkUpload:Parser]";
|
||||
|
||||
type ParseLogPayload = Record<string, unknown>;
|
||||
|
||||
function log(stage: string, payload?: ParseLogPayload) {
|
||||
if (payload) {
|
||||
console.log(LOG_PREFIX, stage, payload);
|
||||
return;
|
||||
}
|
||||
console.log(LOG_PREFIX, stage);
|
||||
}
|
||||
|
||||
export function logBulkUploadParseStart(
|
||||
mode: "new" | "update",
|
||||
fileName: string,
|
||||
) {
|
||||
log("start", { mode, fileName });
|
||||
}
|
||||
|
||||
export function logBulkUploadWorkbook(
|
||||
mode: "new" | "update",
|
||||
sheetNames: string[],
|
||||
) {
|
||||
log("workbook", { mode, sheetNames, sheetCount: sheetNames.length });
|
||||
}
|
||||
|
||||
export function logBulkUploadSheetParse(
|
||||
mode: "new" | "update",
|
||||
sheetName: string,
|
||||
details: ParseLogPayload,
|
||||
) {
|
||||
log(`sheet:${sheetName}`, { mode, ...details });
|
||||
}
|
||||
|
||||
export function logBulkUploadValidation(
|
||||
mode: "new" | "update",
|
||||
details: ParseLogPayload,
|
||||
) {
|
||||
log("validation", { mode, ...details });
|
||||
}
|
||||
|
||||
export function logBulkUploadParseSuccess(
|
||||
mode: "new" | "update",
|
||||
details: ParseLogPayload,
|
||||
) {
|
||||
log("success", { mode, ...details });
|
||||
}
|
||||
|
||||
export function logBulkUploadParseError(
|
||||
mode: "new" | "update",
|
||||
error: unknown,
|
||||
details?: ParseLogPayload,
|
||||
) {
|
||||
log("error", {
|
||||
mode,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
...details,
|
||||
});
|
||||
if (error instanceof Error && error.stack) {
|
||||
console.error(LOG_PREFIX, "stack", error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
export function logBulkUploadParseAbort(
|
||||
mode: "new" | "update",
|
||||
reason: string,
|
||||
details?: ParseLogPayload,
|
||||
) {
|
||||
log("abort", { mode, reason, ...details });
|
||||
}
|
||||
|
||||
export type DuplicateRow = {
|
||||
Username?: string;
|
||||
Email?: string;
|
||||
PhoneNumber?: string;
|
||||
Position?: string;
|
||||
ReportsTo?: string;
|
||||
__rowNumber?: number;
|
||||
};
|
||||
|
||||
function normalizeDuplicateKey(
|
||||
value: string,
|
||||
field: keyof DuplicateRow,
|
||||
): string {
|
||||
const trimmed = value.trim();
|
||||
if (field === "PhoneNumber") {
|
||||
return trimmed.replace(/\D/g, "");
|
||||
}
|
||||
if (field === "Email" || field === "Username") {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
|
||||
function findDuplicateValues<T extends DuplicateRow>(
|
||||
rows: T[],
|
||||
field: keyof T,
|
||||
): Array<{ value: string; count: number; rows: T[] }> {
|
||||
const map = new Map<string, T[]>();
|
||||
for (const row of rows) {
|
||||
const raw = row[field];
|
||||
const value = typeof raw === "string" ? raw.trim() : "";
|
||||
if (!value) continue;
|
||||
const key = normalizeDuplicateKey(value, field as keyof DuplicateRow);
|
||||
if (!key) continue;
|
||||
if (!map.has(key)) map.set(key, []);
|
||||
map.get(key)!.push(row);
|
||||
}
|
||||
return [...map.entries()]
|
||||
.filter(([, matches]) => matches.length > 1)
|
||||
.map(([, matches]) => ({
|
||||
value: String(matches[0][field] ?? "").trim(),
|
||||
count: matches.length,
|
||||
rows: matches,
|
||||
}));
|
||||
}
|
||||
|
||||
export function collectBulkUploadDuplicateErrors(
|
||||
users: DuplicateRow[],
|
||||
): string[] {
|
||||
const errors: string[] = [];
|
||||
const checks: Array<{ field: keyof DuplicateRow; label: string }> = [
|
||||
{ field: "Username", label: "Username" },
|
||||
{ field: "Email", label: "Email" },
|
||||
{ field: "PhoneNumber", label: "Phone number" },
|
||||
];
|
||||
|
||||
for (const { field, label } of checks) {
|
||||
const duplicates = findDuplicateValues(users, field);
|
||||
for (const duplicate of duplicates) {
|
||||
const rows = duplicate.rows
|
||||
.map((row) =>
|
||||
typeof row.__rowNumber === "number"
|
||||
? `row ${row.__rowNumber}`
|
||||
: String(row.Username ?? "unknown"),
|
||||
)
|
||||
.join(", ");
|
||||
errors.push(
|
||||
`Duplicate ${label} "${duplicate.value}" found in ${rows}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function logBulkUploadDuplicateCheck(
|
||||
mode: "new" | "update",
|
||||
positions: DuplicateRow[],
|
||||
users: DuplicateRow[],
|
||||
) {
|
||||
const positionNameDups = findDuplicateValues(positions, "Position");
|
||||
const usernameDups = findDuplicateValues(users, "Username");
|
||||
const emailDups = findDuplicateValues(users, "Email");
|
||||
const phoneDups = findDuplicateValues(users, "PhoneNumber");
|
||||
const duplicateErrors = collectBulkUploadDuplicateErrors(users);
|
||||
|
||||
const invalidReportsTo = positions.filter((row) => {
|
||||
const position = String(row.Position ?? "").trim();
|
||||
const reportsTo = String(row.ReportsTo ?? "").trim();
|
||||
return reportsTo && reportsTo === position;
|
||||
});
|
||||
|
||||
const userPositions = new Set(
|
||||
positions
|
||||
.map((row) => String(row.Position ?? "").trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
const missingPositionRefs = users.filter(
|
||||
(row) =>
|
||||
row.Position &&
|
||||
!userPositions.has(String(row.Position).trim()),
|
||||
);
|
||||
|
||||
log("duplicate-check", {
|
||||
mode,
|
||||
duplicateErrors,
|
||||
positionNameDuplicates: positionNameDups.map((item) => ({
|
||||
value: item.value,
|
||||
count: item.count,
|
||||
})),
|
||||
usernameDuplicates: usernameDups.map((item) => ({
|
||||
value: item.value,
|
||||
count: item.count,
|
||||
usernames: item.rows.map((row) => row.Username),
|
||||
})),
|
||||
emailDuplicates: emailDups.map((item) => ({
|
||||
value: item.value,
|
||||
count: item.count,
|
||||
usernames: item.rows.map((row) => row.Username),
|
||||
})),
|
||||
phoneDuplicates: phoneDups.map((item) => ({
|
||||
value: item.value,
|
||||
count: item.count,
|
||||
usernames: item.rows.map((row) => row.Username),
|
||||
})),
|
||||
selfReferencingReportsTo: invalidReportsTo.map((row) => row.Position),
|
||||
usersWithUnknownPosition: missingPositionRefs.map((row) => ({
|
||||
username: row.Username,
|
||||
position: row.Position,
|
||||
})),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user