sozsoft-platform/ui/src/views/admin/user-management/Details.tsx
2026-06-04 11:26:07 +03:00

1761 lines
77 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Button,
Checkbox,
DatePicker,
FormContainer,
FormItem,
Input,
Notification,
Select,
Tabs,
toast,
Upload,
} 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,
} from '@/services/identity.service'
import { updateProfile } from '@/services/account.service'
import { CountryDto, getCountry } from '@/services/home.service'
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'
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'
import { useParams } from 'react-router-dom'
import * as Yup from 'yup'
import { SelectBoxOption, SelectBoxOptionWithGroup } from '@/types/shared'
import { AdaptableCard, ConfirmDialog, Container } from '@/components/shared'
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'
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)
const { setUser } = useStoreActions((actions) => actions.auth.user)
const { setting } = useSetting()
const isEmailUpdateEnabled = setting('Abp.Identity.User.IsEmailUpdateEnabled')
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')
const getUser = async (syncAvatar = true) => {
const { data } = await getUserDetail(userId || '')
setUserDetails(data)
if (syncAvatar) {
setImage(`${AVATAR_URL(data.id, data.tenantId)}?${dayjs().unix()}`)
}
}
useEffect(() => {
getUser()
getCountry().then(({ data }) => setCountries(data))
}, [])
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)
}
})
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>
<Tabs defaultValue="user">
<TabList>
<TabNav value="user" icon={<FaUser className="text-sm" />}>
{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" />}>
{translate('::Abp.Identity.User.LockoutManagement')}
</TabNav>
<TabNav value="claimTypes" icon={<FaFileAlt className="text-sm" />}>
{translate('::Abp.Identity.User.ClaimTypes')}
</TabNav>
</TabList>
<TabContent value="user">
<div className="px-4 py-6">
<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',
})
}
toast.push(
<Notification type="success" duration={2000}>
{translate('::Kaydet')}
</Notification>,
{
placement: 'top-end',
},
)
getUser(!keepCurrentAvatar)
setSubmitting(false)
}}
>
{({ isSubmitting, values }) => {
return (
<Form>
<div>
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full">
{/* Personal Information */}
<div>
<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>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Name')}
>
<Field
type="text"
name="name"
placeholder="Name"
component={Input}
prefix={<FaUserCircle className="text-xl" />}
/>
</FormItem>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Surname')}
>
<Field
type="text"
name="surname"
placeholder="Surname"
component={Input}
prefix={<FaUserCircle className="text-xl" />}
/>
</FormItem>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
>
<Field
type="text"
name="phoneNumber"
placeholder={translate(
'::Abp.Identity.User.UserInformation.PhoneNumber',
)}
prefix={<FaPhone className="text-xl" />}
component={Input}
/>
</FormItem>
<FormItem size="sm" label={translate('::RocketUsername')}>
<Field
type="text"
name="rocketUsername"
placeholder={translate('::RocketUsername')}
component={Input}
prefix={<FaFacebookMessenger className="text-xl" />}
/>
</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>
<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)
}}
className="cropper max-h-[300px] max-w-[300px]"
stencilComponent={CircleStencil}
minHeight={100}
minWidth={100}
/>
) : (
<img
className="cropper max-h-[300px] max-w-[300px]"
src={image || AVATAR_URL(values.id, values.tenantId)}
/>
)}
<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>
</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="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>
<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>
<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"
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}
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
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}
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>
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-5 w-full">
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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" />}
/>
</FormItem>
</div>
<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>
<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" />}
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
/>
)}
</Field>
</FormItem>
</div>
</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>
<TabContent value="lockout">
<div className="px-4 py-6">
<Formik
initialValues={userDetails}
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true)
await putUserLookout({ ...values })
toast.push(
<Notification type="success" duration={2000}>
{translate('::Abp.Identity.User.SaveLockout')}
</Notification>,
{
placement: 'top-end',
},
)
getUser()
setSubmitting(false)
}}
>
{({ isSubmitting, values }) => {
const workHours = values.workHours
const workHourOptions: SelectBoxOption[] = workHours.map((workHour) => ({
value: workHour.name,
label: workHour.name,
}))
return (
<Form>
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full">
{/* 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>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AdminVerification',
)}
>
<Field
name="isVerified"
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.AdminVerification',
)}
component={Checkbox}
/>
</FormItem>
{isRequireConfirmedEmail?.toLowerCase() === 'true' && (
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.EmailConfirmed',
)}
>
<Field
name="emailConfirmed"
disabled={isRequireConfirmedEmail?.toLowerCase() !== 'true'}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.EmailConfirmed',
)}
component={Checkbox}
/>
</FormItem>
)}
{isRequireConfirmedPhoneNumber?.toLowerCase() === 'true' && (
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.PhoneNumberConfirmed',
)}
>
<Field
name="phoneNumberConfirmed"
disabled={isRequireConfirmedPhoneNumber?.toLowerCase() !== 'true'}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.PhoneNumberConfirmed',
)}
component={Checkbox}
/>
</FormItem>
)}
{isTwoFactorEnabled?.toLowerCase() === 'true' && (
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.TwoFactorEnabled',
)}
>
<Field
name="twoFactorEnabled"
disabled={isTwoFactorEnabled?.toLowerCase() !== 'true'}
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.TwoFactorEnabled',
)}
component={Checkbox}
/>
</FormItem>
)}
</div>
{/* 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(
'::Abp.Identity.User.LockoutManagement.AccountLockoutEnabled',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.AccountLocked',
)}
>
<Field
name="lockUser"
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.AccountLocked',
)}
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" />}
onChange={(date) => {
form.setFieldValue(
field.name,
date ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss') : null,
)
}}
disabled={values.lockoutEnabled === false}
/>
)}
</Field>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.ShouldChangePwOnNextLogin',
)}
>
<Field
name="shouldChangePasswordOnNextLogin"
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.ShouldChangePwOnNextLogin',
)}
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" />}
/>
</FormItem>
</div>
{/* 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)
}
/>
)}
</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" />}
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>
</div>
</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>
<TabContent value="claimTypes">
<div className="px-4 py-6">
<Table compact>
<THead>
<Tr>
<Th className="text-center">
<Button
shape="circle"
variant="plain"
type="button"
size="sm"
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"
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>
<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" />}
/>
</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