Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
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 ? [
Cancel
,
Save Changes
, ] : [
Close
, ] } width={1400} destroyOnHidden >
{ setTempImageUrl(currentPhotoUrl); setIsCropModalOpen(true); }} />
Basic Information
Interests & Projects
{ setProjects(n); syncGraphProjects(n); }} canEdit={canEdit} placeholder="Add a project..." />
Professional Skills
{canEdit && (
} onClick={() => skillsSectionRef.current?.openAddModal?.()} disabled={!availableSkills.length} size="small" > Add Skill )}
Work Experience
{canEdit && (
} onClick={() => experienceSectionRef.current?.openAddModal?.() } size="small" > Add Experience )}
Education
{canEdit && (
} onClick={() => educationSectionRef.current?.openAddModal?.() } size="small" > Add Education )}
{ setEducation(n); syncGraphSchools(n); }} canEdit={canEdit} formatDate={formatDate} showAddButton={false} />
setIsCropModalOpen(false)} onApply={(croppedUrl, file) => { setCurrentPhotoUrl(croppedUrl); setImageFile(file); setIsCropModalOpen(false); }} onDelete={async () => { try { if (canEdit) await deleteCurrentUserPhoto(); setCurrentPhotoUrl(null); setImageFile(null); setIsCropModalOpen(false); message.success("Photo deleted!"); } catch { message.error("Error deleting photo!"); } }} /> ); }
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)