This commit is contained in:
2026-03-21 17:23:27 +03:30
parent 878d8fc544
commit 451a814347
17 changed files with 122 additions and 45 deletions
@@ -13,10 +13,10 @@ import UserList from '@views/apps/user/list'
// API Imports // API Imports
import { userManagementService } from '@/libs/api' import { userManagementService } from '@/libs/api'
import type { User } from '@/libs/api/services/userManagementService' import type { UsersType } from '@/types/apps/userTypes'
const UserListApp = () => { const UserListApp = () => {
const [users, setUsers] = useState<User[]>([]) const [users, setUsers] = useState<UsersType[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@@ -24,7 +24,7 @@ const UserListApp = () => {
const fetchUsers = async () => { const fetchUsers = async () => {
try { try {
setLoading(true) setLoading(true)
const response = await userManagementService.getUsers() const response = await userManagementService.getUsers() as any
setUsers(response.users) setUsers(response.users)
} catch (err: any) { } catch (err: any) {
setError(err.message || 'خطا در دریافت لیست کاربران') setError(err.message || 'خطا در دریافت لیست کاربران')
+3 -2
View File
@@ -3,9 +3,10 @@ import Pagination from '@mui/material/Pagination'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
// Third Party Imports // Third Party Imports
import type { useReactTable } from '@tanstack/react-table' import type { Table } from '@tanstack/react-table'
const TablePaginationComponent = ({ table }: { table: ReturnType<typeof useReactTable> }) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const TablePaginationComponent = ({ table }: { table: Table<any> }) => {
return ( return (
<div className='flex justify-between items-center flex-wrap pli-6 border-bs bs-auto plb-[12.5px] gap-2'> <div className='flex justify-between items-center flex-wrap pli-6 border-bs bs-auto plb-[12.5px] gap-2'>
<Typography color='text.disabled'> <Typography color='text.disabled'>
+2 -1
View File
@@ -1,4 +1,5 @@
import { getRequestConfig } from 'next-intl/server' import { getRequestConfig } from 'next-intl/server'
import type { AbstractIntlMessages } from 'next-intl'
import faMessages from '../../messages/fa.json' import faMessages from '../../messages/fa.json'
export default getRequestConfig(async () => { export default getRequestConfig(async () => {
@@ -6,6 +7,6 @@ export default getRequestConfig(async () => {
return { return {
locale, locale,
messages: faMessages as Record<string, unknown> messages: faMessages as AbstractIntlMessages
} }
}) })
+25 -4
View File
@@ -4,7 +4,16 @@
export * from './client' export * from './client'
export * from './types' export * from './types'
export * from './services/authService' export {
type RequestOTPRequest,
type RequestOTPResponse,
type VerifyOTPRequest,
type AuthUser,
type VerifyOTPResponse,
type UpdateProfilePayload,
type UpdateProfileResponse,
authService
} from './services/authService'
export * from './services/taskService' export * from './services/taskService'
export * from './services/eventService' export * from './services/eventService'
export * from './services/simulatorService' export * from './services/simulatorService'
@@ -12,8 +21,20 @@ export * from './services/chatService'
export * from './services/aiChatService' export * from './services/aiChatService'
export * from './services/kanbanService' export * from './services/kanbanService'
export * from './services/todoService' export * from './services/todoService'
export * from './services/userManagementService' export {
type User,
type UserDetails,
type Account,
type ApiResponse,
type UpdateProfileRequest,
type AddAccountRequest,
type UpdateAccountRequest,
userManagementService
} from './services/userManagementService'
export * from './services/rolesPermissionsService' export * from './services/rolesPermissionsService'
export * from './services/sensorHubService' export * from './services/sensorHubService'
export * from './services/farmDashboardService' export {
type FarmDashboardConfigResponse,
type FarmDashboardCardsResponse,
farmDashboardService
} from './services/farmDashboardService'
@@ -6,6 +6,36 @@
import { apiClient } from '../client' import { apiClient } from '../client'
export interface User {
id: number
username: string
email: string
first_name: string
last_name: string
phone_number: string
role?: string
status?: string
avatar?: string
fullName?: string
currentPlan?: string
billing?: string
company?: string
country?: string
contact?: string
}
export interface UserDetails {
id: number
username: string
email: string
first_name: string
last_name: string
phone_number: string
role?: string
status?: string
avatar?: string
}
export interface Account { export interface Account {
id: number id: number
username: string username: string
@@ -49,6 +79,31 @@ export interface UpdateAccountRequest {
} }
export const userManagementService = { export const userManagementService = {
/**
* Get list of users
*/
async getUsers(): Promise<{ users: User[]; stats: { total: number; active: number; pending: number; inactive: number } }> {
const response = await apiClient.get<ApiResponse<{ users: User[]; stats: { total: number; active: number; pending: number; inactive: number } }>>('/api/account/')
return response.data as { users: User[]; stats: { total: number; active: number; pending: number; inactive: number } }
},
/**
* Get a single user by ID
*/
async getUser(id: number): Promise<UserDetails> {
const response = await apiClient.get<ApiResponse<UserDetails>>(`/api/account/${id}/`)
return response.data as unknown as UserDetails
},
/**
* Delete a user by ID
*/
async deleteUser(id: number): Promise<void> {
await apiClient.delete<ApiResponse<unknown>>(`/api/account/${id}/`)
},
/** /**
* Update current user profile (first_name, last_name, email) * Update current user profile (first_name, last_name, email)
*/ */
+8 -8
View File
@@ -10,9 +10,9 @@ import type { BoxProps } from '@mui/material/Box'
// Third-party Imports // Third-party Imports
import { Calendar } from 'react-multi-date-picker' import { Calendar } from 'react-multi-date-picker'
import type { DateObject } from 'react-multi-date-picker' import DateObject from 'react-date-object'
import persian from 'react-date-object/calendars/persian' import persian from 'react-date-object/calendars/persian'
import fa from 'react-date-object/locales/fa' import persian_fa from 'react-date-object/locales/persian_fa'
// Styles - base styles only, we override colors via sx // Styles - base styles only, we override colors via sx
import 'react-multi-date-picker/styles/colors/teal.css' import 'react-multi-date-picker/styles/colors/teal.css'
@@ -30,23 +30,23 @@ const AppJalaliDatepicker = (props: AppJalaliDatepickerProps) => {
const [internalValue, setInternalValue] = useState<DateObject | undefined>(() => { const [internalValue, setInternalValue] = useState<DateObject | undefined>(() => {
const d = externalValue ?? new Date() const d = externalValue ?? new Date()
return new DateObject(d, { calendar: persian, locale: fa }) return new DateObject({ date: d, calendar: persian, locale: persian_fa })
}) })
useEffect(() => { useEffect(() => {
if (externalValue != null) { if (externalValue != null) {
setInternalValue(new DateObject(externalValue, { calendar: persian, locale: fa })) setInternalValue(new DateObject({ date: externalValue, calendar: persian, locale: persian_fa }))
} }
}, [externalValue]) }, [externalValue])
const handleChange = (d: DateObject | null) => { const handleChange = (d: DateObject | null) => {
if (d) { if (d) {
setInternalValue(d) setInternalValue(d as unknown as DateObject)
onChange?.(d.toDate()) onChange?.(d.toDate())
} }
} }
const displayValue = internalValue ?? new DateObject({ calendar: persian, locale: fa }) const displayValue = internalValue ?? new DateObject({ calendar: persian, locale: persian_fa })
return ( return (
<Box <Box
@@ -87,9 +87,9 @@ const AppJalaliDatepicker = (props: AppJalaliDatepickerProps) => {
> >
<Calendar <Calendar
value={displayValue} value={displayValue}
onChange={handleChange} onChange={handleChange as any}
calendar={persian} calendar={persian}
locale={fa} locale={persian_fa}
className="teal" className="teal"
/> />
</Box> </Box>
+12 -13
View File
@@ -83,7 +83,7 @@ const Calendar = (props: CalenderProps) => {
}, },
views: { views: {
week: { week: {
titleFormat(arg) { titleFormat(arg: any) {
const start = arg.start const start = arg.start
const end = arg.end const end = arg.end
@@ -94,20 +94,21 @@ const Calendar = (props: CalenderProps) => {
}) })
if (!start && !end) { if (!start && !end) {
return { text: '' } return ''
} }
if (start && !end) { if (start && !end) {
return { text: formatter.format(start) } return formatter.format(start instanceof Date ? start : new Date(start.marker ?? start))
} }
if (!start && end) { if (!start && end) {
return { text: formatter.format(end) } return formatter.format(end instanceof Date ? end : new Date(end.marker ?? end))
} }
return { const s = start instanceof Date ? start : new Date(start.marker ?? start)
text: `${formatter.format(start)} - ${formatter.format(end)}` const e = end instanceof Date ? end : new Date(end.marker ?? end)
}
return `${formatter.format(s)} - ${formatter.format(e)}`
} }
} }
}, },
@@ -116,7 +117,7 @@ const Calendar = (props: CalenderProps) => {
return formatter.format(arg.date) return formatter.format(arg.date)
}, },
titleFormat(arg) { titleFormat(arg: any) {
const { start, end } = arg const { start, end } = arg
const monthFormatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { const monthFormatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
@@ -129,18 +130,16 @@ const Calendar = (props: CalenderProps) => {
const target = start || end const target = start || end
if (!target) { if (!target) {
return { text: '' } return ''
} }
const asDate = target instanceof Date ? target : new Date(target as any) const asDate = target instanceof Date ? target : new Date(target as any)
if (Number.isNaN(asDate.getTime())) { if (Number.isNaN(asDate.getTime())) {
return { text: '' } return ''
} }
return { return monthFormatter.format(asDate)
text: monthFormatter.format(asDate)
}
}, },
buttonText: { buttonText: {
today: 'امروز', today: 'امروز',
@@ -44,7 +44,7 @@ const FarmDashboardSettingsDrawer = (props: FarmDashboardSettingsDrawerProps) =>
<Drawer <Drawer
open={open} open={open}
onClose={onClose} onClose={onClose}
anchor='end' anchor='right'
variant='temporary' variant='temporary'
ModalProps={{ ModalProps={{
disablePortal: true, disablePortal: true,
@@ -54,7 +54,7 @@ const cardRowSx = {
'& > *': { flex: 1, minHeight: 0 } '& > *': { flex: 1, minHeight: 0 }
} }
const CARD_COMPONENTS: Record<CardId, React.ComponentType> = { const CARD_COMPONENTS: Record<CardId, React.ComponentType<{ data?: Record<string, unknown> }>> = {
farmOverviewKpis: FarmOverviewKPIs, farmOverviewKpis: FarmOverviewKPIs,
farmWeatherCard: FarmWeatherCard, farmWeatherCard: FarmWeatherCard,
farmAlertsTracker: FarmAlertsTracker, farmAlertsTracker: FarmAlertsTracker,
@@ -37,7 +37,7 @@ const FarmOverviewKPIs = ({ data }: FarmOverviewKPIsProps) => {
avatarIcon={kpi.avatarIcon ?? 'tabler-chart-bar'} avatarIcon={kpi.avatarIcon ?? 'tabler-chart-bar'}
avatarSkin='light' avatarSkin='light'
avatarSize={44} avatarSize={44}
chipText={kpi.chipText} chipText={kpi.chipText ?? ''}
chipColor={(kpi.chipColor as 'success' | 'warning') ?? 'success'} chipColor={(kpi.chipColor as 'success' | 'warning') ?? 'success'}
chipVariant='tonal' chipVariant='tonal'
/> />
@@ -26,10 +26,10 @@ interface FarmWeatherCardProps {
const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => { const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => {
const t = useTranslations('farmDashboard') const t = useTranslations('farmDashboard')
const temperature = data?.temperature ?? 24 const temperature = (data?.temperature as number | undefined) ?? 24
const condition = (data?.condition as string) ?? '' const condition = (data?.condition as string) ?? ''
const humidity = data?.humidity ?? 45 const humidity = (data?.humidity as number | undefined) ?? 45
const windSpeed = data?.windSpeed ?? 12 const windSpeed = (data?.windSpeed as number | undefined) ?? 12
const windUnit = (data?.windUnit as string) ?? 'km/h' const windUnit = (data?.windUnit as string) ?? 'km/h'
const unit = (data?.unit as string) ?? '°C' const unit = (data?.unit as string) ?? '°C'
const chartData = data?.chartData as { labels?: string[]; series?: number[][] } | undefined const chartData = data?.chartData as { labels?: string[]; series?: number[][] } | undefined
@@ -27,7 +27,7 @@ const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => {
const categories = const categories =
(data?.categories as string[]) ?? (data?.categories as string[]) ??
[t('fallback.mon'), t('fallback.tue'), t('fallback.wed'), t('fallback.thu'), t('fallback.fri'), t('fallback.sat'), t('fallback.sun')] [t('fallback.mon'), t('fallback.tue'), t('fallback.wed'), t('fallback.thu'), t('fallback.fri'), t('fallback.sat'), t('fallback.sun')]
const currentValue = data?.currentValue ?? 48 const currentValue = (data?.currentValue as number | undefined) ?? 48
const vsLastWeek = (data?.vsLastWeek as string) ?? t('fallback.plusPercentVsLastWeek', { val: '5' }) const vsLastWeek = (data?.vsLastWeek as string) ?? t('fallback.plusPercentVsLastWeek', { val: '5' })
const theme = useTheme() const theme = useTheme()
if (series.length === 0) return null if (series.length === 0) return null
@@ -128,7 +128,7 @@ export default function CropZoningWeatherSection() {
</Typography> </Typography>
<Typography variant='h5'> <Typography variant='h5'>
{temp} {temp}
{weatherData.unit ?? '°C'} {(weatherData.unit as string) ?? '°C'}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
@@ -67,7 +67,7 @@ export default function ZoneDetailPanel({
<Drawer <Drawer
open={open} open={open}
onClose={onClose} onClose={onClose}
anchor='end' anchor='right'
variant='temporary' variant='temporary'
ModalProps={{ ModalProps={{
disablePortal: true, disablePortal: true,
@@ -68,11 +68,11 @@ export const CARD_GRID_SIZE: Record<CardId, { xs?: number; sm?: number; md?: num
farmAlertsTimeline: { xs: 12, lg: 4 }, farmAlertsTimeline: { xs: 12, lg: 4 },
waterNeedPrediction: { xs: 12, lg: 8 }, waterNeedPrediction: { xs: 12, lg: 8 },
harvestPredictionCard: { xs: 12, md: 6, lg: 4 }, harvestPredictionCard: { xs: 12, md: 6, lg: 4 },
yieldPredictionChart: { xs: 12 }, yieldPredictionChart: { xs: 12, lg: 12 },
soilMoistureHeatmap: { xs: 12 }, soilMoistureHeatmap: { xs: 12, lg: 12 },
ndviHealthCard: { xs: 12, md: 6, lg: 4 }, ndviHealthCard: { xs: 12, md: 6, lg: 4 },
recommendationsList: { xs: 12, md: 6, lg: 8 }, recommendationsList: { xs: 12, md: 6, lg: 8 },
economicOverview: { xs: 12 } economicOverview: { xs: 12, lg: 12 }
} }
/** Display label for each card (for settings UI) */ /** Display label for each card (for settings UI) */
+1 -1
View File
@@ -29,7 +29,7 @@ type OptionSensorHubProps = {
const formatLastUpdated = (dateStr: string | null | undefined): string => { const formatLastUpdated = (dateStr: string | null | undefined): string => {
if (!dateStr) return '—' if (!dateStr) return '—'
try { try {
const d = new DateObject(new Date(dateStr)).convert('persian').setLocale('fa') const d = new DateObject(new Date(dateStr)).convert('persian' as any).setLocale('fa' as any)
return d.format('YYYY/MM/DD') return d.format('YYYY/MM/DD')
} catch { } catch {
+1 -1
View File
@@ -23,7 +23,7 @@ import styles from '@core/styles/table.module.css'
const formatToShamsi = (dateStr: string | null | undefined): string => { const formatToShamsi = (dateStr: string | null | undefined): string => {
if (!dateStr) return '—' if (!dateStr) return '—'
try { try {
const d = new DateObject(new Date(dateStr)).convert('persian').setLocale('fa') const d = new DateObject(new Date(dateStr)).convert('persian' as any).setLocale('fa' as any)
return d.format('YYYY/MM/DD') return d.format('YYYY/MM/DD')
} catch { } catch {