import * as React from "react";
import {
Modal,
Form,
Input,
Button,
message,
Row,
Col,
Spin,
DatePicker,
Card,
Space,
Divider,
} from "antd";
import { PlusOutlined } from "@ant-design/icons";
import { useEmployeeSkillsDirectoryStore } from "../EmployeeSkillsDirectoryStore";
import { User } from "@microsoft/microsoft-graph-types";
import { EnhancedUser } from "../../../services/MSGraphService";
import { globalContext } from "../../../common/GlobalContext";
import CropModal from "./CropModal";
import dayjs from "dayjs";
import {
SkillEntry,
ExperienceEntry,
EducationEntry,
} from "../../../services/SPServices/DAO/StaffListDAO";
import { SkillDefinition } from "../../../services/SPServices/DAO/SkillsListDAO";
import PhotoEditor from "./EditUser/PhotoEditor";
import TagEditor from "./EditUser/TagEditor";
import SkillsSection from "./EditUser/SkillsSection";
import ExperienceSection from "./EditUser/ExperienceSection";
import EducationSection from "./EditUser/EducationSection";
const { TextArea } = Input;
type Props = {
isVisible: boolean;
selectedUser: EnhancedUser | null;
onClose: () => void;
};
export default function EditUserModal({
isVisible,
selectedUser,
onClose,
}: Props) {
const [form] = Form.useForm();
const skillsSectionRef = React.useRef(null);
const experienceSectionRef = React.useRef(null);
const educationSectionRef = React.useRef(null);
const {
updateUser,
getUserDetails,
getCurrentUserPhotoBlob,
updateCurrentUserPhoto,
deleteCurrentUserPhoto,
getSkillsDictionary,
getStaffItemById,
saveProfile,
} = useEmployeeSkillsDirectoryStore();
const { spfxContext } = React.useContext(globalContext);
const [loading, setLoading] = React.useState(false);
const [detailsLoading, setDetailsLoading] = React.useState(false);
const [photoLoading, setPhotoLoading] = React.useState(false);
const [skillsLoading, setSkillsLoading] = React.useState(false);
const [availableSkills, setAvailableSkills] = React.useState<
SkillDefinition[]
>([]);
const [userSkills, setUserSkills] = React.useState([]);
const [experience, setExperience] = React.useState([]);
const [education, setEducation] = React.useState([]);
const [interests, setInterests] = React.useState([]);
const [projects, setProjects] = React.useState([]);
const [schools, setSchools] = React.useState([]);
const [skills, setSkills] = React.useState([]);
const [userEmail, setUserEmail] = React.useState("");
const [imageFile, setImageFile] = React.useState(null);
const [currentPhotoUrl, setCurrentPhotoUrl] = React.useState(
null
);
const [isCropModalOpen, setIsCropModalOpen] = React.useState(false);
const [tempImageUrl, setTempImageUrl] = React.useState(null);
const [isAdmin, setIsAdmin] = React.useState(false);
const context = React.useContext(globalContext);
const canEdit = React.useMemo(() => {
if (!selectedUser) return false;
const currentUserEmail = (spfxContext.pageContext.user.email || "").toLowerCase();
const selMail = (selectedUser.mail || selectedUser.userPrincipalName || "").toLowerCase();
const isOwner = selMail && currentUserEmail && (selMail === currentUserEmail);
return isOwner || isAdmin;
}, [selectedUser, spfxContext, isAdmin]);
const uniq = (arr: readonly string[]) =>
Array.from(new Set(arr.map((x) => (x ?? "").trim()).filter(Boolean)));
const titlesFromUserSkills = (items: readonly SkillEntry[]) =>
items.map((s) => s.title || "").filter((t) => t.trim().length > 0);
const schoolsFromEducation = (items: readonly EducationEntry[]) =>
items.map((e) => e.institution || "").filter((t) => t.trim().length > 0);
React.useEffect(() => {
if (!selectedUser || !isVisible) return;
let mounted = true;
(async () => {
try {
setDetailsLoading(true);
setSkillsLoading(true);
// 1. Skills dictionary
const dict = await getSkillsDictionary();
if (!mounted) return;
setAvailableSkills(dict || []);
setSkillsLoading(false);
// 2. User details
const details = await getUserDetails(selectedUser.id!);
if (!mounted) return;
form.setFieldsValue({
aboutMe: details.aboutMe || "",
birthday: details.birthday ? dayjs(details.birthday) : null,
mobilePhone: details.mobilePhone || "",
businessPhone: details.businessPhones?.[0] || "",
jobTitle: details.jobTitle || "",
companyName: details.companyName || "",
department: details.department || "",
city: details.city || "",
});
setInterests(details.interests || []);
setSkills(uniq(details.skills || []));
setSchools(uniq(details.schools || []));
setCurrentPhotoUrl(details.photoUrl);
setUserEmail(details.userPrincipalName || details.mail || "");
const staff = await getStaffItemById(details.id!);
if (!mounted) return;
if (staff) {
setUserSkills(staff.Skills || []);
setExperience(staff.Experience || []);
setEducation(staff.Education || []);
setProjects(uniq(staff.Projects || []));
}
console.log('details', details);
try {
const adminsRes: any = await (context as any).sp.adminDAO.getAdmins();
if (!mounted) return;
const adminsArr: any[] = Array.isArray(adminsRes)
? adminsRes
: Array.isArray(adminsRes?.PromiseResult)
? adminsRes.PromiseResult
: [];
const currentEmail = (spfxContext.pageContext.user.email || "").toLowerCase();
const currentId = (spfxContext.pageContext.user.loginName || "") || null;
const found = adminsArr.some((a: any) => {
const adminEmail = (a.AdminUserEmail ?? a.Title ?? "").toLowerCase();
const adminId = a.AdminUserId ?? a.Id ?? null;
if (adminEmail && currentEmail && adminEmail === currentEmail) return true;
if (adminId && currentId && String(adminId) === String(currentId)) return true;
return false;
});
if (!mounted) return;
setIsAdmin(Boolean(found));
} catch (admErr) {
console.error("Failed to load admins", admErr);
if (mounted) setIsAdmin(false);
}
// 5. Photo: якщо власник (canEdit за власником) — підвантажуємо поточне фото користувача,
// якщо адмін і редагує чужий профіль — намагаємось отримати фото selectedUser (якщо API підтримує)
if (canEdit) {
setPhotoLoading(true);
const url = await getCurrentUserPhotoBlob();
if (mounted) setCurrentPhotoUrl(url);
} else if (isAdmin) {
// пробуємо отримати фото для selectedUser через getUserDetails response або інший API
// якщо у вас є метод отримати фото за id/email для будь-якого користувача — викличте його тут.
// Інакше залишаємо photoUrl з details (встановлено вище).
try {
setPhotoLoading(true);
// приклад: const url = await getPhotoForUser(selectedUser.id || selectedUser.userPrincipalName);
// якщо такого методу немає — пропускаємо
// const url = await getPhotoForUser(selectedUser.id || selectedUser.userPrincipalName || "");
// if (mounted && url) setCurrentPhotoUrl(url);
} catch (photoErr) {
console.warn("Could not load photo for selected user", photoErr);
} finally {
if (mounted) setPhotoLoading(false);
}
}
} catch (e) {
console.error(e);
message.error("Не вдалося завантажити дані користувача");
} finally {
if (mounted) {
setDetailsLoading(false);
setPhotoLoading(false);
setSkillsLoading(false);
}
}
})();
return () => {
mounted = false;
};
}, [selectedUser, isVisible, canEdit, getSkillsDictionary, getUserDetails, getStaffItemById, getCurrentUserPhotoBlob, spfxContext, context]);
const syncGraphSkills = async (nextUserSkills: SkillEntry[] = userSkills) => {
const merged = uniq(titlesFromUserSkills(nextUserSkills));
setSkills(merged);
await updateUser(selectedUser?.id || "", {
skills: merged,
} as Partial);
};
const syncGraphSchools = async (
nextEducation: EducationEntry[] = education
) => {
const merged = uniq(schoolsFromEducation(nextEducation));
setSchools(merged);
await updateUser(selectedUser?.id || "", {
schools: merged,
} as Partial);
};
const syncGraphProjects = async (nextProjects: string[] = projects) => {
const merged = uniq(nextProjects);
await updateUser(selectedUser?.id || "", {
pastProjects: merged,
} as Partial);
};
const handleSubmit = async () => {
try {
setLoading(true);
const v = await form.validateFields();
// Створюємо об'єкт лише з заповненими полями
const profileData: any = {
userId: selectedUser?.id || selectedUser?.userPrincipalName || "",
email: userEmail,
interests,
projects,
staffSkills: userSkills,
education,
experience,
};
// Додаємо лише заповнені поля
if (v.aboutMe?.trim()) profileData.aboutMe = v.aboutMe.trim();
if (v.birthday) profileData.birthday = v.birthday.format("YYYY-MM-DD");
if (v.mobilePhone?.trim()) profileData.mobilePhone = v.mobilePhone.trim();
if (v.businessPhone?.trim()) profileData.businessPhone = v.businessPhone.trim();
if (v.jobTitle?.trim()) profileData.jobTitle = v.jobTitle.trim();
if (v.companyName?.trim()) profileData.companyName = v.companyName.trim();
if (v.department?.trim()) profileData.department = v.department.trim();
if (v.city?.trim()) profileData.city = v.city.trim();
await saveProfile(profileData);
if (imageFile && canEdit) {
await updateCurrentUserPhoto(imageFile);
}
message.success("Profile updated!");
onClose();
} catch (e) {
console.error(e);
message.error("Error updating profile!");
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
setSkills([]);
setSchools([]);
setInterests([]);
setProjects([]);
setUserSkills([]);
setExperience([]);
setEducation([]);
setImageFile(null);
setCurrentPhotoUrl(null);
onClose();
};
const formatDate = (s: string) => dayjs(s).format("MMM YYYY");
const cardStyle = {
marginBottom: 4,
borderRadius: 8,
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
};
const sectionHeaderStyle = {
backgroundColor: "#fafafa",
borderRadius: "8px 8px 0 0",
padding: "8px",
margin: "-1px -1px 16px -1px",
borderBottom: "1px solid #f0f0f0",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
};
return (
{canEdit ? "Edit your profile" : "View Profile"}
}
open={isVisible}
onCancel={handleCancel}
footer={
canEdit
? [
,
]
}
width={1400}
destroyOnHidden
>