diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 86d64d9c..93bb9bb2 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,5 +1,11 @@
# @baseapp-frontend/components
+## 1.0.12
+
+### Patch Changes
+
+- add filter by date to the module activity log. Filter as a modal on desktop and swap on mobile. Filtering can be don by start and/or end date
+
## 1.0.11
### Patch Changes
diff --git a/packages/components/modules/__shared__/common/types.ts b/packages/components/modules/__shared__/common/types.ts
index 27187687..b787b310 100644
--- a/packages/components/modules/__shared__/common/types.ts
+++ b/packages/components/modules/__shared__/common/types.ts
@@ -1,3 +1,16 @@
+import { ReactNode } from 'react'
+
export type SocialUpsertForm = {
body: string
}
+
+export interface SearchNotFoundStateProps {
+ message?: string
+}
+
+export interface MobileDrawerProps {
+ open: boolean
+ onClose: () => void
+ title?: string
+ children: ReactNode
+}
diff --git a/packages/components/modules/__shared__/web/SearchNotFoundState/index.tsx b/packages/components/modules/__shared__/web/SearchNotFoundState/index.tsx
index 43a9b85c..9fcd8346 100644
--- a/packages/components/modules/__shared__/web/SearchNotFoundState/index.tsx
+++ b/packages/components/modules/__shared__/web/SearchNotFoundState/index.tsx
@@ -2,7 +2,11 @@ import { SearchingImage } from '@baseapp-frontend/design-system/components/web/i
import { Box, Typography } from '@mui/material'
-const SearchNotFoundState = () => (
+import { SearchNotFoundStateProps } from '../../common/types'
+
+const SearchNotFoundState = ({
+ message = 'Check your spelling or try another search.',
+}: SearchNotFoundStateProps) => (
@@ -10,7 +14,7 @@ const SearchNotFoundState = () => (
No results found
- Check your spelling or try another search.
+ {message}
diff --git a/packages/components/modules/activity-log/DateFilterChip/index.tsx b/packages/components/modules/activity-log/DateFilterChip/index.tsx
new file mode 100644
index 00000000..37249712
--- /dev/null
+++ b/packages/components/modules/activity-log/DateFilterChip/index.tsx
@@ -0,0 +1,91 @@
+import { FC, useState } from 'react'
+
+import { SwipeableDrawer } from '@baseapp-frontend/design-system/components/web/drawers'
+import { DATE_FORMAT, formatDate } from '@baseapp-frontend/utils'
+
+import { KeyboardArrowDown } from '@mui/icons-material'
+import { Box, Chip, Divider, Menu, Theme, Typography, useMediaQuery } from '@mui/material'
+import { DateTime } from 'luxon'
+
+import DateFilterComponent from '../DateFilterComponent'
+import { DateFilterChipProps } from './types'
+
+const DateFilterChip: FC = ({ fetchParameters, executeRefetch }) => {
+ const [drawerOpen, setDrawerOpen] = useState(false)
+ const [anchorEl, setAnchorEl] = useState(null)
+ const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm'))
+
+ const handleDrawerOpen = () => setDrawerOpen(true)
+ const handleDrawerClose = () => setDrawerOpen(false)
+ const handleMenuClose = () => setAnchorEl(null)
+
+ const { createdFrom, createdTo } = fetchParameters
+ const hasDateSelected = createdFrom || createdTo
+
+ const parseDate = (date: string | null): DateTime | null =>
+ date ? DateTime.fromFormat(date, DATE_FORMAT.api) : null
+
+ const formatLabelDate = (date: string | null): string | null =>
+ date ? formatDate(date, { toFormat: DATE_FORMAT[3] }) : null
+
+ const labelRender = () => {
+ const fromDate = formatLabelDate(createdFrom)
+ const toDate = formatLabelDate(createdTo)
+ if (createdFrom && createdTo) return `${fromDate} - ${toDate}`
+
+ if (createdFrom) return `From ${fromDate}`
+ if (createdTo) return `Until ${toDate}`
+ return 'Period'
+ }
+
+ const handleOnClickOnChip = (event: React.MouseEvent) => {
+ if (isMobile) {
+ handleDrawerOpen()
+ } else {
+ setAnchorEl(event.currentTarget)
+ }
+ }
+
+ const renderDateFilterComponent = (onClose: () => void) => (
+ <>
+
+ Period
+
+
+
+ >
+ )
+
+ return (
+ <>
+
+ {labelRender()}
+
+
+ }
+ onClick={handleOnClickOnChip}
+ variant={hasDateSelected ? 'filled' : 'soft'}
+ color="default"
+ />
+ {isMobile ? (
+
+ {renderDateFilterComponent(handleDrawerClose)}
+
+ ) : (
+
+ )}
+ >
+ )
+}
+
+export default DateFilterChip
diff --git a/packages/components/modules/activity-log/DateFilterChip/types.ts b/packages/components/modules/activity-log/DateFilterChip/types.ts
new file mode 100644
index 00000000..aca7ed58
--- /dev/null
+++ b/packages/components/modules/activity-log/DateFilterChip/types.ts
@@ -0,0 +1,6 @@
+import { FetchParameters } from '../common/types'
+
+export interface DateFilterChipProps {
+ fetchParameters: FetchParameters
+ executeRefetch: (params: Partial) => void
+}
diff --git a/packages/components/modules/activity-log/DateFilterComponent/index.tsx b/packages/components/modules/activity-log/DateFilterComponent/index.tsx
new file mode 100644
index 00000000..6b2abda6
--- /dev/null
+++ b/packages/components/modules/activity-log/DateFilterComponent/index.tsx
@@ -0,0 +1,116 @@
+import React, { FC, useState } from 'react'
+
+import { Box, Button } from '@mui/material'
+import { DateValidationError, LocalizationProvider } from '@mui/x-date-pickers'
+import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'
+import { DatePicker } from '@mui/x-date-pickers/DatePicker'
+import { DateTime } from 'luxon'
+
+import { DateFilterComponentProps } from './types'
+
+const DateFilterComponent: FC = ({
+ createdFrom,
+ createdTo,
+ executeRefetch,
+ onApply,
+ onClearFilter,
+}) => {
+ const [tempCreatedFrom, setTempCreatedFrom] = useState(createdFrom ?? null)
+ const [tempCreatedTo, setTempCreatedTo] = useState(createdTo ?? null)
+
+ const [error, setError] = useState(null)
+
+ const errorMessage = React.useMemo(() => {
+ switch (error) {
+ case 'minDate':
+ return 'End date cannot be earlier than start date.'
+ case 'invalidDate':
+ return 'Your date is not valid'
+ default:
+ return ''
+ }
+ }, [error])
+
+ const handleApply = () => {
+ if (tempCreatedTo && tempCreatedFrom && tempCreatedTo < tempCreatedFrom) {
+ setError('minDate')
+ return
+ }
+
+ setError(null)
+ executeRefetch({
+ createdFrom: tempCreatedFrom ? tempCreatedFrom.toISODate() : null,
+ createdTo: tempCreatedTo ? tempCreatedTo.toISODate() : null,
+ })
+ onApply?.()
+ }
+
+ const handleClear = () => {
+ if (createdFrom || createdTo) {
+ executeRefetch({
+ createdFrom: null,
+ createdTo: null,
+ })
+ }
+ setTempCreatedFrom(null)
+ setTempCreatedTo(null)
+ onClearFilter?.()
+ }
+
+ const disableStartDate = (date: DateTime) => (tempCreatedTo ? date > tempCreatedTo : false)
+
+ const disableEndDate = (date: DateTime) => (tempCreatedFrom ? date < tempCreatedFrom : false)
+
+ return (
+
+
+ setTempCreatedFrom(newValue)}
+ disableFuture
+ value={tempCreatedFrom}
+ shouldDisableDate={disableStartDate}
+ slotProps={{
+ day: (dayProps) => ({
+ sx: {
+ ...(disableStartDate(dayProps.day) && {
+ backgroundColor: 'error.lighter',
+ }),
+ },
+ }),
+ }}
+ />
+ setTempCreatedTo(newValue)}
+ onError={(newError) => setError(newError)}
+ disableFuture
+ shouldDisableDate={disableEndDate}
+ slotProps={{
+ textField: {
+ helperText: errorMessage,
+ },
+ day: (dayProps) => ({
+ sx: {
+ ...(disableEndDate(dayProps.day) && {
+ backgroundColor: 'error.lighter',
+ }),
+ },
+ }),
+ }}
+ />
+
+
+
+
+
+
+ )
+}
+
+export default DateFilterComponent
diff --git a/packages/components/modules/activity-log/DateFilterComponent/types.ts b/packages/components/modules/activity-log/DateFilterComponent/types.ts
new file mode 100644
index 00000000..b4322ccf
--- /dev/null
+++ b/packages/components/modules/activity-log/DateFilterComponent/types.ts
@@ -0,0 +1,11 @@
+import { DateTime } from 'luxon'
+
+import { FetchParameters } from '../common/types'
+
+export interface DateFilterComponentProps {
+ createdFrom: DateTime | null
+ createdTo: DateTime | null
+ executeRefetch: (params: Partial) => void
+ onApply?: () => void
+ onClearFilter?: () => void
+}
diff --git a/packages/components/modules/activity-log/common/graphql/queries/ActivityLogsFragment.ts b/packages/components/modules/activity-log/common/graphql/queries/ActivityLogsFragment.ts
index 3703a992..9521f7ef 100644
--- a/packages/components/modules/activity-log/common/graphql/queries/ActivityLogsFragment.ts
+++ b/packages/components/modules/activity-log/common/graphql/queries/ActivityLogsFragment.ts
@@ -13,9 +13,20 @@ export const ActivityLogsFragmentQuery = graphql`
count: { type: "Int", defaultValue: 10 }
cursor: { type: "String" }
userName: { type: "String", defaultValue: null }
+ createdFrom: { type: "Date", defaultValue: null }
+ createdTo: { type: "Date", defaultValue: null }
) {
- activityLogs(first: $count, after: $cursor, userName: $userName)
- @connection(key: "ActivityLogs_activityLogs", filters: ["userName"]) {
+ activityLogs(
+ first: $count
+ after: $cursor
+ userName: $userName
+ createdFrom: $createdFrom
+ createdTo: $createdTo
+ )
+ @connection(
+ key: "ActivityLogs_activityLogs"
+ filters: ["userName", "createdFrom", "createdTo"]
+ ) {
edges {
node {
id
diff --git a/packages/components/modules/activity-log/common/types.ts b/packages/components/modules/activity-log/common/types.ts
index 76c05001..c699f164 100644
--- a/packages/components/modules/activity-log/common/types.ts
+++ b/packages/components/modules/activity-log/common/types.ts
@@ -8,3 +8,11 @@ export interface LogGroup {
lastActivityTimestamp: string
logs: ActivityLogNode[]
}
+
+export interface FetchParameters {
+ createdFrom: string | null
+ createdTo: string | null
+ userName: string
+ count: number
+ cursor: string | null
+}
diff --git a/packages/components/modules/activity-log/web/ActivityLogComponent/index.tsx b/packages/components/modules/activity-log/web/ActivityLogComponent/index.tsx
index a25356b6..7af89b6a 100644
--- a/packages/components/modules/activity-log/web/ActivityLogComponent/index.tsx
+++ b/packages/components/modules/activity-log/web/ActivityLogComponent/index.tsx
@@ -8,7 +8,8 @@ import { Box, Typography } from '@mui/material'
import { useForm } from 'react-hook-form'
import { SearchNotFoundState } from '../../../__shared__/web'
-import { useActivityLogs } from '../../common'
+import DateFilterChip from '../../DateFilterChip'
+import { FetchParameters, useActivityLogs } from '../../common'
import EventFilterChip from './EventFilterChip'
import DefaultLogGroups from './LogGroups'
import { ActivityLogComponentProps, EventFilterOption } from './types'
@@ -18,23 +19,38 @@ const ActivityLogComponent: FC = ({
LogGroups = DefaultLogGroups,
LogGroupsProps,
}) => {
- const [selectedFilters, setSelectedFilters] = useState(['All'])
+ const [fetchParameters, setFetchParameters] = useState({
+ createdFrom: null,
+ createdTo: null,
+ userName: '',
+ count: 10,
+ cursor: null,
+ })
+ const { logGroups, loadNext, hasNext, isLoadingNext, refetch } = useActivityLogs(queryRef)
const [isPending, startTransition] = useTransition()
+ const [selectedFilters, setSelectedFilters] = useState(['All'])
const { control, reset, watch } = useForm({ defaultValues: { search: '' } })
const searchValue = watch('search')
- const { logGroups, loadNext, hasNext, isLoadingNext, refetch } = useActivityLogs(queryRef)
+ const executeRefetch = (updatedParameters: Partial) => {
+ const newFetchParameters = { ...fetchParameters, ...updatedParameters }
+ setFetchParameters(newFetchParameters)
+ startTransition(() => {
+ refetch(newFetchParameters, { fetchPolicy: 'store-and-network' })
+ })
+ }
const handleSearchClear = () => {
startTransition(() => {
reset()
- refetch({ userName: '' })
+ executeRefetch({ userName: '' })
})
}
+
const handleSearchChange = (e: ChangeEvent) => {
const value = e.target.value || ''
startTransition(() => {
- refetch({ userName: value, count: 10, cursor: null }, { fetchPolicy: 'store-and-network' })
+ executeRefetch({ userName: value })
})
}
@@ -54,7 +70,8 @@ const ActivityLogComponent: FC = ({
onClear={handleSearchClear}
isPending={isPending}
/>
-
+
+
= ({
/>
{!isPending && searchValue && emptyLogsList && }
+ {!isPending &&
+ (fetchParameters.createdFrom != null || fetchParameters.createdTo != null) &&
+ emptyLogsList && (
+
+ )}