mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-28 11:46:02 +08:00
Improve list detail UI (#586)
This commit is contained in:
parent
a6be349036
commit
22a6991bb2
7 changed files with 84 additions and 32 deletions
26
apps/ui/package-lock.json
generated
26
apps/ui/package-lock.json
generated
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
26
package-lock.json
generated
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue