Skip to content

Commit

Permalink
Improve list detail UI (#586)
Browse files Browse the repository at this point in the history
  • Loading branch information
pushchris authored Dec 20, 2024
1 parent a6be349 commit 22a6991
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 32 deletions.
26 changes: 13 additions & 13 deletions apps/ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"react-hot-toast": "^2.4.0",
"react-i18next": "^14.1.0",
"react-popper": "^2.3.0",
"react-router-dom": "^6.4.2",
"react-router-dom": "^6.28.0",
"reactflow": "11.10.1",
"rrule": "2.7.2",
"uuid": "^9.0.0",
Expand Down
1 change: 1 addition & 0 deletions apps/ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"click_rate": "Click Rate",
"clicks": "Clicks",
"code": "Code",
"confirm_unsaved_changes": "Are you sure you want to leave? You have unsaved changes.",
"create": "Create",
"create_campaign": "Create Campaign",
"create_journey": "Create Journey",
Expand Down
1 change: 1 addition & 0 deletions apps/ui/public/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"click_rate": "Ratio de clics",
"clicks": "Clics",
"code": "Código",
"confirm_unsaved_changes": "¿Estás seguro de que deseas salir? Tienes cambios sin guardar.",
"create": "Crear",
"create_campaign": "Crear Campaña",
"create_journey": "Crear Camino",
Expand Down
58 changes: 54 additions & 4 deletions apps/ui/src/views/users/ListDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useContext, useState } from 'react'
import { useCallback, useContext, useEffect, useState } from 'react'
import api from '../../api'
import { ListContext, ProjectContext } from '../../contexts'
import { DynamicList, ListUpdateParams, Rule } from '../../types'
Expand All @@ -20,15 +20,26 @@ import { EditIcon, SendIcon, UploadIcon } from '../../ui/icons'
import { TagPicker } from '../settings/TagPicker'
import { useTranslation } from 'react-i18next'
import { Alert } from '../../ui'
import { useBlocker } from 'react-router-dom'

const RuleSection = ({ list, onRuleSave }: { list: DynamicList, onRuleSave: (rule: Rule) => void }) => {
interface RuleSectionProps {
list: DynamicList
onRuleSave: (rule: Rule) => void
onChange?: (rule: Rule) => void
}

const RuleSection = ({ list, onRuleSave, onChange }: RuleSectionProps) => {
const { t } = useTranslation()
const [rule, setRule] = useState<Rule>(list.rule)
const onSetRule = (rule: Rule) => {
setRule(rule)
onChange?.(rule)
}
return <>
<Heading size="h3" title={t('rules')} actions={
<Button size="small" onClick={() => onRuleSave(rule) }>{t('rules_save')}</Button>
} />
<RuleBuilder rule={rule} setRule={setRule} />
<RuleBuilder rule={rule} setRule={onSetRule} />
</>
}

Expand All @@ -39,18 +50,52 @@ export default function ListDetail() {
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isEditListOpen, setIsEditListOpen] = useState(false)
const [isUploadOpen, setIsUploadOpen] = useState(false)
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
const [error, setError] = useState<string | undefined>()

const state = useSearchTableState(useCallback(async params => await api.lists.users(project.id, list.id, params), [list, project]))
const route = useRoute()

useEffect(() => {
const refresh = () => {
api.lists.get(project.id, list.id)
.then(setList)
.then(() => state.reload)
.catch(() => {})
}

if (list.state !== 'loading') return
const complete = list.progress?.complete ?? 0
const total = list.progress?.total ?? 0
const percent = total > 0 ? complete / total * 100 : 0
const refreshRate = percent < 5 ? 1000 : 5000
const interval = setInterval(refresh, refreshRate)
refresh()

return () => clearInterval(interval)
}, [list.state])

const blocker = useBlocker(
({ currentLocation, nextLocation }) => hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname,
)

useEffect(() => {
if (blocker.state !== 'blocked') return
if (confirm(t('confirm_unsaved_changes'))) {
blocker.proceed()
} else {
blocker.reset()
}
}, [blocker.state])

const saveList = async ({ name, rule, published, tags }: ListUpdateParams) => {
try {
const value = await api.lists.update(project.id, list.id, { name, rule, published, tags })
setError(undefined)
setList(value)
setIsEditListOpen(false)
setIsDialogOpen(true)
setHasUnsavedChanges(false)
} catch (error: any) {
const errorMessage = error.response?.data?.error ?? error.message
setError(errorMessage)
Expand Down Expand Up @@ -92,7 +137,12 @@ export default function ListDetail() {

{error && <Alert variant="error" title="Error">{error}</Alert>}

{list.type === 'dynamic' && <RuleSection list={list} onRuleSave={async (rule: any) => await saveList({ name: list.name, rule })} />}
{list.type === 'dynamic' && (
<RuleSection
list={list}
onRuleSave={async (rule: any) => await saveList({ name: list.name, rule })}
onChange={() => setHasUnsavedChanges(true)} />
)}

<SearchTable title="Users"
{...state}
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/views/users/RuleBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function ruleDescription(preferences: Preferences, rule: Rule | GroupedRu
</code>,
)

nodes.push(' ' + operatorTypes[rule.type]?.find(ot => ot.key === rule.operator)?.label ?? rule.operator)
nodes.push(' ' + (operatorTypes[rule.type]?.find(ot => ot.key === rule.operator)?.label ?? rule.operator))

if (rule.operator !== 'empty' && rule.operator !== 'is set' && rule.operator !== 'is not set') {
nodes.push(' ')
Expand Down
26 changes: 13 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 22a6991

Please sign in to comment.