user management ui

This commit is contained in:
yaschalew
2026-07-10 10:41:48 +03:00
parent dcb2d98503
commit 28a20923ff
595 changed files with 0 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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,
})),
});
}