sozsoft-platform/ui/src/views/admin/user-management/Details.tsx

1762 lines
77 KiB
TypeScript
Raw Normal View History

2026-02-24 20:44:16 +00:00
import {
Button,
Checkbox,
DatePicker,
FormContainer,
FormItem,
Input,
Notification,
Select,
Tabs,
toast,
Upload,
2026-02-24 20:44:16 +00:00
} from '@/components/ui'
import Dialog from '@/components/ui/Dialog'
import DateTimepicker from '@/components/ui/DatePicker/DateTimepicker'
import Table from '@/components/ui/Table'
import TBody from '@/components/ui/Table/TBody'
import Td from '@/components/ui/Table/Td'
import Th from '@/components/ui/Table/Th'
import THead from '@/components/ui/Table/THead'
import Tr from '@/components/ui/Table/Tr'
import TabContent from '@/components/ui/Tabs/TabContent'
import TabList from '@/components/ui/Tabs/TabList'
import TabNav from '@/components/ui/Tabs/TabNav'
import {
deleteClaimUser,
getUserDetail,
postClaimUser,
putUserDetail,
putUserLookout,
putUserPermission,
2026-02-24 20:44:16 +00:00
} from '@/services/identity.service'
import { updateProfile } from '@/services/account.service'
import { CountryDto, getCountry } from '@/services/home.service'
2026-02-24 20:44:16 +00:00
import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs'
import { Field, FieldArray, FieldProps, Form, Formik, FormikHelpers } from 'formik'
import { useEffect, useRef, useState } from 'react'
2026-02-24 20:44:16 +00:00
import { Helmet } from 'react-helmet'
import {
FaBuilding,
FaLockOpen,
FaUser,
FaFileAlt,
FaTrashAlt,
FaCheckCircle,
FaUserAstronaut,
FaEnvelope,
FaPhone,
FaUserCircle,
FaFacebookMessenger,
FaHome,
FaUniversity,
FaCalendarAlt,
FaBriefcase,
FaIdCard,
FaMapMarkerAlt,
FaCity,
FaBook,
FaHashtag,
FaMapPin,
FaUserTie,
FaFemale,
FaHeart,
FaExclamationTriangle,
FaUsers,
FaPlus,
} from 'react-icons/fa'
import {
CircleStencil,
Cropper,
CropperPreview,
CropperPreviewRef,
CropperRef,
} from 'react-advanced-cropper'
import 'react-advanced-cropper/dist/style.css'
2026-02-24 20:44:16 +00:00
import { useParams } from 'react-router-dom'
import * as Yup from 'yup'
import { SelectBoxOption, SelectBoxOptionWithGroup } from '@/types/shared'
import { AdaptableCard, ConfirmDialog, Container } from '@/components/shared'
2026-02-24 20:44:16 +00:00
import { AssignedClaimViewModel, UserInfoViewModel } from '@/proxy/admin/models'
import { APP_NAME, AVATAR_URL } from '@/constants/app.constant'
import { useStoreActions, useStoreState } from '@/store'
import { useSetting } from '@/utils/hooks/useSetting'
2026-02-24 20:44:16 +00:00
export interface ClaimTypeDto {
claimType: string
claimValue: string
}
function UserDetails() {
const { userId } = useParams()
const { translate } = useLocalization()
const [userDetails, setUserDetails] = useState<UserInfoViewModel>()
const [loading, setLoading] = useState(true)
const [open, setOpen] = useState(false)
const [confirmDeleteClaim, setConfirmDeleteClaim] = useState<AssignedClaimViewModel | null>(null)
const [countries, setCountries] = useState<CountryDto[]>([])
const [image, setImage] = useState<string | undefined>()
const cropperRef = useRef<CropperRef>(null)
const previewRef = useRef<CropperPreviewRef>(null)
const auth = useStoreState((state) => state.auth)
2026-06-03 21:38:21 +00:00
const { setUser } = useStoreActions((actions) => actions.auth.user)
const { setting } = useSetting()
2026-06-03 21:38:21 +00:00
const isEmailUpdateEnabled = setting('Abp.Identity.User.IsEmailUpdateEnabled')
2026-06-03 21:38:21 +00:00
const isRequireVerifiedAccount = setting('Abp.Identity.Profile.General.RequireVerifiedAccount')
const isRequireConfirmedEmail = setting('Abp.Identity.SignIn.RequireConfirmedEmail')
const isRequireConfirmedPhoneNumber = setting('Abp.Identity.SignIn.RequireConfirmedPhoneNumber')
const isTwoFactorEnabled = setting('Abp.Account.TwoFactor.Enabled')
2026-02-24 20:44:16 +00:00
const getUser = async (syncAvatar = true) => {
2026-02-24 20:44:16 +00:00
const { data } = await getUserDetail(userId || '')
setUserDetails(data)
if (syncAvatar) {
setImage(`${AVATAR_URL(data.id, data.tenantId)}?${dayjs().unix()}`)
}
2026-02-24 20:44:16 +00:00
}
useEffect(() => {
getUser()
getCountry().then(({ data }) => setCountries(data))
2026-02-24 20:44:16 +00:00
}, [])
const scheme = Yup.object().shape({
claimType: Yup.string().required(),
claimValue: Yup.string().required(),
})
const onChooseImage = async (file: File[]) => {
if (file[0]) {
setImage(URL.createObjectURL(file[0]))
} else {
setImage(undefined)
}
}
const beforeUpload = (files: FileList | null, fileList: File[]) => {
let valid: string | boolean = true
const allowedFileType = ['image/jpeg', 'image/png', 'image/gif']
const maxFileSize = 2000000
if (fileList.length >= 1) {
return `Sadece bir dosya seçebilirsiniz`
}
if (files) {
for (const f of files) {
if (!allowedFileType.includes(f.type)) {
valid = '.jpg, .jpeg, .gif veya .png yükleyebilirsiniz'
} else if (f.size >= maxFileSize) {
valid = 'En fazla 2mb dosya yükleyebilirsiniz'
}
}
}
return valid
}
const getCroppedAvatar = () =>
new Promise<Blob | null>((resolve, reject) => {
const canvas = cropperRef.current?.getCanvas({
minHeight: 100,
minWidth: 100,
maxHeight: 300,
maxWidth: 300,
})
if (canvas) {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob)
} else {
reject()
}
}, 'image/jpeg')
} else {
resolve(null)
}
})
2026-02-24 20:44:16 +00:00
const handleSubmit = async (
values: ClaimTypeDto,
{ setSubmitting }: FormikHelpers<ClaimTypeDto>,
) => {
setLoading(true)
setSubmitting(true)
try {
await postClaimUser({ userId, claimType: values.claimType, claimValue: values.claimValue })
toast.push(
<Notification type="success" duration={2000}>
{translate('::Kaydet')}
</Notification>,
{
placement: 'top-end',
},
)
setOpen(false)
getUser()
} catch (error) {
toast.push(
<Notification type="danger" duration={2000}>
{'Hata'}
</Notification>,
{
placement: 'top-end',
},
)
} finally {
setLoading(false)
setSubmitting(false)
}
}
return userDetails ? (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={userDetails.email}
defaultTitle={APP_NAME}
></Helmet>
<AdaptableCard>
2026-02-24 20:44:16 +00:00
<Tabs defaultValue="user">
<TabList>
<TabNav value="user" icon={<FaUser className="text-sm" />}>
2026-02-24 20:44:16 +00:00
{translate('::Abp.Identity.User.UserInformation')}
</TabNav>
<TabNav value="permission" icon={<FaCheckCircle className="text-sm" />}>
{translate('::Abp.Identity.User.Permissions')}
</TabNav>
<TabNav value="work" icon={<FaBuilding className="text-sm" />}>
{translate('::Abp.Identity.User.WorkInformation')}
</TabNav>
<TabNav value="identity" icon={<FaUserAstronaut className="text-sm" />}>
{translate('::Abp.Identity.User.IndentityInformation')}
</TabNav>
<TabNav value="lockout" icon={<FaLockOpen className="text-sm" />}>
2026-02-24 20:44:16 +00:00
{translate('::Abp.Identity.User.LockoutManagement')}
</TabNav>
<TabNav value="claimTypes" icon={<FaFileAlt className="text-sm" />}>
2026-02-24 20:44:16 +00:00
{translate('::Abp.Identity.User.ClaimTypes')}
</TabNav>
</TabList>
<TabContent value="user">
<div className="px-4 py-6">
2026-02-24 20:44:16 +00:00
<Formik
initialValues={userDetails}
onSubmit={async (values, { resetForm, setSubmitting }) => {
setSubmitting(true)
await putUserDetail({ ...values })
let keepCurrentAvatar = false
const avatar = await getCroppedAvatar()
const resp = await updateProfile({
name: values.name ?? '',
surname: values.surname ?? '',
avatar: avatar ? new File([avatar], 'avatar') : undefined,
})
if (resp.status === 200) {
const avatarUrl =
AVATAR_URL(auth.user.id, auth.tenant?.tenantId) + `?${dayjs().unix()}`
setUser({
...auth.user,
name: `${resp.data.name} ${resp.data.surname}`.trim(),
avatar: avatarUrl,
})
setImage(avatarUrl)
keepCurrentAvatar = true
} else {
toast.push(<Notification title={resp?.error?.message} type="danger" />, {
placement: 'top-end',
})
}
2026-02-24 20:44:16 +00:00
toast.push(
<Notification type="success" duration={2000}>
{translate('::Kaydet')}
2026-02-24 20:44:16 +00:00
</Notification>,
{
placement: 'top-end',
},
)
getUser(!keepCurrentAvatar)
2026-02-24 20:44:16 +00:00
setSubmitting(false)
}}
>
2026-04-26 19:05:19 +00:00
{({ isSubmitting, values }) => {
2026-02-24 20:44:16 +00:00
return (
<Form>
2026-05-25 09:14:42 +00:00
<div>
2026-02-24 20:44:16 +00:00
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full">
2026-02-24 20:44:16 +00:00
{/* Personal Information */}
<div>
2026-05-25 09:14:42 +00:00
<h6 className="mb-4">
{translate(
'::Abp.Identity.User.UserInformation.ContactInformation',
)}
</h6>
<FormItem label={translate('::Abp.Account.EmailAddress')}>
<Field
type="text"
disabled={isEmailUpdateEnabled?.toLowerCase() !== 'true'}
name="email"
placeholder="Email Address"
prefix={<FaEnvelope className="text-xl" />}
component={Input}
/>
</FormItem>
2026-02-24 20:44:16 +00:00
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Name')}
>
<Field
type="text"
name="name"
placeholder="Name"
component={Input}
prefix={<FaUserCircle className="text-xl" />}
2026-02-24 20:44:16 +00:00
/>
</FormItem>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Surname')}
>
<Field
type="text"
name="surname"
placeholder="Surname"
component={Input}
prefix={<FaUserCircle className="text-xl" />}
2026-02-24 20:44:16 +00:00
/>
</FormItem>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
>
<Field
type="text"
name="phoneNumber"
2026-04-26 19:05:19 +00:00
placeholder={translate(
'::Abp.Identity.User.UserInformation.PhoneNumber',
)}
prefix={<FaPhone className="text-xl" />}
2026-02-24 20:44:16 +00:00
component={Input}
/>
</FormItem>
2026-05-25 09:14:42 +00:00
<FormItem size="sm" label={translate('::RocketUsername')}>
2026-02-24 20:44:16 +00:00
<Field
type="text"
name="rocketUsername"
placeholder={translate('::RocketUsername')}
component={Input}
prefix={<FaFacebookMessenger className="text-xl" />}
2026-02-24 20:44:16 +00:00
/>
</FormItem>
</div>
<div>
<h6 className="mb-4">
{translate(
'::Abp.Identity.User.UserInformation.AdditionalInformation',
)}
</h6>
<FormItem label={translate('::Abp.Account.HomeAddress')}>
<Field
type="text"
name="homeAddress"
placeholder={translate('::Abp.Account.HomeAddress')}
component={Input}
prefix={<FaHome className="text-xl" />}
/>
</FormItem>
<FormItem label={translate('::Abp.Account.Nationality')}>
<Field type="text" name="nationality">
{({ field, form }: FieldProps<SelectBoxOption>) => {
const nationalityOptions: SelectBoxOption[] = countries.map(
(c) => ({ value: c.name, label: c.name }),
)
return (
<Select
field={field}
form={form}
options={nationalityOptions}
isClearable={true}
value={nationalityOptions.filter(
(o) => o.value === values.nationality,
)}
onChange={(option) =>
form.setFieldValue(field.name, option?.value ?? null)
}
/>
)
}}
</Field>
</FormItem>
<FormItem label={translate('::Abp.Account.EducationLevel')}>
<Field type="text" name="educationLevel">
{({ field, form }: FieldProps<SelectBoxOption>) => {
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',
},
]
return (
<Select
field={field}
form={form}
options={educationOptions}
isClearable={true}
value={educationOptions.filter(
(o) => o.value === values.educationLevel,
)}
onChange={(option) =>
form.setFieldValue(field.name, option?.value ?? null)
}
/>
)
}}
</Field>
</FormItem>
<FormItem label={translate('::Abp.Account.GraduationSchool')}>
<Field
type="text"
name="graduationSchool"
placeholder={translate('::GraduationSchool')}
component={Input}
prefix={<FaUniversity className="text-xl" />}
/>
</FormItem>
<FormItem label={translate('::Abp.Account.BloodType')}>
<Field type="text" name="bloodType">
{({ field, form }: FieldProps<SelectBoxOption>) => {
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-' },
]
return (
<Select
field={field}
form={form}
options={bloodTypeOptions}
isClearable={true}
value={bloodTypeOptions.filter(
(o) => o.value === values.bloodType,
)}
onChange={(option) =>
form.setFieldValue(field.name, option?.value ?? null)
}
/>
)
}}
</Field>
</FormItem>
</div>
2026-02-24 20:44:16 +00:00
<div>
<h6 className="mb-4">Avatar</h6>
<FormItem>
<div className="flex flex-col gap-4">
{image ? (
<Cropper
ref={cropperRef}
src={image}
onUpdate={(cropper) => {
setTimeout(() => {
previewRef.current?.update(cropper)
}, 100)
2026-02-24 20:44:16 +00:00
}}
className="cropper max-h-[300px] max-w-[300px]"
stencilComponent={CircleStencil}
minHeight={100}
minWidth={100}
2026-02-24 20:44:16 +00:00
/>
) : (
<img
className="cropper max-h-[300px] max-w-[300px]"
src={image || AVATAR_URL(values.id, values.tenantId)}
2026-02-24 20:44:16 +00:00
/>
)}
<div className="flex items-start gap-4">
<div className="flex flex-col gap-2">
<Upload
className="cursor-pointer"
showList={false}
multiple={false}
uploadLimit={1}
beforeUpload={beforeUpload}
onChange={onChooseImage}
>
<Button icon={<FaPlus />} type="button"></Button>
</Upload>
<Button
type="button"
icon={<FaTrashAlt />}
onClick={() => setImage(undefined)}
></Button>
</div>
{image && (
<CropperPreview
ref={previewRef}
className="preview max-w-[100px] avatar-img avatar-circle"
/>
)}
</div>
</div>
2026-02-24 20:44:16 +00:00
</FormItem>
</div>
</div>
</FormContainer>
</div>
<div className="mt-4 ltr:text-right">
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
2026-02-24 20:44:16 +00:00
</Form>
)
}}
</Formik>
</div>
</TabContent>
<TabContent value="permission">
<div className="px-4 py-6">
<Formik
initialValues={userDetails}
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true)
await putUserPermission({ ...values })
toast.push(
<Notification type="success" duration={2000}>
{translate('::Abp.Identity.User.SavePermission')}
</Notification>,
{
placement: 'top-end',
},
)
getUser()
setSubmitting(false)
}}
>
{({ isSubmitting, values }) => {
const roles = values.roles
const branches = values.branches
return (
<Form>
2026-05-25 09:14:42 +00:00
<div>
<FormContainer size="md">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
{/* Şube Management */}
<div>
<h6 className="mb-4">
{translate('::Abp.Identity.User.UserInformation.BranchManagement')}
</h6>
<div className="border-2 rounded-lg p-4">
<FieldArray name="branches">
{({ form, remove, push }) => (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 text-center">
{branches && branches.length > 0
? branches.map((_, index: number) => (
<div key={index} className="p-2">
<FormItem
labelClass="block text-center"
className="mb-0 justify-center"
label={branches[index].name}
>
<Field
className="mr-0"
name={`branches[${index}].isAssigned`}
component={Checkbox}
/>
</FormItem>
</div>
))
: null}
</div>
)}
</FieldArray>
</div>
</div>
{/* Role Management */}
<div>
<h6 className="mb-4">
{translate('::Abp.Identity.User.UserInformation.RoleManagement')}
</h6>
<div className="border-2 rounded-lg p-4">
<FieldArray name="roles">
{({ form, remove, push }) => (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 text-center">
{roles && roles.length > 0
? roles.map((_, index: number) => (
<div key={index} className="p-2">
<FormItem
labelClass="block text-center"
className="mb-0 justify-center"
label={roles[index].name}
>
<Field
className="mr-0"
name={`roles[${index}].isAssigned`}
component={Checkbox}
/>
</FormItem>
</div>
))
: null}
</div>
)}
</FieldArray>
</div>
</div>
</div>
</FormContainer>
</div>
<div className="mt-4 ltr:text-right">
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
</Form>
)
}}
</Formik>
</div>
</TabContent>
<TabContent value="work">
<div className="px-4 py-6">
<Formik
initialValues={userDetails}
onSubmit={async (values, { resetForm, setSubmitting }) => {
setSubmitting(true)
await putUserDetail({ ...values })
toast.push(
<Notification type="success" duration={2000}>
{translate('::Kaydet')}
</Notification>,
{
placement: 'top-end',
},
)
getUser()
setSubmitting(false)
}}
>
{({ isSubmitting, values }) => {
const departments = values.departments
const jobPositions = values.jobPositions
const departmentOptions: SelectBoxOption[] = departments.map((department) => ({
value: department.id,
label: department.name,
}))
const jobPositionOptions: SelectBoxOptionWithGroup[] = jobPositions.map(
(jobPosition) => ({
value: jobPosition.id,
label: jobPosition.name,
group: jobPosition.departmentId,
}),
)
return (
<Form>
2026-05-25 09:14:42 +00:00
<div>
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full">
<div>
<FormItem
label={translate(
'::Abp.Identity.User.UserInformation.DepartmentId',
)}
>
<Field type="text" name="departmentId">
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
options={departmentOptions}
isClearable={true}
value={departmentOptions.filter(
(option) => option.value === values.departmentId,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
form.setFieldValue('jobPositionId', null)
}}
/>
)}
</Field>
</FormItem>
</div>
<div>
<FormItem
label={translate(
'::Abp.Identity.User.UserInformation.JobPositionId',
)}
>
<Field type="text" name="jobPositionId">
{({ field, form }: FieldProps<SelectBoxOptionWithGroup>) => (
<Select
field={field}
form={form}
options={jobPositionOptions.filter(
(option) => option.group === values.departmentId,
)}
isClearable={true}
value={jobPositionOptions.filter(
(option) => option.value === values.jobPositionId,
)}
onChange={(option) =>
form.setFieldValue(field.name, option?.value)
}
/>
)}
</Field>
</FormItem>
</div>
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.SskNo')}
>
<Field
type="text"
name="sskNo"
2026-05-25 09:14:42 +00:00
placeholder={translate(
'::Abp.Identity.User.UserInformation.SskNo',
)}
component={Input}
prefix={<FaIdCard className="text-xl" />}
/>
</FormItem>
</div>
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.HireDate')}
>
<Field name="hireDate">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
2026-05-25 09:14:42 +00:00
placeholder={translate(
'::Abp.Identity.User.UserInformation.HireDate',
)}
inputPrefix={<FaBriefcase className="text-xl" />}
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
<div>
<FormItem
2026-05-25 09:14:42 +00:00
label={translate(
'::Abp.Identity.User.UserInformation.TerminationDate',
)}
>
<Field name="terminationDate">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
2026-05-25 09:14:42 +00:00
placeholder={translate(
'::Abp.Identity.User.UserInformation.TerminationDate',
)}
inputPrefix={<FaCalendarAlt className="text-xl" />}
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
</div>
</FormContainer>
</div>
<div className="mt-4 ltr:text-right">
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
</Form>
)
}}
</Formik>
</div>
</TabContent>
<TabContent value="identity">
<div className="px-4 py-6">
<Formik
initialValues={userDetails}
onSubmit={async (values, { resetForm, setSubmitting }) => {
setSubmitting(true)
await putUserDetail({ ...values })
toast.push(
<Notification type="success" duration={2000}>
{translate('::Kaydet')}
</Notification>,
{
placement: 'top-end',
},
)
getUser()
setSubmitting(false)
}}
>
{({ isSubmitting, values }) => {
const maritalStatusOptions: SelectBoxOption[] = [
{
value: 'Bekar',
label: translate('::App.MaritalStatus.Single') || 'Bekar',
},
{
value: 'Evli',
label: translate('::App.MaritalStatus.Married') || 'Evli',
},
{
value: 'Boşanmış',
label: translate('::App.MaritalStatus.Divorced') || 'Boşanmış',
},
{
value: 'Dul',
label: translate('::App.MaritalStatus.Widowed') || 'Dul',
},
]
return (
<Form>
2026-05-25 09:14:42 +00:00
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-5 w-full">
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate(
'::Abp.Identity.User.UserInformation.IdentityNumber',
)}
>
<Field
type="text"
name="identityNumber"
placeholder={translate(
'::Abp.Identity.User.UserInformation.IdentityNumber',
)}
component={Input}
prefix={<FaIdCard className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.SerialNo')}
>
<Field
type="text"
name="serialNo"
placeholder={translate(
'::Abp.Identity.User.UserInformation.SerialNo',
)}
component={Input}
prefix={<FaHashtag className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Province')}
>
<Field
type="text"
name="province"
placeholder={translate(
'::Abp.Identity.User.UserInformation.Province',
)}
component={Input}
prefix={<FaMapMarkerAlt className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.District')}
>
<Field
type="text"
name="district"
placeholder={translate(
'::Abp.Identity.User.UserInformation.District',
)}
component={Input}
prefix={<FaCity className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Village')}
>
<Field
type="text"
name="village"
placeholder={translate(
'::Abp.Identity.User.UserInformation.Village',
)}
component={Input}
prefix={<FaHome className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.VolumeNo')}
>
<Field
type="text"
name="volumeNo"
placeholder={translate(
'::Abp.Identity.User.UserInformation.VolumeNo',
)}
component={Input}
prefix={<FaBook className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate(
'::Abp.Identity.User.UserInformation.FamilySequenceNo',
)}
>
<Field
type="text"
name="familySequenceNo"
placeholder={translate(
'::Abp.Identity.User.UserInformation.FamilySequenceNo',
)}
component={Input}
prefix={<FaUsers className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.SequenceNo')}
>
<Field
type="text"
name="sequenceNo"
placeholder={translate(
'::Abp.Identity.User.UserInformation.SequenceNo',
)}
component={Input}
prefix={<FaHashtag className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.IssuedPlace')}
>
<Field
type="text"
name="issuedPlace"
placeholder={translate(
'::Abp.Identity.User.UserInformation.IssuedPlace',
)}
component={Input}
prefix={<FaMapPin className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.IssuedDate')}
>
<Field name="issuedDate">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder={translate(
'::Abp.Identity.User.UserInformation.IssuedDate',
)}
inputPrefix={<FaCalendarAlt className="text-xl" />}
2026-05-25 09:14:42 +00:00
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.BirthPlace')}
>
<Field
type="text"
name="birthPlace"
placeholder={translate(
'::Abp.Identity.User.UserInformation.BirthPlace',
)}
component={Input}
prefix={<FaMapMarkerAlt className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.BirthDate')}
>
<Field name="birthDate">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder={translate(
'::Abp.Identity.User.UserInformation.BirthDate',
)}
inputPrefix={<FaCalendarAlt className="text-xl" />}
2026-05-25 09:14:42 +00:00
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.FatherName')}
>
<Field
type="text"
name="fatherName"
placeholder={translate(
'::Abp.Identity.User.UserInformation.FatherName',
)}
component={Input}
prefix={<FaUserTie className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.MotherName')}
>
<Field
type="text"
name="motherName"
placeholder={translate(
'::Abp.Identity.User.UserInformation.MotherName',
)}
component={Input}
prefix={<FaFemale className="text-xl" />}
2026-05-25 09:14:42 +00:00
/>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.MaritalStatus')}
>
<Field type="text" name="maritalStatus">
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
options={maritalStatusOptions}
isClearable={true}
value={maritalStatusOptions.filter(
(o) => o.value === values.maritalStatus,
)}
onChange={(option) =>
form.setFieldValue(field.name, option?.value ?? null)
}
/>
)}
</Field>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.MarriageDate')}
>
<Field name="marriageDate">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder={translate(
'::Abp.Identity.User.UserInformation.MarriageDate',
)}
inputPrefix={<FaHeart className="text-xl" />}
2026-05-25 09:14:42 +00:00
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
2026-05-25 09:14:42 +00:00
</div>
</FormContainer>
<div className="mt-4 ltr:text-right">
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
</Form>
)
}}
</Formik>
</div>
</TabContent>
2026-02-24 20:44:16 +00:00
<TabContent value="lockout">
<div className="px-4 py-6">
2026-02-24 20:44:16 +00:00
<Formik
initialValues={userDetails}
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true)
await putUserLookout({ ...values })
toast.push(
<Notification type="success" duration={2000}>
{translate('::Abp.Identity.User.SaveLockout')}
2026-02-24 20:44:16 +00:00
</Notification>,
{
placement: 'top-end',
},
)
getUser()
setSubmitting(false)
}}
>
2026-04-26 19:05:19 +00:00
{({ isSubmitting, values }) => {
const workHours = values.workHours
const workHourOptions: SelectBoxOption[] = workHours.map((workHour) => ({
value: workHour.name,
label: workHour.name,
}))
2026-02-24 20:44:16 +00:00
return (
<Form>
2026-05-25 09:14:42 +00:00
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full">
2026-05-25 09:14:42 +00:00
{/* Account Status */}
<div>
<h6 className="mb-4">
{translate('::Abp.Identity.User.LockoutManagement.AccountStatus')}
</h6>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate('::Abp.Identity.User.LockoutManagement.Status')}
>
<Field
name="isActive"
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.Status',
)}
component={Checkbox}
/>
</FormItem>
2026-06-04 08:26:07 +00:00
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AdminVerification',
)}
>
<Field
name="isVerified"
placeholder={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.AdminVerification',
)}
2026-06-04 08:26:07 +00:00
component={Checkbox}
/>
</FormItem>
2026-05-25 09:14:42 +00:00
2026-06-03 21:38:21 +00:00
{isRequireConfirmedEmail?.toLowerCase() === 'true' && (
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.EmailConfirmed',
)}
2026-06-03 21:38:21 +00:00
>
<Field
name="emailConfirmed"
disabled={isRequireConfirmedEmail?.toLowerCase() !== 'true'}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.EmailConfirmed',
)}
component={Checkbox}
/>
</FormItem>
)}
2026-05-25 09:14:42 +00:00
2026-06-03 21:38:21 +00:00
{isRequireConfirmedPhoneNumber?.toLowerCase() === 'true' && (
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.PhoneNumberConfirmed',
)}
2026-06-03 21:38:21 +00:00
>
<Field
name="phoneNumberConfirmed"
disabled={isRequireConfirmedPhoneNumber?.toLowerCase() !== 'true'}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.PhoneNumberConfirmed',
)}
component={Checkbox}
/>
</FormItem>
)}
2026-05-25 09:14:42 +00:00
2026-06-03 21:38:21 +00:00
{isTwoFactorEnabled?.toLowerCase() === 'true' && (
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.TwoFactorEnabled',
)}
2026-06-03 21:38:21 +00:00
>
<Field
name="twoFactorEnabled"
disabled={isTwoFactorEnabled?.toLowerCase() !== 'true'}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.TwoFactorEnabled',
)}
component={Checkbox}
/>
</FormItem>
)}
2026-05-25 09:14:42 +00:00
</div>
2026-02-24 20:44:16 +00:00
2026-05-25 09:14:42 +00:00
{/* Login & Lockout Settings */}
<div>
<h6 className="mb-4">
{translate(
'::Abp.Identity.User.LockoutManagement.LoginAndLockoutSettings',
)}
</h6>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AccountLockoutEnabled',
)}
>
<Field
name="lockoutEnabled"
placeholder={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.AccountLockoutEnabled',
)}
2026-05-25 09:14:42 +00:00
component={Checkbox}
/>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AccountLocked',
)}
>
<Field
name="lockUser"
placeholder={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.AccountLocked',
)}
2026-05-25 09:14:42 +00:00
component={Checkbox}
disabled={values.lockoutEnabled === false}
/>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AccountEndDate',
)}
>
<Field name="lockoutEnd">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.AccountEndDate',
)}
inputPrefix={<FaCalendarAlt className="text-xl" />}
2026-05-25 09:14:42 +00:00
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
disabled={values.lockoutEnabled === false}
/>
2026-02-24 20:44:16 +00:00
)}
2026-05-25 09:14:42 +00:00
</Field>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.ShouldChangePwOnNextLogin',
)}
>
<Field
name="shouldChangePasswordOnNextLogin"
placeholder={translate(
2026-02-24 20:44:16 +00:00
'::Abp.Identity.User.LockoutManagement.ShouldChangePwOnNextLogin',
)}
2026-05-25 09:14:42 +00:00
component={Checkbox}
/>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AccessFailedCount',
)}
>
<Field
type="number"
name="accessFailedCount"
component={Input}
prefix={<FaExclamationTriangle className="text-xl" />}
/>
2026-05-25 09:14:42 +00:00
</FormItem>
</div>
2026-02-24 20:44:16 +00:00
2026-05-25 09:14:42 +00:00
{/* Login & Lockout Settings */}
<div>
<h6 className="mb-4">
{translate('::Abp.Identity.User.LockoutManagement.ConnectionsTimes')}
</h6>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate('::Abp.Identity.User.LockoutManagement.WorkHour')}
>
<Field type="text" name="workHour">
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
options={workHourOptions}
isClearable={true}
value={workHourOptions.filter(
(option) => option.value === values.workHour,
)}
onChange={(option) =>
form.setFieldValue(field.name, option?.value)
}
/>
2026-02-24 20:44:16 +00:00
)}
2026-05-25 09:14:42 +00:00
</Field>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.LoginEndDate',
)}
>
<Field name="loginEndDate">
{({ field, form }: FieldProps) => (
<DatePicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.LoginEndDate',
)}
inputPrefix={<FaCalendarAlt className="text-xl" />}
2026-05-25 09:14:42 +00:00
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.UserInformation.PasswordChangeTime',
)}
>
<Field name="lastPasswordChangeTime">
{({ field, form }: FieldProps) => (
<DateTimepicker
inputFormat="DD/MM/YYYY HH:mm"
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder="Select Date"
inputPrefix={<FaCalendarAlt className="text-xl" />}
onChange={(date: any) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate('::Abp.Identity.User.UserInformation.CreateTime')}
>
<Field name="creationTime">
{({ field, form }: FieldProps) => (
<Input
field={field}
form={form}
value={
field.value
? dayjs(field.value).format('DD/MM/YYYY HH:mm')
: undefined
}
disabled
prefix={<FaCalendarAlt className="text-xl" />}
/>
)}
</Field>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate('::Abp.Identity.User.UserInformation.UpdateTime')}
>
<Field name="lastModificationTime">
{({ field, form }: FieldProps) => (
<Input
field={field}
form={form}
value={
field.value
? dayjs(field.value).format('DD/MM/YYYY HH:mm')
: undefined
}
disabled
prefix={<FaCalendarAlt className="text-xl" />}
/>
)}
</Field>
</FormItem>
2026-02-24 20:44:16 +00:00
</div>
2026-05-25 09:14:42 +00:00
</div>
</FormContainer>
2026-02-24 20:44:16 +00:00
<div className="mt-4 ltr:text-right">
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
2026-02-24 20:44:16 +00:00
</Form>
)
}}
</Formik>
</div>
</TabContent>
2026-02-24 20:44:16 +00:00
<TabContent value="claimTypes">
<div className="px-4 py-6">
2026-02-24 20:44:16 +00:00
<Table compact>
<THead>
<Tr>
<Th className="text-center">
<Button
shape="circle"
variant="plain"
type="button"
size="sm"
2026-02-24 20:44:16 +00:00
title="Add"
icon={<FaFileAlt />}
onClick={async (e) => {
setOpen(true)
}}
/>
</Th>
<Th>{translate('::Abp.Identity.User.ClaimType')}</Th>
<Th>{translate('::Abp.Identity.User.ClaimValue')}</Th>
</Tr>
</THead>
<TBody>
{userDetails.claims.filter((a) => a.isAssigned === true) &&
userDetails.claims.filter((a) => a.isAssigned === true).length > 0 ? (
userDetails.claims.map((claim) => (
<Tr key={claim.id}>
<Td>
<Button
shape="circle"
variant="plain"
type="button"
size="sm"
2026-02-24 20:44:16 +00:00
title="Delete"
icon={<FaTrashAlt />}
onClick={() => setConfirmDeleteClaim(claim)}
/>
</Td>
<Td>{claim.claimType}</Td>
<Td>{claim.claimValue}</Td>
</Tr>
))
) : (
<Tr>
<Td colSpan={3} className="text-center">
{translate('::Abp.Identity.User.NoClaimsFound')}
</Td>
</Tr>
)}
</TBody>
</Table>
</div>
</TabContent>
</Tabs>
</AdaptableCard>
2026-02-24 20:44:16 +00:00
<Dialog isOpen={open} onClose={() => setOpen(false)} onRequestClose={() => setOpen(false)}>
<Formik
initialValues={{ claimType: '', claimValue: '' }}
validationSchema={scheme}
onSubmit={handleSubmit}
>
{({ touched, errors, values, isSubmitting }) => {
const claimOptions: SelectBoxOption[] = userDetails?.claims.map((claim) => ({
value: claim.claimType,
label: claim.claimType,
}))
return (
<Form>
<FormContainer size="sm">
<FormItem
label={translate('::Abp.Identity.User.ClaimType')}
invalid={errors.claimType && touched.claimType}
errorMessage={errors.claimType}
>
<Field type="text" name="claimType">
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
options={claimOptions}
isClearable={true}
value={claimOptions.filter((option) => option.value === values.claimType)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::Abp.Identity.User.ClaimValue')}
invalid={errors.claimValue && touched.claimValue}
errorMessage={errors.claimValue}
>
<Field
type="text"
autoComplete="off"
name="claimValue"
component={Input}
prefix={<FaFileAlt className="text-xl" />}
/>
2026-02-24 20:44:16 +00:00
</FormItem>
<div className="mt-6 flex flex-row justify-end gap-3">
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
<Button type="button" variant="plain" onClick={() => setOpen(false)}>
{translate('::Cancel')}
</Button>
</div>
</FormContainer>
</Form>
)
}}
</Formik>
</Dialog>
<ConfirmDialog
isOpen={!!confirmDeleteClaim}
type="danger"
title={translate('::DeleteConfirmation')}
confirmText={translate('::Delete')}
cancelText={translate('::Cancel')}
confirmButtonColor="red-600"
onCancel={() => setConfirmDeleteClaim(null)}
onConfirm={async () => {
if (confirmDeleteClaim) {
await deleteClaimUser(confirmDeleteClaim.id, userId)
toast.push(
<Notification type="success" duration={2000}>
{translate('::Abp.Identity.User.ClaimDeleted')}
</Notification>,
{ placement: 'top-end' },
)
setConfirmDeleteClaim(null)
getUser()
}
}}
>
<p>
<span className="font-semibold">{confirmDeleteClaim?.claimType}</span> claim silmek
istediğinize emin misiniz?
</p>
</ConfirmDialog>
</>
) : (
<></>
)
}
export default UserDetails