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

775 lines
33 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,
} 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,
} from '@/services/identity.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs'
import { Field, FieldArray, FieldProps, Form, Formik, FormikHelpers } from 'formik'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { FaLockOpen, FaUser, FaFileAlt, FaTrashAlt } from 'react-icons/fa'
import { useParams } from 'react-router-dom'
import * as Yup from 'yup'
import { SelectBoxOption } from '@/types/shared'
import { ConfirmDialog, Container } from '@/components/shared'
import { AssignedClaimViewModel, UserInfoViewModel } from '@/proxy/admin/models'
import { APP_NAME } from '@/constants/app.constant'
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 getUser = async () => {
const { data } = await getUserDetail(userId || '')
setUserDetails(data)
}
useEffect(() => {
getUser()
}, [])
const scheme = Yup.object().shape({
claimType: Yup.string().required(),
claimValue: Yup.string().required(),
})
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>
<Container>
<Tabs defaultValue="user">
<TabList>
<TabNav value="user" icon={<FaUser />}>
{translate('::Abp.Identity.User.UserInformation')}
</TabNav>
<TabNav value="lockout" icon={<FaLockOpen />}>
{translate('::Abp.Identity.User.LockoutManagement')}
</TabNav>
<TabNav value="claimTypes" icon={<FaFileAlt />}>
{translate('::Abp.Identity.User.ClaimTypes')}
</TabNav>
</TabList>
<TabContent value="user">
<div className="mt-5">
<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)
}}
>
{({ touched, errors, resetForm, isSubmitting, values }) => {
const userRoleNames = values.userRoleNames
const roles = values.roles
const branches = values.branches
return (
<Form>
<div className="w-1/2">
<FormContainer size="md">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
{/* Personal Information */}
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Name')}
>
<Field
type="text"
name="name"
placeholder="Name"
component={Input}
/>
</FormItem>
</div>
{/* Personal Information */}
<div>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.Surname')}
>
<Field
type="text"
name="surname"
placeholder="Surname"
component={Input}
/>
</FormItem>
</div>
{/* Ş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>
{/* Contact Information */}
<div>
<h6 className="mb-4">
{translate(
'::Abp.Identity.User.UserInformation.ContactInformation',
)}
</h6>
<FormItem
label={translate(
'::Abp.Account.EmailAddress',
)}
>
<Field
type="text"
disabled
name="email"
placeholder="Email Address"
component={Input}
/>
</FormItem>
<FormItem
label={translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
>
<Field
type="text"
name="phoneNumber"
2026-03-29 08:59:07 +00:00
placeholder={translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
2026-02-24 20:44:16 +00:00
component={Input}
/>
</FormItem>
<FormItem label={translate('::RocketUsername')}>
<Field
type="text"
name="rocketUsername"
placeholder={translate('::RocketUsername')}
component={Input}
/>
</FormItem>
</div>
{/* Account Timestamps */}
<div>
<h6 className="mb-4">
{translate('::Abp.Identity.User.UserInformation.AccountTimestamps')}
</h6>
<FormItem
label={translate(
'::Abp.Identity.User.UserInformation.PasswordChangeTime',
)}
>
<Field name="lastPasswordChangeTime">
{({ field, form }: FieldProps) => (
<DateTimepicker
field={field}
form={form}
value={field.value ? dayjs(field.value).toDate() : null}
placeholder="Select Date"
onChange={(date: any) => {
form.setFieldValue(
field.name,
date
? dayjs(date).format('YYYY-MM-DDTHH:mm:ss')
: null,
)
}}
/>
)}
</Field>
</FormItem>
<FormItem
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('L LT') : undefined
}
disabled
/>
)}
</Field>
</FormItem>
<FormItem
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('L LT') : undefined
}
disabled
/>
)}
</Field>
</FormItem>
</div>
</div>
</FormContainer>
</div>
<div>
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
</Form>
)
}}
</Formik>
</div>
</TabContent>
<TabContent value="lockout">
<div className="mt-5">
<Formik
initialValues={userDetails}
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true)
await putUserLookout({ ...values })
toast.push(
<Notification type="success" duration={2000}>
{'Lockout bilgileri kaydedildi.'}
</Notification>,
{
placement: 'top-end',
},
)
getUser()
setSubmitting(false)
}}
>
{({ touched, errors, resetForm, isSubmitting, values }) => {
const userRoleNames = values.userRoleNames
return (
<Form>
<div className="w-1/2">
<FormContainer size="md">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 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>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.EmailConfirmed',
)}
>
<Field
name="emailConfirmed"
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.EmailConfirmed',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.PhoneNumberConfirmed',
)}
>
<Field
name="phoneNumberConfirmed"
placeholder={translate(
'::Abp.Identity.User.LockoutManagement.PhoneNumberConfirmed',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
layout="horizontal"
labelClass="!justify-start"
labelWidth="50%"
label={translate(
'::Abp.Identity.User.LockoutManagement.TwoFactorEnabled',
)}
>
<Field
name="twoFactorEnabled"
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.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',
)}
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.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}
/>
</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',
)}
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.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} />
</FormItem>
</div>
</div>
</FormContainer>
</div>
<div>
<Button variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
</Form>
)
}}
</Formik>
</div>
</TabContent>
<TabContent value="claimTypes">
<div className="mt-5 w-1/2">
<Table compact>
<THead>
<Tr>
<Th className="text-center">
<Button
shape="circle"
variant="plain"
type="button"
size="xs"
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="xs"
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>
</Container>
<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} />
</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