1
0
forked from baron/baron-sso

feat: update worksmobile sync and restore planning

This commit is contained in:
2026-06-01 17:01:53 +09:00
parent 6574fb54b9
commit 5c8a338085
36 changed files with 3922 additions and 243 deletions

View File

@@ -95,6 +95,7 @@ import { resolvePersonalTenant } from "./utils/personalTenant";
type UserFormValues = Omit<UserUpdateRequest, "metadata"> & {
email: string;
metadata: Record<string, unknown> & {
employee_id?: string;
sub_email?: string | string[];
};
};
@@ -130,6 +131,29 @@ function cleanMetadataValue(value: unknown): unknown {
return value;
}
function normalizeEmployeeIDMetadataValue(value: unknown) {
if (typeof value === "string" || typeof value === "number") {
return String(value).trim();
}
if (!isMetadataRecord(value)) {
return "";
}
const entries = Object.entries(value)
.map(([key, fieldValue]) => ({
index: Number(key),
value: typeof fieldValue === "string" ? fieldValue : "",
}))
.filter((entry) => Number.isInteger(entry.index) && entry.value.length > 0)
.sort((a, b) => a.index - b.index);
if (entries.length === 0) {
return "";
}
return entries
.map((entry) => entry.value)
.join("")
.trim();
}
function normalizeSubEmails(value: unknown): string[] {
if (Array.isArray(value)) {
return value
@@ -699,6 +723,9 @@ function UserDetailPage() {
string,
Record<string, string | number | boolean>
>) || {}),
employee_id: normalizeEmployeeIDMetadataValue(
user.metadata?.employee_id,
),
sub_email: Array.isArray(user.metadata?.sub_email)
? user.metadata.sub_email
: typeof user.metadata?.sub_email === "string"
@@ -837,15 +864,22 @@ function UserDetailPage() {
...safeMetadata,
...(subEmail.length > 0 ? { sub_email: subEmail } : { sub_email: [] }),
};
const employeeID = String(data.metadata?.employee_id ?? "").trim();
if (employeeID) {
metadata.employee_id = employeeID;
} else {
delete metadata.employee_id;
}
const payload: UserUpdateRequest = {
...data,
metadata,
};
// email cannot be updated directly via this API in current backend implementation,
// so we delete it from payload if it spread
// @ts-expect-error
delete payload.email;
if (profileRole !== "super_admin") {
delete payload.email;
} else {
payload.email = data.email.trim();
}
payload.role = undefined;
if (userCategory === "personal") {
@@ -1107,9 +1141,19 @@ function UserDetailPage() {
</Label>
<Input
id="email"
value={user.email}
disabled
className="bg-muted/50 border-none font-medium h-11"
type="email"
disabled={profileRole !== "super_admin"}
{...register("email", {
required: t(
"msg.admin.users.detail.email_required",
"이메일을 입력하세요.",
),
})}
className={
profileRole === "super_admin"
? "h-11 shadow-sm"
: "bg-muted/50 border-none font-medium h-11"
}
/>
</div>
<div className="space-y-2">
@@ -1146,6 +1190,37 @@ function UserDetailPage() {
className="h-11 shadow-sm"
/>
</div>
<div className="space-y-2">
<Label
htmlFor="metadata_employee_id"
className="text-xs font-bold uppercase text-muted-foreground"
>
</Label>
<Input
id="metadata_employee_id"
maxLength={20}
{...register("metadata.employee_id", {
setValueAs: (value) =>
typeof value === "string" ? value.trim() : value,
maxLength: {
value: 20,
message:
"Worksmobile 사번은 20자 이하로 입력해야 합니다.",
},
})}
className="h-11 shadow-sm"
/>
{errors.metadata?.employee_id && (
<p className="text-xs text-destructive">
{String(errors.metadata.employee_id.message)}
</p>
)}
<p className="text-[10px] text-muted-foreground mt-1">
Worksmobile employeeNumber로 . 1~20
.
</p>
</div>
</div>
<div className="grid gap-8 md:grid-cols-2 pt-6 border-t border-dashed">