Improve list detail UI (#586)

This commit is contained in:
Chris Anderson 2024-12-19 20:32:09 -06:00 committed by GitHub
parent a6be349036
commit 22a6991bb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 84 additions and 32 deletions

View file

@ -36,7 +36,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",
@ -4190,9 +4190,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz",
"integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==",
"engines": {
"node": ">=14.0.0"
}
@ -18057,11 +18057,11 @@
}
},
"node_modules/react-router": {
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz",
"integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==",
"dependencies": {
"@remix-run/router": "1.15.3"
"@remix-run/router": "1.21.0"
},
"engines": {
"node": ">=14.0.0"
@ -18071,12 +18071,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz",
"integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==",
"dependencies": {
"@remix-run/router": "1.15.3",
"react-router": "6.22.3"
"@remix-run/router": "1.21.0",
"react-router": "6.28.0"
},
"engines": {
"node": ">=14.0.0"

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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'
@ -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} />
</>
}
@ -39,11 +50,44 @@ 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 })
@ -51,6 +95,7 @@ export default function ListDetail() {
setList(value)
setIsEditListOpen(false)
setIsDialogOpen(true)
setHasUnsavedChanges(false)
} catch (error: any) {
const errorMessage = error.response?.data?.error ?? error.message
setError(errorMessage)
@ -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}

View file

@ -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(' ')

26
package-lock.json generated
View file

@ -148,7 +148,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",
@ -7194,9 +7194,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz",
"integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==",
"engines": {
"node": ">=14.0.0"
}
@ -28112,11 +28112,11 @@
}
},
"node_modules/react-router": {
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz",
"integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==",
"dependencies": {
"@remix-run/router": "1.15.3"
"@remix-run/router": "1.21.0"
},
"engines": {
"node": ">=14.0.0"
@ -28126,12 +28126,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz",
"integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==",
"dependencies": {
"@remix-run/router": "1.15.3",
"react-router": "6.22.3"
"@remix-run/router": "1.21.0",
"react-router": "6.28.0"
},
"engines": {
"node": ">=14.0.0"