From a3e66081e9e5e20615b27e1c0bbb85fa04bdcd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Thu, 28 May 2026 01:30:13 +0300 Subject: [PATCH] =?UTF-8?q?Profile=20ve=20UserDetail=20komponentleri=20d?= =?UTF-8?q?=C3=BCzenlendi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/PlatformIdentityAppService.cs | 3 +- ui/public/version.json | 2 +- ui/src/components/template/UserDropdown.tsx | 39 +- ui/src/services/platformApi.service.ts | 1 + ui/src/store/auth.model.ts | 5 +- ui/src/utils/hooks/useAuth.ts | 2 + ui/src/views/admin/profile/Profile.tsx | 8 + .../admin/profile/components/General.tsx | 367 +++++++++----- .../views/admin/user-management/Details.tsx | 460 +++++++++++++----- 9 files changed, 627 insertions(+), 260 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs index 3751490..80dd656 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs @@ -133,6 +133,7 @@ public class PlatformIdentityAppService : ApplicationService return new UserInfoViewModel() { Id = user.Id, + TenantId = user.TenantId, UserName = user.UserName, Name = user.Name, Surname = user.Surname, @@ -219,6 +220,7 @@ public class PlatformIdentityAppService : ApplicationService user.SetLoginEndDate(UserInfo.LoginEndDate); user.SetWorkHour(UserInfo.WorkHour); user.SetIsActive(UserInfo.IsActive); + user.SetLastPasswordChangeTime(UserInfo.LastPasswordChangeTime); user.SetEmailConfirmed(UserInfo.EmailConfirmed); user.SetPhoneNumberConfirmed(UserInfo.PhoneNumberConfirmed); @@ -239,7 +241,6 @@ public class PlatformIdentityAppService : ApplicationService user.Name = UserInfo.Name; user.Surname = UserInfo.Surname; user.SetPhoneNumber(UserInfo.PhoneNumber, user.PhoneNumberConfirmed); - user.SetLastPasswordChangeTime(UserInfo.LastPasswordChangeTime); user.SetRocketUsername(UserInfo.RocketUsername); user.SetWorkHour(UserInfo.WorkHour); user.SetDepartmentId(UserInfo.DepartmentId); diff --git a/ui/public/version.json b/ui/public/version.json index b69fd0c..8cd93a7 100644 --- a/ui/public/version.json +++ b/ui/public/version.json @@ -115,4 +115,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/ui/src/components/template/UserDropdown.tsx b/ui/src/components/template/UserDropdown.tsx index fbeb587..06541ff 100644 --- a/ui/src/components/template/UserDropdown.tsx +++ b/ui/src/components/template/UserDropdown.tsx @@ -22,6 +22,8 @@ const _UserDropdown = ({ className }: CommonProps) => { const tenant = useStoreState((state) => state.abpConfig.config?.currentTenant) const { translate } = useLocalization() const { signOut } = useAuth() + const displayName = name || userName + const tenantName = tenant?.name const dropdownItemList: DropdownList[] = [ { @@ -42,12 +44,20 @@ const _UserDropdown = ({ className }: CommonProps) => { ] const UserAvatar = ( -
- -
-
{userName}
-
{name}
-
{tenant?.name}
+
+ +
+ + {userName} + + + {displayName} + + {tenantName && ( + + {tenantName} + + )}
) @@ -55,11 +65,18 @@ const _UserDropdown = ({ className }: CommonProps) => { return ( -
- -
-
{name}
-
{email}
+
+ +
+
+ {displayName} +
+
{email}
+ {tenantName && ( +
+ {tenantName} +
+ )}
diff --git a/ui/src/services/platformApi.service.ts b/ui/src/services/platformApi.service.ts index 2ab5520..50d3de7 100644 --- a/ui/src/services/platformApi.service.ts +++ b/ui/src/services/platformApi.service.ts @@ -95,6 +95,7 @@ platformApiService.interceptors.response.use( authority: [tokenDetails?.role], name: `${tokenDetails?.given_name} ${tokenDetails?.family_name}`.trim(), role: 'teacher', + tenantId: tokenDetails?.tenantid, }, }) setIsRefreshing(false) diff --git a/ui/src/store/auth.model.ts b/ui/src/store/auth.model.ts index c37c7b4..00fd1f7 100644 --- a/ui/src/store/auth.model.ts +++ b/ui/src/store/auth.model.ts @@ -21,6 +21,7 @@ export interface AuthStoreModel { name: string avatar?: string role: string + tenantId?: string } tenant?: { tenantId?: string @@ -59,6 +60,7 @@ export const initialState: AuthStoreModel = { name: '', avatar: '', role: 'teacher', + tenantId: '', }, tenant: { tenantId: '', @@ -82,7 +84,8 @@ export const authModel: AuthModel = { state.user.userName = payload.user.userName state.user.authority = payload.user.authority state.user.email = payload.user.email - state.user.avatar = AVATAR_URL(payload.user.id, state.tenant?.tenantId) + `?${dayjs().unix()}` + state.user.tenantId = payload.user.tenantId + state.user.avatar = AVATAR_URL(payload.user.id, state.user?.tenantId) + `?${dayjs().unix()}` }), signOut: action(() => ({ ...initialState })), // signOut: action((state) => ({ ...initialState, tenantId: state.tenant?.tenantId })), diff --git a/ui/src/utils/hooks/useAuth.ts b/ui/src/utils/hooks/useAuth.ts index daae2ca..ea6adc9 100644 --- a/ui/src/utils/hooks/useAuth.ts +++ b/ui/src/utils/hooks/useAuth.ts @@ -35,6 +35,7 @@ function useAuth() { expiresIn: number }) => { const tokenDetails: any = jwtDecode(token) + signInStore({ session: { token, refreshToken, expiresIn, expire: tokenDetails?.exp, signedIn: true }, user: { @@ -44,6 +45,7 @@ function useAuth() { authority: [tokenDetails?.role], name: `${tokenDetails?.given_name} ${tokenDetails?.family_name}`.trim(), role: 'teacher', + tenantId: tokenDetails?.tenantid, }, }) } diff --git a/ui/src/views/admin/profile/Profile.tsx b/ui/src/views/admin/profile/Profile.tsx index 900e2e2..e5852e2 100644 --- a/ui/src/views/admin/profile/Profile.tsx +++ b/ui/src/views/admin/profile/Profile.tsx @@ -5,6 +5,7 @@ import { APP_NAME } from '@/constants/app.constant' import { useLocalization } from '@/utils/hooks/useLocalization' import { Suspense, lazy, useState } from 'react' import { Helmet } from 'react-helmet' +import { FaCheckCircle, FaCompressAlt, FaNode, FaUser } from 'react-icons/fa' import { useLocation, useNavigate } from 'react-router-dom' type AccountSetting = { @@ -35,19 +36,23 @@ const Profile = () => { { label: string path: string + icon?: JSX.Element } > = { general: { label: translate('::Abp.Identity.Profile.General'), path: 'general', + icon: , }, password: { label: translate('::Abp.Identity.Password'), path: 'password', + icon: , }, notificationSettings: { label: translate('::Abp.Identity.NotificationSettings'), path: 'notification-settings', + icon: , }, } @@ -78,6 +83,9 @@ const Profile = () => { {Object.keys(settingsMenu).map((key) => ( + {settingsMenu[key].icon && ( + {settingsMenu[key].icon} + )} {settingsMenu[key].label} ))} diff --git a/ui/src/views/admin/profile/components/General.tsx b/ui/src/views/admin/profile/components/General.tsx index 982e3a2..a8e276b 100644 --- a/ui/src/views/admin/profile/components/General.tsx +++ b/ui/src/views/admin/profile/components/General.tsx @@ -1,13 +1,12 @@ -import { Input, Upload } from '@/components/ui' +import { Input, Select, Upload } from '@/components/ui' import Button from '@/components/ui/Button' -import { FormContainer } from '@/components/ui/Form' +import { FormContainer, FormItem } from '@/components/ui/Form' import Notification from '@/components/ui/Notification' import toast from '@/components/ui/toast' import { AVATAR_URL } from '@/constants/app.constant' import { useStoreActions, useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' import dayjs from 'dayjs' -import type { FieldProps } from 'formik' import { Field, Form, Formik } from 'formik' import { useEffect, useRef, useState } from 'react' @@ -26,13 +25,15 @@ import { FaUserCircle, FaPhone, FaPlus, + FaHome, + FaUniversity, } from 'react-icons/fa' import * as Yup from 'yup' import isEmpty from 'lodash/isEmpty' -import FormRow from '@/views/shared/FormRow' -import FormDesription from '@/views/shared/FormDesription' import { ProfileDto, UpdateProfileDto } from '@/proxy/account/models' import { getProfile, updateProfile } from '@/services/account.service' +import { CountryDto, getCountry } from '@/services/home.service' +import { SelectBoxOption } from '@/types/shared' const schema = Yup.object().shape({ name: Yup.string().min(3).max(50).required(), @@ -44,6 +45,7 @@ const General = () => { const [profileData, setProfileData] = useState() const [formData, setFormData] = useState() const [image, setImage] = useState() + const [countries, setCountries] = useState([]) const auth = useStoreState((state) => state.auth) const { setUser } = useStoreActions((actions) => actions.auth.user) @@ -54,8 +56,9 @@ const General = () => { const fetchData = async () => { setLoading(true) - const response = await getProfile() + const [response, countryResponse] = await Promise.all([getProfile(), getCountry()]) setProfileData(response.data) + setCountries(countryResponse.data) setImage(auth.user.avatar) setFormData({ name: response.data.name, @@ -150,6 +153,69 @@ const General = () => { } }) + const getProfileExtraValue = (key: string) => { + const extraProperties = profileData?.extraProperties + const value = + extraProperties?.[key] ?? extraProperties?.[`${key.charAt(0).toLowerCase()}${key.slice(1)}`] + + return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined + } + + const getSelectValue = (options: SelectBoxOption[], value?: string) => { + if (!value) { + return null + } + + return options.find((option) => option.value === value) ?? { value, label: value } + } + + const nationalityOptions: SelectBoxOption[] = countries.map((country) => ({ + value: country.name, + label: country.name, + })) + + const educationOptions: SelectBoxOption[] = [ + { + value: 'İlkokul', + label: translate('::App.EducationLevel.Primary') || 'İlkokul', + }, + { + value: 'Ortaokul', + label: translate('::App.EducationLevel.MiddleSchool') || 'Ortaokul', + }, + { + value: 'Lise', + label: translate('::App.EducationLevel.HighSchool') || 'Lise', + }, + { + value: 'Ön Lisans', + label: translate('::App.EducationLevel.Associate') || 'Ön Lisans', + }, + { + value: 'Lisans', + label: translate('::App.EducationLevel.Bachelor') || 'Lisans', + }, + { + value: 'Yüksek Lisans', + label: translate('::App.EducationLevel.Master') || 'Yüksek Lisans', + }, + { + value: 'Doktora', + label: translate('::App.EducationLevel.PhD') || 'Doktora', + }, + ] + + const bloodTypeOptions: SelectBoxOption[] = [ + { value: 'A Rh+', label: 'A Rh+' }, + { value: 'A Rh-', label: 'A Rh-' }, + { value: 'B Rh+', label: 'B Rh+' }, + { value: 'B Rh-', label: 'B Rh-' }, + { value: 'AB Rh+', label: 'AB Rh+' }, + { value: 'AB Rh-', label: 'AB Rh-' }, + { value: '0 Rh+', label: '0 Rh+' }, + { value: '0 Rh-', label: '0 Rh-' }, + ] + if (loading) { return <> } @@ -165,134 +231,173 @@ const General = () => { }} > {({ touched, errors, isSubmitting, resetForm }) => { - const validatorProps = { touched, errors } return (
- - - - } - value={profileData?.email} - > - - - } - value={profileData?.phoneNumber} - > - - - } - value={profileData?.extraProperties?.['RocketUsername'] as string | undefined} - > - - - } - /> - - - } - /> - - - - {({ field, form }: FieldProps) => { - return ( - <> -
-
- {image ? ( - { - setTimeout(() => { - previewRef.current?.update(cropper) - }, 100) - }} - className="cropper max-h-[300px] max-w-[300px]" - stencilComponent={CircleStencil} - minHeight={100} - minWidth={100} - /> - ) : ( - - )} -
-
- - - - -
- {image && ( - - )} -
-
+ +
+
+
+ {translate('::Abp.Identity.User.UserInformation.ContactInformation')} +
+ + + } + value={profileData?.email} + > + + + } + /> + + + } + /> + + + } + value={profileData?.phoneNumber} + > + + + } + value={profileData?.extraProperties?.['RocketUsername'] as string | undefined} + > + +
+ +
+
+ {translate('::Abp.Identity.User.UserInformation.AdditionalInformation')} +
+ + + } + value={getProfileExtraValue('HomeAddress')} + > + + + + isDisabled + options={nationalityOptions} + value={getSelectValue( + nationalityOptions, + getProfileExtraValue('Nationality'), + )} + /> + + + + isDisabled + options={educationOptions} + value={getSelectValue( + educationOptions, + getProfileExtraValue('EducationLevel'), + )} + /> + + + } + value={getProfileExtraValue('GraduationSchool')} + > + + + + isDisabled + options={bloodTypeOptions} + value={getSelectValue(bloodTypeOptions, getProfileExtraValue('BloodType'))} + /> + +
+ +
+
Avatar
+ + +
+ {image ? ( + { + setTimeout(() => { + previewRef.current?.update(cropper) + }, 100) + }} + className="cropper max-h-[300px] max-w-[300px]" + stencilComponent={CircleStencil} + minHeight={100} + minWidth={100} + /> + ) : ( + + )} +
+
+ + + +
- - ) - }} - - + {image && ( + + )} +
+
+
+
+
+ + +
+ {image && ( + + )} +
+
- +
+ +
) }} @@ -449,7 +569,7 @@ function UserDetails() { -
+
{ @@ -547,8 +667,8 @@ function UserDetails() {
-
-
@@ -560,7 +680,7 @@ function UserDetails() { -
+
{ @@ -601,7 +721,7 @@ function UserDetails() {
-
+
} />
@@ -683,6 +804,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.UserInformation.HireDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -710,6 +832,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.UserInformation.TerminationDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -725,9 +848,11 @@ function UserDetails() {
- +
+ +
) }} @@ -736,7 +861,7 @@ function UserDetails() { -
+
{ @@ -779,7 +904,7 @@ function UserDetails() { return (
-
+
} />
@@ -808,6 +934,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.SerialNo', )} component={Input} + prefix={} />
@@ -823,6 +950,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.Province', )} component={Input} + prefix={} />
@@ -838,6 +966,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.District', )} component={Input} + prefix={} />
@@ -853,6 +982,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.Village', )} component={Input} + prefix={} />
@@ -868,6 +998,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.VolumeNo', )} component={Input} + prefix={} />
@@ -885,6 +1016,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.FamilySequenceNo', )} component={Input} + prefix={} />
@@ -900,6 +1032,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.SequenceNo', )} component={Input} + prefix={} />
@@ -915,6 +1048,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.IssuedPlace', )} component={Input} + prefix={} />
@@ -932,6 +1066,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.UserInformation.IssuedDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -955,6 +1090,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.BirthPlace', )} component={Input} + prefix={} />
@@ -972,6 +1108,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.UserInformation.BirthDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -995,6 +1132,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.FatherName', )} component={Input} + prefix={} /> @@ -1010,6 +1148,7 @@ function UserDetails() { '::Abp.Identity.User.UserInformation.MotherName', )} component={Input} + prefix={} /> @@ -1050,6 +1189,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.UserInformation.MarriageDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -1064,9 +1204,11 @@ function UserDetails() { - +
+ +
) }} @@ -1075,7 +1217,7 @@ function UserDetails() {
-
+
{ @@ -1108,7 +1250,7 @@ function UserDetails() { return (
-
+
{/* Account Status */}
@@ -1258,6 +1400,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.LockoutManagement.AccountEndDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -1295,7 +1438,12 @@ function UserDetails() { '::Abp.Identity.User.LockoutManagement.AccessFailedCount', )} > - + } + />
@@ -1346,6 +1494,7 @@ function UserDetails() { placeholder={translate( '::Abp.Identity.User.LockoutManagement.LoginEndDate', )} + inputPrefix={} onChange={(date) => { form.setFieldValue( field.name, @@ -1356,13 +1505,88 @@ function UserDetails() { )} + + + + {({ field, form }: FieldProps) => ( + } + onChange={(date: any) => { + form.setFieldValue( + field.name, + date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null, + ) + }} + /> + )} + + + + + + {({ field, form }: FieldProps) => ( + } + /> + )} + + + + + {({ field, form }: FieldProps) => ( + } + /> + )} + +
- +
+ +
) }} @@ -1371,7 +1595,7 @@ function UserDetails() { -
+
@@ -1424,7 +1648,7 @@ function UserDetails() { - + setOpen(false)} onRequestClose={() => setOpen(false)}> - + } + />