mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-25 09:13:22 +03:00
Merge branch 'main' into c--build-twenty-query-builder-for-event-emitter
This commit is contained in:
commit
09d6a64e53
2
.github/workflows/ci-front.yaml
vendored
2
.github/workflows/ci-front.yaml
vendored
@ -223,4 +223,4 @@ jobs:
|
|||||||
uses: ./.github/workflows/actions/nx-affected
|
uses: ./.github/workflows/actions/nx-affected
|
||||||
with:
|
with:
|
||||||
tag: scope:frontend
|
tag: scope:frontend
|
||||||
tasks: ${{ matrix.task }}
|
tasks: ${{ matrix.task }}
|
||||||
|
21
.github/workflows/ci-tinybird.yaml
vendored
Normal file
21
.github/workflows/ci-tinybird.yaml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: CI Tinybird
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
uses: tinybirdco/ci/.github/workflows/ci.yml@main
|
||||||
|
with:
|
||||||
|
data_project_dir: packages/twenty-tinybird
|
||||||
|
secrets:
|
||||||
|
tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }}
|
||||||
|
tb_host: https://api.eu-central-1.aws.tinybird.co
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
.nx/installation
|
.nx/installation
|
||||||
.nx/cache
|
.nx/cache
|
||||||
projectStructure.cache.json
|
|
||||||
|
|
||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
.yarn/*
|
||||||
@ -30,3 +29,4 @@ storybook-static
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
test-results/
|
test-results/
|
||||||
dump.rdb
|
dump.rdb
|
||||||
|
.tinyb
|
||||||
|
@ -20,4 +20,8 @@ Your turn 👇
|
|||||||
|
|
||||||
» 19-October-2024 by [Thefool76](https://oss.gg/thefool76) YouTube Link: [YouTube](https://youtu.be/KuAycGuW698?si=q-YxcukbbYuO8BWf)
|
» 19-October-2024 by [Thefool76](https://oss.gg/thefool76) YouTube Link: [YouTube](https://youtu.be/KuAycGuW698?si=q-YxcukbbYuO8BWf)
|
||||||
|
|
||||||
|
» 26-October-2024 by [Khaan25](https://oss.gg/Khaan25) YouTube Link: [YouTube](https://www.youtube.com/watch?v=1ruo4tTWNIg)
|
||||||
|
|
||||||
|
» 28-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Youtube Link: [Youtube](https://youtu.be/qfZyhrhCeyo)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -25,3 +25,11 @@ Your turn 👇
|
|||||||
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) blog Link: [blog](https://open.substack.com/pub/rajeevdewangan/p/comprehensive-guide-to-self-hosting?r=4lly3x&utm_campaign=post&utm_medium=web&showWelcomeOnShare=true)
|
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) blog Link: [blog](https://open.substack.com/pub/rajeevdewangan/p/comprehensive-guide-to-self-hosting?r=4lly3x&utm_campaign=post&utm_medium=web&showWelcomeOnShare=true)
|
||||||
|
|
||||||
» 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/twenty-crm-modern-solution-for-modern-problems-a0b65fec9d6c)
|
» 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/twenty-crm-modern-solution-for-modern-problems-a0b65fec9d6c)
|
||||||
|
|
||||||
|
» 27-October-2024 by [Karan0207](https://oss.gg/karan0207) blog Link: [blog](https://medium.com/@karansingh0201k/my-journey-with-twenty-the-open-source-crm-that-really-gets-it-133879af6280)
|
||||||
|
|
||||||
|
» 27-October-2024 by [Vardhaman619](https://oss.gg/vardhaman619) blog Link: [blog](https://dev.to/vardhaman619/my-experience-with-modern-open-source-crm-twenty-crm-2hen)
|
||||||
|
|
||||||
|
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-crm)
|
||||||
|
|
||||||
|
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/is-twenty-crm-the-right-tool-for-your-business-heres-my-honest-review-0d41e9d8a7eb)
|
@ -20,8 +20,14 @@ Your turn 👇
|
|||||||
|
|
||||||
» 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) blog Link: [blog](https://dev.to/sateshcharan/streamlined-self-hosting-with-twenty-crm-1-click-docker-compose-setup-188o)
|
» 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) blog Link: [blog](https://dev.to/sateshcharan/streamlined-self-hosting-with-twenty-crm-1-click-docker-compose-setup-188o)
|
||||||
|
|
||||||
|
» 24-October-2024 by [Shrey](https://oss.gg/shreykx) guide link : [https://github.com/shreykx/newfolder/blob/8046bc7373b8632b7fc2bfa28c360b86f8890a81/twentyguide.md]
|
||||||
» 23-October-2024 by [Thefool76](https://oss.gg/thefool76) blog Link: [blog](https://k5lo7h.hashnode.dev/a-detailed-guide-to-self-host-twenty-crm-on-you-local-server)
|
» 23-October-2024 by [Thefool76](https://oss.gg/thefool76) blog Link: [blog](https://k5lo7h.hashnode.dev/a-detailed-guide-to-self-host-twenty-crm-on-you-local-server)
|
||||||
|
|
||||||
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/detailed-guide-on-self-hosting-twenty-crm-on-your-server-troubleshooting-and-best-practices-1f2ca15cd6eb)
|
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/detailed-guide-on-self-hosting-twenty-crm-on-your-server-troubleshooting-and-best-practices-1f2ca15cd6eb)
|
||||||
|
|
||||||
|
» 26-October-2024 by [Yash-1511](https://oss.gg/Yash-1511) blog Link: [blog](https://medium.com/@yashp3020/a-comprehensive-guide-to-self-hosting-twenty-crm-with-docker-compose-40ea3fb4afdc)
|
||||||
---
|
---
|
||||||
|
|
||||||
|
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-self-host)
|
||||||
|
|
||||||
|
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/comprehensive-guide-to-self-hosting-twenty-crm-26e7fa36c846)
|
@ -18,4 +18,12 @@ Your turn 👇
|
|||||||
|
|
||||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
|
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
|
||||||
|
|
||||||
---
|
» 24-October-2024 by [Thefool76](https://oss.gg/thefool76) video Link: [video](https://youtube.com/shorts/lC4oqm7UlCI?si=Md-nsfK9F6Shzjkv)
|
||||||
|
|
||||||
|
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://x.com/zia_webdev/status/1850409233663115529)
|
||||||
|
|
||||||
|
» 27-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) video link: [video](https://youtube.com/shorts/OK52eaq0pAQ?feature=share)
|
||||||
|
|
||||||
|
» 28-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) video link: [video](https://youtu.be/65sOHce1gjw)
|
||||||
|
|
||||||
|
---
|
||||||
|
@ -18,7 +18,11 @@ Your turn 👇
|
|||||||
////////////////////////////
|
////////////////////////////
|
||||||
|
|
||||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
|
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
|
||||||
|
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) Figma Link: [Figma](https://www.figma.com/design/XE21QdkFuy0IJHtmW7TURa/Twenty-(rajeevDewangan)?node-id=0-1&node-type=canvas&t=BYBulCT6hpJu6E8G-0)
|
||||||
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1)
|
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1)
|
||||||
|
» 24-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Figma Link: [Figma](https://www.figma.com/design/2qlAPS3llwf8jrWKGHEf6O/Twenty-(sateshcharan)?node-id=1633-94880&t=GIceWxqyY0ajWXnZ-1)
|
||||||
|
» 28-October-2024 by [Vanshdeep Singh](https://oss.gg/Vanshdeepsingh-2232) Figma Link:[Figma](https://www.figma.com/design/akgDOb37YLUW9iWLB155EV/Twenty-(Copy)?node-id=478-19796&t=8Gz1yqls2Q3dsN9h-1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
**Side Quest**: Develop a script to facilitate the migration of data from another CRM to Twenty.
|
**Side Quest**: Develop a script to facilitate the migration of data from another CRM to Twenty.
|
||||||
**Points**: 750 Points
|
**Points**: 750 Points
|
||||||
**Proof**: Add your oss handle and record video and share link to the list below. In video show the working proof of your created script.
|
**Proof**: Add your oss handle and record video and share link to the list below. In video show the working proof of your created script.
|
||||||
|
|
||||||
@ -19,4 +19,5 @@ Your turn 👇
|
|||||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
|
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
|
||||||
|
|
||||||
» 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view)
|
» 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view)
|
||||||
---
|
|
||||||
|
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://drive.google.com/file/d/1-wgzofJaWmnMcFgZZV5uYNNgtbJKJ_1G/view?usp=sharing/)
|
||||||
|
@ -18,4 +18,6 @@ Your turn 👇
|
|||||||
|
|
||||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/)
|
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/)
|
||||||
|
|
||||||
---
|
» 25-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) template Link: [template]()
|
||||||
|
|
||||||
|
---
|
||||||
|
@ -18,4 +18,6 @@ Your turn 👇
|
|||||||
|
|
||||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/)
|
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/)
|
||||||
|
|
||||||
---
|
» 26-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) guide Link: [guide](https://dev.to/sateshcharan/supercharge-your-marketing-with-twentycrm-n8n-1hfd)
|
||||||
|
|
||||||
|
---
|
@ -18,4 +18,8 @@ Your turn 👇
|
|||||||
|
|
||||||
» 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan)
|
» 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan)
|
||||||
|
|
||||||
» 22-October-2024 by [Khaan25](https://oss.gg/Khaan25)
|
» 22-October-2024 by [Khaan25](https://oss.gg/Khaan25)
|
||||||
|
|
||||||
|
» 26-October-2024 by [Naprila](https://oss.gg/Naprila)
|
||||||
|
|
||||||
|
» 28-October-2024 by [Aditya Deshlahre](https://oss.gg/adityadeshlahre)
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"@stoplight/elements": "^8.0.5",
|
"@stoplight/elements": "^8.0.5",
|
||||||
"@swc/jest": "^0.2.29",
|
"@swc/jest": "^0.2.29",
|
||||||
"@tabler/icons-react": "^2.44.0",
|
"@tabler/icons-react": "^2.44.0",
|
||||||
|
"@tiptap/extension-hard-break": "^2.9.1",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/facepaint": "^1.2.5",
|
"@types/facepaint": "^1.2.5",
|
||||||
"@types/lodash.camelcase": "^4.3.7",
|
"@types/lodash.camelcase": "^4.3.7",
|
||||||
@ -295,7 +296,7 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
"eslint-plugin-prettier": "^5.1.2",
|
"eslint-plugin-prettier": "^5.1.2",
|
||||||
"eslint-plugin-project-structure": "^3.7.2",
|
"eslint-plugin-project-structure": "^3.9.1",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
"eslint-plugin-react-refresh": "^0.4.4",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { Loader } from '@/ui/display/loader/components/Loader';
|
import { Loader } from '@/ui/display/loader/components/Loader';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
5
packages/twenty-front/.gitignore
vendored
5
packages/twenty-front/.gitignore
vendored
@ -41,4 +41,7 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.vite/
|
.vite/
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
|
||||||
|
# eslint-plugin-project-structure
|
||||||
|
projectStructure.cache.json
|
||||||
|
@ -57,5 +57,6 @@ const config: StorybookConfig = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
logLevel: 'error',
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -29,6 +29,7 @@ initialize({
|
|||||||
with payload ${JSON.stringify(requestBody)}\n
|
with payload ${JSON.stringify(requestBody)}\n
|
||||||
This request should be mocked with MSW`);
|
This request should be mocked with MSW`);
|
||||||
},
|
},
|
||||||
|
quiet: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
|
@ -1,57 +1,45 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json",
|
"$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json",
|
||||||
|
"projectRoot": "packages/twenty-front",
|
||||||
|
"structureRoot": "src",
|
||||||
"regexParameters": {
|
"regexParameters": {
|
||||||
"camelCase": "^[a-z]+[A-Za-z0-9]+"
|
"camelCase": "^[a-z]+([A-Za-z0-9]+)+",
|
||||||
|
"kebab-case": "[a-z][a-z0-9]*(?:-[a-z0-9]+)*"
|
||||||
},
|
},
|
||||||
"structure": [
|
"structure": [
|
||||||
{
|
{ "name": "*" },
|
||||||
"name": "packages",
|
{ "name": "*", "children": [] },
|
||||||
"children": [
|
{ "name": "modules", "ruleId": "modulesFolderRule" }
|
||||||
{
|
|
||||||
"name": "twenty-front",
|
|
||||||
"children": [
|
|
||||||
{ "name": "*", "children": [] },
|
|
||||||
{ "name": "*" },
|
|
||||||
{
|
|
||||||
"name": "src",
|
|
||||||
"children": [
|
|
||||||
{ "name": "*", "children": [] },
|
|
||||||
{ "name": "*" },
|
|
||||||
{
|
|
||||||
"name": "modules",
|
|
||||||
"children": [
|
|
||||||
{ "ruleId": "moduleFolderRule" },
|
|
||||||
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"modulesFolderRule": {
|
||||||
|
"children": [
|
||||||
|
{ "ruleId": "moduleFolderRule" },
|
||||||
|
{ "name": "types", "children": [] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
"moduleFolderRule": {
|
"moduleFolderRule": {
|
||||||
"name": "^(?!utils$|hooks$|states$|types$|graphql$|components$|effect-components$|constants$|validation-schemas$|contexts$|scopes$|services$|errors$)[a-z][a-z0-9]**(?:-[a-z0-9]+)**$",
|
"name": "{kebab-case}",
|
||||||
"folderRecursionLimit": 6,
|
"folderRecursionLimit": 6,
|
||||||
"children": [
|
"children": [
|
||||||
{ "ruleId": "moduleFolderRule" },
|
{ "ruleId": "moduleFolderRule" },
|
||||||
{ "name": "hooks", "ruleId": "hooksLeafFolderRule" },
|
{ "name": "hooks", "ruleId": "hooksLeafFolderRule" },
|
||||||
{ "name": "utils", "ruleId": "utilsLeafFolderRule" },
|
{ "name": "utils", "ruleId": "utilsLeafFolderRule" },
|
||||||
{ "name": "states", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "states", "children": [] },
|
||||||
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "types", "children": [] },
|
||||||
{ "name": "graphql", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "graphql", "children": [] },
|
||||||
{ "name": "components", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "components", "children": [] },
|
||||||
{ "name": "effect-components", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "effect-components", "children": [] },
|
||||||
{ "name": "constants", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "constants", "children": [] },
|
||||||
{ "name": "validation-schemas", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "validation-schemas", "children": [] },
|
||||||
{ "name": "contexts", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "contexts", "children": [] },
|
||||||
{ "name": "scopes", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "scopes", "children": [] },
|
||||||
{ "name": "services", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "services", "children": [] },
|
||||||
{ "name": "errors", "ruleId": "doNotCheckLeafFolderRule" }
|
{ "name": "errors", "children": [] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"hooksLeafFolderRule": {
|
"hooksLeafFolderRule": {
|
||||||
"folderRecursionLimit": 2,
|
"folderRecursionLimit": 2,
|
||||||
"children": [
|
"children": [
|
||||||
@ -63,12 +51,8 @@
|
|||||||
{ "name": "internal", "ruleId": "hooksLeafFolderRule" }
|
{ "name": "internal", "ruleId": "hooksLeafFolderRule" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doNotCheckLeafFolderRule": {
|
|
||||||
"folderRecursionLimit": 1,
|
|
||||||
"children": [{ "name": "*" }, { "name": "*", "children": [] }]
|
|
||||||
},
|
|
||||||
"utilsLeafFolderRule": {
|
"utilsLeafFolderRule": {
|
||||||
"folderRecursionLimit": 1,
|
|
||||||
"children": [
|
"children": [
|
||||||
{ "name": "{camelCase}.ts" },
|
{ "name": "{camelCase}.ts" },
|
||||||
{
|
{
|
||||||
|
@ -25,9 +25,9 @@ const jestConfig: JestConfigWithTsJest = {
|
|||||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 59,
|
statements: 58,
|
||||||
lines: 55,
|
lines: 55,
|
||||||
functions: 49,
|
functions: 47,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
collectCoverageFrom: ['<rootDir>/src/**/*.ts'],
|
collectCoverageFrom: ['<rootDir>/src/**/*.ts'],
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import { jest } from '@storybook/jest';
|
import { jest } from '@storybook/jest';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { graphql, HttpResponse } from 'msw';
|
import { HttpResponse, graphql } from 'msw';
|
||||||
import { HelmetProvider } from 'react-helmet-async';
|
import { HelmetProvider } from 'react-helmet-async';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
import { IconsProvider } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||||
import indexAppPath from '@/navigation/utils/indexAppPath';
|
import indexAppPath from '@/navigation/utils/indexAppPath';
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||||
|
|
||||||
import { AppRouter } from '@/app/components/AppRouter';
|
import { AppRouter } from '@/app/components/AppRouter';
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { IconsProvider } from 'twenty-ui';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { mockedUserData } from '~/testing/mock-data/users';
|
import { mockedUserData } from '~/testing/mock-data/users';
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
export type Maybe<T> = T | null;
|
export type Maybe<T> = T | null;
|
||||||
export type InputMaybe<T> = Maybe<T>;
|
export type InputMaybe<T> = Maybe<T>;
|
||||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||||
@ -155,6 +155,11 @@ export type ClientConfig = {
|
|||||||
support: Support;
|
support: Support;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaInput = {
|
||||||
|
/** Step JSON format */
|
||||||
|
step: Scalars['JSON'];
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateServerlessFunctionInput = {
|
export type CreateServerlessFunctionInput = {
|
||||||
description?: InputMaybe<Scalars['String']>;
|
description?: InputMaybe<Scalars['String']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
@ -424,6 +429,7 @@ export type Mutation = {
|
|||||||
authorizeApp: AuthorizeApp;
|
authorizeApp: AuthorizeApp;
|
||||||
challenge: LoginToken;
|
challenge: LoginToken;
|
||||||
checkoutSession: SessionEntity;
|
checkoutSession: SessionEntity;
|
||||||
|
computeStepOutputSchema: Scalars['JSON'];
|
||||||
createOIDCIdentityProvider: SetupSsoOutput;
|
createOIDCIdentityProvider: SetupSsoOutput;
|
||||||
createOneAppToken: AppToken;
|
createOneAppToken: AppToken;
|
||||||
createOneObject: Object;
|
createOneObject: Object;
|
||||||
@ -509,6 +515,11 @@ export type MutationCheckoutSessionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationComputeStepOutputSchemaArgs = {
|
||||||
|
input: ComputeStepOutputSchemaInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOidcIdentityProviderArgs = {
|
export type MutationCreateOidcIdentityProviderArgs = {
|
||||||
input: SetupOidcSsoInput;
|
input: SetupOidcSsoInput;
|
||||||
};
|
};
|
||||||
@ -1155,12 +1166,13 @@ export type UpdateObjectPayload = {
|
|||||||
icon?: InputMaybe<Scalars['String']>;
|
icon?: InputMaybe<Scalars['String']>;
|
||||||
imageIdentifierFieldMetadataId?: InputMaybe<Scalars['String']>;
|
imageIdentifierFieldMetadataId?: InputMaybe<Scalars['String']>;
|
||||||
isActive?: InputMaybe<Scalars['Boolean']>;
|
isActive?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
isLabelSyncedWithName?: InputMaybe<Scalars['Boolean']>;
|
||||||
labelIdentifierFieldMetadataId?: InputMaybe<Scalars['String']>;
|
labelIdentifierFieldMetadataId?: InputMaybe<Scalars['String']>;
|
||||||
labelPlural?: InputMaybe<Scalars['String']>;
|
labelPlural?: InputMaybe<Scalars['String']>;
|
||||||
labelSingular?: InputMaybe<Scalars['String']>;
|
labelSingular?: InputMaybe<Scalars['String']>;
|
||||||
namePlural?: InputMaybe<Scalars['String']>;
|
namePlural?: InputMaybe<Scalars['String']>;
|
||||||
nameSingular?: InputMaybe<Scalars['String']>;
|
nameSingular?: InputMaybe<Scalars['String']>;
|
||||||
shouldSyncLabelAndName?: InputMaybe<Scalars['Boolean']>;
|
shortcut?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateOneObjectInput = {
|
export type UpdateOneObjectInput = {
|
||||||
@ -1271,6 +1283,7 @@ export type Workspace = {
|
|||||||
displayName?: Maybe<Scalars['String']>;
|
displayName?: Maybe<Scalars['String']>;
|
||||||
domainName?: Maybe<Scalars['String']>;
|
domainName?: Maybe<Scalars['String']>;
|
||||||
featureFlags?: Maybe<Array<FeatureFlag>>;
|
featureFlags?: Maybe<Array<FeatureFlag>>;
|
||||||
|
hasValidEntrepriseKey: Scalars['Boolean'];
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
inviteHash?: Maybe<Scalars['String']>;
|
inviteHash?: Maybe<Scalars['String']>;
|
||||||
isPublicInviteLinkEnabled: Scalars['Boolean'];
|
isPublicInviteLinkEnabled: Scalars['Boolean'];
|
||||||
@ -1470,6 +1483,7 @@ export type Object = {
|
|||||||
indexMetadatas: ObjectIndexMetadatasConnection;
|
indexMetadatas: ObjectIndexMetadatasConnection;
|
||||||
isActive: Scalars['Boolean'];
|
isActive: Scalars['Boolean'];
|
||||||
isCustom: Scalars['Boolean'];
|
isCustom: Scalars['Boolean'];
|
||||||
|
isLabelSyncedWithName: Scalars['Boolean'];
|
||||||
isRemote: Scalars['Boolean'];
|
isRemote: Scalars['Boolean'];
|
||||||
isSystem: Scalars['Boolean'];
|
isSystem: Scalars['Boolean'];
|
||||||
labelIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
labelIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
||||||
@ -1477,7 +1491,7 @@ export type Object = {
|
|||||||
labelSingular: Scalars['String'];
|
labelSingular: Scalars['String'];
|
||||||
namePlural: Scalars['String'];
|
namePlural: Scalars['String'];
|
||||||
nameSingular: Scalars['String'];
|
nameSingular: Scalars['String'];
|
||||||
shouldSyncLabelAndName: Scalars['Boolean'];
|
shortcut?: Maybe<Scalars['String']>;
|
||||||
updatedAt: Scalars['DateTime'];
|
updatedAt: Scalars['DateTime'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1675,7 +1689,7 @@ export type ImpersonateMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||||
|
|
||||||
export type RenewTokenMutationVariables = Exact<{
|
export type RenewTokenMutationVariables = Exact<{
|
||||||
appToken: Scalars['String'];
|
appToken: Scalars['String'];
|
||||||
@ -1708,7 +1722,7 @@ export type VerifyMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||||
|
|
||||||
export type CheckUserExistsQueryVariables = Exact<{
|
export type CheckUserExistsQueryVariables = Exact<{
|
||||||
email: Scalars['String'];
|
email: Scalars['String'];
|
||||||
@ -1795,7 +1809,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key:
|
|||||||
|
|
||||||
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdpType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdpType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
||||||
|
|
||||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> };
|
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> };
|
||||||
|
|
||||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@ -1812,7 +1826,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
|||||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } };
|
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } };
|
||||||
|
|
||||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
@ -1821,6 +1835,13 @@ export type ActivateWorkflowVersionMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean };
|
export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean };
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaMutationVariables = Exact<{
|
||||||
|
input: ComputeStepOutputSchemaInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaMutation = { __typename?: 'Mutation', computeStepOutputSchema: any };
|
||||||
|
|
||||||
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
}>;
|
}>;
|
||||||
@ -2042,6 +2063,7 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
allowImpersonation
|
allowImpersonation
|
||||||
activationStatus
|
activationStatus
|
||||||
isPublicInviteLinkEnabled
|
isPublicInviteLinkEnabled
|
||||||
|
hasValidEntrepriseKey
|
||||||
featureFlags {
|
featureFlags {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
@ -3441,6 +3463,37 @@ export function useActivateWorkflowVersionMutation(baseOptions?: Apollo.Mutation
|
|||||||
export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>;
|
export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>;
|
||||||
export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>;
|
export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>;
|
||||||
export type ActivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>;
|
export type ActivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>;
|
||||||
|
export const ComputeStepOutputSchemaDocument = gql`
|
||||||
|
mutation ComputeStepOutputSchema($input: ComputeStepOutputSchemaInput!) {
|
||||||
|
computeStepOutputSchema(input: $input)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ComputeStepOutputSchemaMutationFn = Apollo.MutationFunction<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useComputeStepOutputSchemaMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useComputeStepOutputSchemaMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useComputeStepOutputSchemaMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [computeStepOutputSchemaMutation, { data, loading, error }] = useComputeStepOutputSchemaMutation({
|
||||||
|
* variables: {
|
||||||
|
* input: // value for 'input'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useComputeStepOutputSchemaMutation(baseOptions?: Apollo.MutationHookOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>(ComputeStepOutputSchemaDocument, options);
|
||||||
|
}
|
||||||
|
export type ComputeStepOutputSchemaMutationHookResult = ReturnType<typeof useComputeStepOutputSchemaMutation>;
|
||||||
|
export type ComputeStepOutputSchemaMutationResult = Apollo.MutationResult<ComputeStepOutputSchemaMutation>;
|
||||||
|
export type ComputeStepOutputSchemaMutationOptions = Apollo.BaseMutationOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>;
|
||||||
export const DeactivateWorkflowVersionDocument = gql`
|
export const DeactivateWorkflowVersionDocument = gql`
|
||||||
mutation DeactivateWorkflowVersion($workflowVersionId: String!) {
|
mutation DeactivateWorkflowVersion($workflowVersionId: String!) {
|
||||||
deactivateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
deactivateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
||||||
|
@ -2,6 +2,7 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
|||||||
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
|
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
|
||||||
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
|
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
|
||||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||||
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
|
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
|
||||||
import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTokenState';
|
import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTokenState';
|
||||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||||
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const useCleanRecoilState = () => {
|
export const useCleanRecoilState = () => {
|
||||||
|
@ -10,6 +10,6 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */
|
/* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */
|
||||||
.grecaptcha-badge {
|
.grecaptcha-badge {
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||||
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
|
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
@ -12,17 +12,15 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl
|
|||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { IconTrash, isDefined } from 'twenty-ui';
|
import { IconTrash, isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
export const DeleteRecordsActionEffect = ({
|
export const DeleteRecordsActionEffect = ({
|
||||||
position,
|
position,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
actionMenuType,
|
|
||||||
}: {
|
}: {
|
||||||
position: number;
|
position: number;
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
actionMenuType: ActionMenuType;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||||
|
|
||||||
@ -93,6 +91,9 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT &&
|
contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT &&
|
||||||
contextStoreNumberOfSelectedRecords > 0;
|
contextStoreNumberOfSelectedRecords > 0;
|
||||||
|
|
||||||
|
const { isInRightDrawer, onActionExecutedCallback } =
|
||||||
|
useContext(ActionMenuContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
addActionMenuEntry({
|
addActionMenuEntry({
|
||||||
@ -120,17 +121,14 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
} can be recovered from the Options menu.`}
|
} can be recovered from the Options menu.`}
|
||||||
onConfirmClick={() => {
|
onConfirmClick={() => {
|
||||||
handleDeleteClick();
|
handleDeleteClick();
|
||||||
|
onActionExecutedCallback?.();
|
||||||
if (actionMenuType === 'recordShow') {
|
if (isInRightDrawer) {
|
||||||
closeRightDrawer();
|
closeRightDrawer();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
deleteButtonText={`Delete ${
|
deleteButtonText={`Delete ${
|
||||||
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
|
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
|
||||||
}`}
|
}`}
|
||||||
modalVariant={
|
|
||||||
actionMenuType === 'recordShow' ? 'tertiary' : 'primary'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -142,13 +140,14 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
removeActionMenuEntry('delete');
|
removeActionMenuEntry('delete');
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
actionMenuType,
|
|
||||||
addActionMenuEntry,
|
addActionMenuEntry,
|
||||||
canDelete,
|
canDelete,
|
||||||
closeRightDrawer,
|
closeRightDrawer,
|
||||||
contextStoreNumberOfSelectedRecords,
|
contextStoreNumberOfSelectedRecords,
|
||||||
handleDeleteClick,
|
handleDeleteClick,
|
||||||
isDeleteRecordsModalOpen,
|
isDeleteRecordsModalOpen,
|
||||||
|
isInRightDrawer,
|
||||||
|
onActionExecutedCallback,
|
||||||
position,
|
position,
|
||||||
removeActionMenuEntry,
|
removeActionMenuEntry,
|
||||||
]);
|
]);
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
|
||||||
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
|
||||||
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
||||||
|
|
||||||
const actionEffects = [ExportRecordsActionEffect, DeleteRecordsActionEffect];
|
|
||||||
|
|
||||||
export const MultipleRecordsActionMenuEntriesSetter = ({
|
|
||||||
objectMetadataItem,
|
|
||||||
actionMenuType,
|
|
||||||
}: {
|
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
|
||||||
actionMenuType: ActionMenuType;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{actionEffects.map((ActionEffect, index) => (
|
|
||||||
<ActionEffect
|
|
||||||
key={index}
|
|
||||||
position={index}
|
|
||||||
objectMetadataItem={objectMetadataItem}
|
|
||||||
actionMenuType={actionMenuType}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,16 +1,23 @@
|
|||||||
import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter';
|
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
||||||
import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter';
|
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
||||||
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
|
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';
|
||||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordActionMenuEntriesSetter = ({
|
const singleRecordActionEffects = [
|
||||||
actionMenuType,
|
ManageFavoritesActionEffect,
|
||||||
}: {
|
ExportRecordsActionEffect,
|
||||||
actionMenuType: ActionMenuType;
|
DeleteRecordsActionEffect,
|
||||||
}) => {
|
];
|
||||||
|
|
||||||
|
const multipleRecordActionEffects = [
|
||||||
|
ExportRecordsActionEffect,
|
||||||
|
DeleteRecordsActionEffect,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RecordActionMenuEntriesSetter = () => {
|
||||||
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
|
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
|
||||||
contextStoreNumberOfSelectedRecordsComponentState,
|
contextStoreNumberOfSelectedRecordsComponentState,
|
||||||
);
|
);
|
||||||
@ -33,19 +40,20 @@ export const RecordActionMenuEntriesSetter = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextStoreNumberOfSelectedRecords === 1) {
|
const actions =
|
||||||
return (
|
contextStoreNumberOfSelectedRecords === 1
|
||||||
<SingleRecordActionMenuEntriesSetter
|
? singleRecordActionEffects
|
||||||
objectMetadataItem={objectMetadataItem}
|
: multipleRecordActionEffects;
|
||||||
actionMenuType={actionMenuType}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultipleRecordsActionMenuEntriesSetter
|
<>
|
||||||
objectMetadataItem={objectMetadataItem}
|
{actions.map((ActionEffect, index) => (
|
||||||
actionMenuType={actionMenuType}
|
<ActionEffect
|
||||||
/>
|
key={index}
|
||||||
|
position={index}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
|
||||||
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
|
||||||
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';
|
|
||||||
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
||||||
|
|
||||||
export const SingleRecordActionMenuEntriesSetter = ({
|
|
||||||
objectMetadataItem,
|
|
||||||
actionMenuType,
|
|
||||||
}: {
|
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
|
||||||
actionMenuType: ActionMenuType;
|
|
||||||
}) => {
|
|
||||||
const actionEffects = [
|
|
||||||
ManageFavoritesActionEffect,
|
|
||||||
ExportRecordsActionEffect,
|
|
||||||
DeleteRecordsActionEffect,
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{actionEffects.map((ActionEffect, index) => (
|
|
||||||
<ActionEffect
|
|
||||||
key={index}
|
|
||||||
position={index}
|
|
||||||
objectMetadataItem={objectMetadataItem}
|
|
||||||
actionMenuType={actionMenuType}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -3,16 +3,12 @@ import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMen
|
|||||||
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
|
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
|
||||||
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
|
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
|
||||||
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
|
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
|
||||||
|
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||||
|
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
|
||||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordIndexActionMenu = ({
|
export const RecordIndexActionMenu = () => {
|
||||||
actionMenuId,
|
|
||||||
}: {
|
|
||||||
actionMenuId: string;
|
|
||||||
}) => {
|
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||||
contextStoreCurrentObjectMetadataIdComponentState,
|
contextStoreCurrentObjectMetadataIdComponentState,
|
||||||
);
|
);
|
||||||
@ -20,15 +16,18 @@ export const RecordIndexActionMenu = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextStoreCurrentObjectMetadataId && (
|
{contextStoreCurrentObjectMetadataId && (
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
<ActionMenuContext.Provider
|
||||||
value={{ instanceId: actionMenuId }}
|
value={{
|
||||||
|
isInRightDrawer: false,
|
||||||
|
onActionExecutedCallback: () => {},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RecordIndexActionMenuBar />
|
<RecordIndexActionMenuBar />
|
||||||
<RecordIndexActionMenuDropdown />
|
<RecordIndexActionMenuDropdown />
|
||||||
<ActionMenuConfirmationModals />
|
<ActionMenuConfirmationModals />
|
||||||
<RecordIndexActionMenuEffect />
|
<RecordIndexActionMenuEffect />
|
||||||
<RecordActionMenuEntriesSetter actionMenuType="recordIndex" />
|
<RecordActionMenuEntriesSetter />
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
</ActionMenuContext.Provider>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||||
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
@ -25,6 +27,9 @@ export const RecordIndexActionMenuEffect = () => {
|
|||||||
`action-menu-dropdown-${actionMenuId}`,
|
`action-menu-dropdown-${actionMenuId}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const { isRightDrawerOpen } = useRightDrawer();
|
||||||
|
|
||||||
|
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) {
|
if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) {
|
||||||
@ -43,5 +48,11 @@ export const RecordIndexActionMenuEffect = () => {
|
|||||||
isDropdownOpen,
|
isDropdownOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isRightDrawerOpen || isCommandMenuOpened) {
|
||||||
|
closeActionBar();
|
||||||
|
}
|
||||||
|
}, [closeActionBar, isRightDrawerOpen, isCommandMenuOpened]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -1,30 +1,53 @@
|
|||||||
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
||||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||||
import { RecordShowActionMenuBar } from '@/action-menu/components/RecordShowActionMenuBar';
|
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||||
|
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
|
||||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader';
|
||||||
|
|
||||||
export const RecordShowActionMenu = ({
|
export const RecordShowActionMenu = ({
|
||||||
actionMenuId,
|
isFavorite,
|
||||||
|
handleFavoriteButtonClick,
|
||||||
|
record,
|
||||||
|
objectMetadataItem,
|
||||||
|
objectNameSingular,
|
||||||
}: {
|
}: {
|
||||||
actionMenuId: string;
|
isFavorite: boolean;
|
||||||
|
handleFavoriteButtonClick: () => void;
|
||||||
|
record: ObjectRecord | undefined;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
objectNameSingular: string;
|
||||||
}) => {
|
}) => {
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||||
contextStoreCurrentObjectMetadataIdComponentState,
|
contextStoreCurrentObjectMetadataIdComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: refactor RecordShowPageBaseHeader to use the context store
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextStoreCurrentObjectMetadataId && (
|
{contextStoreCurrentObjectMetadataId && (
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
<ActionMenuContext.Provider
|
||||||
value={{ instanceId: actionMenuId }}
|
value={{
|
||||||
|
isInRightDrawer: false,
|
||||||
|
onActionExecutedCallback: () => {},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RecordShowActionMenuBar />
|
<RecordShowPageBaseHeader
|
||||||
|
{...{
|
||||||
|
isFavorite,
|
||||||
|
handleFavoriteButtonClick,
|
||||||
|
record,
|
||||||
|
objectMetadataItem,
|
||||||
|
objectNameSingular,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ActionMenuConfirmationModals />
|
<ActionMenuConfirmationModals />
|
||||||
<RecordActionMenuEntriesSetter actionMenuType="recordShow" />
|
<RecordActionMenuEntriesSetter />
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
</ActionMenuContext.Provider>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
||||||
|
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||||
|
import { RecordShowRightDrawerActionMenuBar } from '@/action-menu/components/RecordShowRightDrawerActionMenuBar';
|
||||||
|
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||||
|
|
||||||
|
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
|
export const RecordShowRightDrawerActionMenu = () => {
|
||||||
|
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||||
|
contextStoreCurrentObjectMetadataIdComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{contextStoreCurrentObjectMetadataId && (
|
||||||
|
<ActionMenuContext.Provider
|
||||||
|
value={{
|
||||||
|
isInRightDrawer: true,
|
||||||
|
onActionExecutedCallback: () => {},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordShowRightDrawerActionMenuBar />
|
||||||
|
<ActionMenuConfirmationModals />
|
||||||
|
<RecordActionMenuEntriesSetter />
|
||||||
|
</ActionMenuContext.Provider>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -2,7 +2,7 @@ import { RecordShowActionMenuBarEntry } from '@/action-menu/components/RecordSho
|
|||||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordShowActionMenuBar = () => {
|
export const RecordShowRightDrawerActionMenuBar = () => {
|
||||||
const actionMenuEntries = useRecoilComponentValueV2(
|
const actionMenuEntries = useRecoilComponentValueV2(
|
||||||
actionMenuEntriesComponentSelector,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
@ -2,7 +2,7 @@ import { expect, jest } from '@storybook/jest';
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
||||||
import { RecordShowActionMenuBar } from '@/action-menu/components/RecordShowActionMenuBar';
|
import { RecordShowRightDrawerActionMenuBar } from '@/action-menu/components/RecordShowRightDrawerActionMenuBar';
|
||||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
@ -20,9 +20,9 @@ const deleteMock = jest.fn();
|
|||||||
const addToFavoritesMock = jest.fn();
|
const addToFavoritesMock = jest.fn();
|
||||||
const exportMock = jest.fn();
|
const exportMock = jest.fn();
|
||||||
|
|
||||||
const meta: Meta<typeof RecordShowActionMenuBar> = {
|
const meta: Meta<typeof RecordShowRightDrawerActionMenuBar> = {
|
||||||
title: 'Modules/ActionMenu/RecordShowActionMenuBar',
|
title: 'Modules/ActionMenu/RecordShowRightDrawerActionMenuBar',
|
||||||
component: RecordShowActionMenuBar,
|
component: RecordShowRightDrawerActionMenuBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<RecoilRoot
|
<RecoilRoot
|
||||||
@ -98,7 +98,7 @@ const meta: Meta<typeof RecordShowActionMenuBar> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof RecordShowActionMenuBar>;
|
type Story = StoryObj<typeof RecordShowRightDrawerActionMenuBar>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
type ActionMenuContextType = {
|
||||||
|
isInRightDrawer: boolean;
|
||||||
|
onActionExecutedCallback: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActionMenuContext = createContext<ActionMenuContextType>({
|
||||||
|
isInRightDrawer: false,
|
||||||
|
onActionExecutedCallback: () => {},
|
||||||
|
});
|
@ -1 +0,0 @@
|
|||||||
export type ActionMenuType = 'recordIndex' | 'recordShow';
|
|
@ -8,6 +8,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
H3Title,
|
H3Title,
|
||||||
|
Section,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
||||||
@ -21,7 +22,6 @@ import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
|||||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
|
||||||
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
@ -4,7 +4,7 @@ import { differenceInSeconds, endOfDay, format } from 'date-fns';
|
|||||||
|
|
||||||
import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow';
|
import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow';
|
||||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
import { CardContent } from 'twenty-ui';
|
||||||
import { TimelineCalendarEvent } from '~/generated/graphql';
|
import { TimelineCalendarEvent } from '~/generated/graphql';
|
||||||
|
|
||||||
type CalendarDayCardContentProps = {
|
type CalendarDayCardContentProps = {
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconLock,
|
IconLock,
|
||||||
isDefined,
|
isDefined,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor';
|
import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor';
|
||||||
@ -18,8 +20,6 @@ import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendar
|
|||||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||||
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
|
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
|
||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
|
||||||
import {
|
import {
|
||||||
CalendarChannelVisibility,
|
CalendarChannelVisibility,
|
||||||
TimelineCalendarEvent,
|
TimelineCalendarEvent,
|
||||||
|
@ -2,7 +2,7 @@ import { useContext } from 'react';
|
|||||||
|
|
||||||
import { CalendarDayCardContent } from '@/activities/calendar/components/CalendarDayCardContent';
|
import { CalendarDayCardContent } from '@/activities/calendar/components/CalendarDayCardContent';
|
||||||
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
import { Card } from 'twenty-ui';
|
||||||
|
|
||||||
type CalendarMonthCardProps = {
|
type CalendarMonthCardProps = {
|
||||||
dayTimes: number[];
|
dayTimes: number[];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Card } from '@/ui/layout/card/components/Card';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { Card } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledList = styled(Card)`
|
const StyledList = styled(Card)`
|
||||||
& > :not(:last-child) {
|
& > :not(:last-child) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { CardContent } from 'twenty-ui';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const StyledRowContent = styled(CardContent)<{
|
const StyledRowContent = styled(CardContent)<{
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
|
||||||
import {
|
import {
|
||||||
AnimatedPlaceholder,
|
AnimatedPlaceholder,
|
||||||
AnimatedPlaceholderEmptyContainer,
|
AnimatedPlaceholderEmptyContainer,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
Loader,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
export const EmailLoader = ({ loadingText }: { loadingText?: string }) => (
|
export const EmailLoader = ({ loadingText }: { loadingText?: string }) => (
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
H1Title,
|
H1Title,
|
||||||
H1TitleFontColor,
|
H1TitleFontColor,
|
||||||
|
Section,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { ActivityList } from '@/activities/components/ActivityList';
|
import { ActivityList } from '@/activities/components/ActivityList';
|
||||||
@ -20,7 +21,6 @@ import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/quer
|
|||||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
|
||||||
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';
|
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui';
|
import {
|
||||||
|
Checkbox,
|
||||||
|
CheckboxShape,
|
||||||
|
IconCalendar,
|
||||||
|
OverflowingTextWithTooltip,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||||
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
||||||
import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox';
|
|
||||||
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
||||||
|
|
||||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
import { Card } from 'twenty-ui';
|
||||||
|
|
||||||
type EventCardProps = {
|
type EventCardProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -10,12 +10,12 @@ import { previousUrlState } from '@/auth/states/previousUrlState';
|
|||||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||||
import { workspacesState } from '@/auth/states/workspaces';
|
import { workspacesState } from '@/auth/states/workspaces';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ApolloFactory, Options } from '../services/apollo.factory';
|
import { ApolloFactory, Options } from '../services/apollo.factory';
|
||||||
|
|
||||||
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||||
|
@ -27,11 +27,17 @@ export const GotoHotkeysEffectsProvider = () => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => (
|
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => {
|
||||||
<GoToHotkeyItemEffect
|
if (!objectMetadataItem.shortcut) {
|
||||||
key={`go-to-hokey-item-${objectMetadataItem.id}`}
|
return null;
|
||||||
hotkey={objectMetadataItem.namePlural[0]}
|
}
|
||||||
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
|
|
||||||
/>
|
return (
|
||||||
));
|
<GoToHotkeyItemEffect
|
||||||
|
key={`go-to-hokey-item-${objectMetadataItem.id}`}
|
||||||
|
hotkey={objectMetadataItem.shortcut}
|
||||||
|
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconCheckbox } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import {
|
import {
|
||||||
@ -21,6 +20,7 @@ import { AppPath } from '@/types/AppPath';
|
|||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
import { IconCheckbox } from 'twenty-ui';
|
||||||
import { useCleanRecoilState } from '~/hooks/useCleanRecoilState';
|
import { useCleanRecoilState } from '~/hooks/useCleanRecoilState';
|
||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
||||||
@ -115,7 +115,7 @@ export const PageChangeEffect = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(AppPath.CreateWorkspace): {
|
case isMatchingLocation(AppPath.CreateWorkspace): {
|
||||||
setHotkeyScope(PageHotkeyScope.CreateWokspace);
|
setHotkeyScope(PageHotkeyScope.CreateWorkspace);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(AppPath.SyncEmails): {
|
case isMatchingLocation(AppPath.SyncEmails): {
|
||||||
|
@ -2,14 +2,16 @@ import { FooterNote } from '@/auth/sign-in-up/components/FooterNote';
|
|||||||
import { HorizontalSeparator } from '@/auth/sign-in-up/components/HorizontalSeparator';
|
import { HorizontalSeparator } from '@/auth/sign-in-up/components/HorizontalSeparator';
|
||||||
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
import { SignInUpMode, useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
import { SignInUpMode, useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
import {
|
||||||
|
useSignInUpForm,
|
||||||
|
validationSchema,
|
||||||
|
} from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||||
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
||||||
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
||||||
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
||||||
import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState';
|
import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState';
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
||||||
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -23,7 +25,9 @@ import {
|
|||||||
IconGoogle,
|
IconGoogle,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconMicrosoft,
|
IconMicrosoft,
|
||||||
|
Loader,
|
||||||
MainButton,
|
MainButton,
|
||||||
|
StyledText,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
@ -127,7 +131,8 @@ export const SignInUpForm = () => {
|
|||||||
|
|
||||||
const isEmailStepSubmitButtonDisabledCondition =
|
const isEmailStepSubmitButtonDisabledCondition =
|
||||||
signInUpStep === SignInUpStep.Email &&
|
signInUpStep === SignInUpStep.Email &&
|
||||||
(form.watch('email')?.length === 0 || shouldWaitForCaptchaToken);
|
(!validationSchema.shape.email.safeParse(form.watch('email')).success ||
|
||||||
|
shouldWaitForCaptchaToken);
|
||||||
|
|
||||||
// TODO: isValid is actually a proxy function. If it is not rendered the first time, react might not trigger re-renders
|
// TODO: isValid is actually a proxy function. If it is not rendered the first time, react might not trigger re-renders
|
||||||
// We make the isValid check synchronous and update a reactState to make sure this does not happen
|
// We make the isValid check synchronous and update a reactState to make sure this does not happen
|
||||||
@ -183,7 +188,9 @@ export const SignInUpForm = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<HorizontalSeparator visible={true} />
|
{(authProviders.google ||
|
||||||
|
authProviders.microsoft ||
|
||||||
|
authProviders.sso) && <HorizontalSeparator visible />}
|
||||||
|
|
||||||
{authProviders.password &&
|
{authProviders.password &&
|
||||||
(signInUpStep === SignInUpStep.Password ||
|
(signInUpStep === SignInUpStep.Password ||
|
||||||
@ -263,6 +270,12 @@ export const SignInUpForm = () => {
|
|||||||
disableHotkeys
|
disableHotkeys
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
|
{signInUpMode === SignInUpMode.SignUp && (
|
||||||
|
<StyledText
|
||||||
|
text={'At least 8 characters long.'}
|
||||||
|
color={theme.font.color.secondary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</StyledInputContainer>
|
</StyledInputContainer>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -5,20 +5,20 @@ import { useParams, useSearchParams } from 'react-router-dom';
|
|||||||
import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
|
||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { useAuth } from '../../hooks/useAuth';
|
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||||
|
import { availableSSOIdentityProvidersState } from '@/auth/states/availableWorkspacesForSSO';
|
||||||
import {
|
import {
|
||||||
SignInUpStep,
|
SignInUpStep,
|
||||||
signInUpStepState,
|
signInUpStepState,
|
||||||
} from '@/auth/states/signInUpStepState';
|
} from '@/auth/states/signInUpStepState';
|
||||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { availableSSOIdentityProvidersState } from '@/auth/states/availableWorkspacesForSSO';
|
import { useAuth } from '../../hooks/useAuth';
|
||||||
|
|
||||||
export enum SignInUpMode {
|
export enum SignInUpMode {
|
||||||
SignIn = 'sign-in',
|
SignIn = 'sign-in',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
||||||
|
|
||||||
const validationSchema = z
|
export const validationSchema = z
|
||||||
.object({
|
.object({
|
||||||
exist: z.boolean(),
|
exist: z.boolean(),
|
||||||
email: z.string().trim().email('Email must be a valid email'),
|
email: z.string().trim().email('Email must be a valid email'),
|
||||||
|
@ -3,11 +3,12 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
||||||
|
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useGetWorkspaceFromInviteHashQuery } from '~/generated/graphql';
|
import { useGetWorkspaceFromInviteHashQuery } from '~/generated/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export type CurrentWorkspace = Pick<
|
|||||||
| 'currentBillingSubscription'
|
| 'currentBillingSubscription'
|
||||||
| 'workspaceMembersCount'
|
| 'workspaceMembersCount'
|
||||||
| 'isPublicInviteLinkEnabled'
|
| 'isPublicInviteLinkEnabled'
|
||||||
|
| 'hasValidEntrepriseKey'
|
||||||
| 'metadataVersion'
|
| 'metadataVersion'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS
|
|||||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
import { Command, CommandType } from '@/command-menu/types/Command';
|
import { Command, CommandType } from '@/command-menu/types/Command';
|
||||||
import { Company } from '@/companies/types/Company';
|
import { Company } from '@/companies/types/Company';
|
||||||
|
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
|
||||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
||||||
@ -287,6 +288,14 @@ export const CommandMenu = () => {
|
|||||||
: true) && cmd.type === CommandType.Create,
|
: true) && cmd.type === CommandType.Create,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const matchingActionCommands = commandMenuCommands.filter(
|
||||||
|
(cmd) =>
|
||||||
|
(deferredCommandMenuSearch.length > 0
|
||||||
|
? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
|
||||||
|
checkInLabels(cmd, deferredCommandMenuSearch)
|
||||||
|
: true) && cmd.type === CommandType.Action,
|
||||||
|
);
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [commandMenuRef],
|
refs: [commandMenuRef],
|
||||||
callback: closeCommandMenu,
|
callback: closeCommandMenu,
|
||||||
@ -312,6 +321,7 @@ export const CommandMenu = () => {
|
|||||||
|
|
||||||
const selectableItemIds = copilotCommands
|
const selectableItemIds = copilotCommands
|
||||||
.map((cmd) => cmd.id)
|
.map((cmd) => cmd.id)
|
||||||
|
.concat(matchingActionCommands.map((cmd) => cmd.id))
|
||||||
.concat(matchingCreateCommand.map((cmd) => cmd.id))
|
.concat(matchingCreateCommand.map((cmd) => cmd.id))
|
||||||
.concat(matchingNavigateCommand.map((cmd) => cmd.id))
|
.concat(matchingNavigateCommand.map((cmd) => cmd.id))
|
||||||
.concat(people?.map((person) => person.id))
|
.concat(people?.map((person) => person.id))
|
||||||
@ -320,22 +330,28 @@ export const CommandMenu = () => {
|
|||||||
.concat(notes?.map((note) => note.id));
|
.concat(notes?.map((note) => note.id));
|
||||||
|
|
||||||
const isNoResults =
|
const isNoResults =
|
||||||
|
!matchingActionCommands.length &&
|
||||||
!matchingCreateCommand.length &&
|
!matchingCreateCommand.length &&
|
||||||
!matchingNavigateCommand.length &&
|
!matchingNavigateCommand.length &&
|
||||||
!people?.length &&
|
!people?.length &&
|
||||||
!companies?.length &&
|
!companies?.length &&
|
||||||
!notes?.length &&
|
!notes?.length &&
|
||||||
!opportunities?.length;
|
!opportunities?.length;
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isPeopleLoading ||
|
isPeopleLoading ||
|
||||||
isNotesLoading ||
|
isNotesLoading ||
|
||||||
isOpportunitiesLoading ||
|
isOpportunitiesLoading ||
|
||||||
isCompaniesLoading;
|
isCompaniesLoading;
|
||||||
|
|
||||||
|
const mainContextStoreComponentInstanceId = useRecoilValue(
|
||||||
|
mainContextStoreComponentInstanceIdState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCommandMenuOpened && (
|
{isCommandMenuOpened && (
|
||||||
<StyledCommandMenu ref={commandMenuRef}>
|
<StyledCommandMenu ref={commandMenuRef} className="command-menu">
|
||||||
<StyledInputContainer>
|
<StyledInputContainer>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
autoFocus
|
autoFocus
|
||||||
@ -393,6 +409,23 @@ export const CommandMenu = () => {
|
|||||||
</SelectableItem>
|
</SelectableItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
)}
|
)}
|
||||||
|
{mainContextStoreComponentInstanceId && (
|
||||||
|
<CommandGroup heading="Actions">
|
||||||
|
{matchingActionCommands?.map((actionCommand) => (
|
||||||
|
<SelectableItem
|
||||||
|
itemId={actionCommand.id}
|
||||||
|
key={actionCommand.id}
|
||||||
|
>
|
||||||
|
<CommandMenuItem
|
||||||
|
id={actionCommand.id}
|
||||||
|
label={actionCommand.label}
|
||||||
|
Icon={actionCommand.Icon}
|
||||||
|
onClick={actionCommand.onCommandClick}
|
||||||
|
/>
|
||||||
|
</SelectableItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
<CommandGroup heading="Create">
|
<CommandGroup heading="Create">
|
||||||
{matchingCreateCommand.map((cmd) => (
|
{matchingCreateCommand.map((cmd) => (
|
||||||
<SelectableItem itemId={cmd.id} key={cmd.id}>
|
<SelectableItem itemId={cmd.id} key={cmd.id}>
|
||||||
|
@ -124,7 +124,8 @@ describe('useCommandMenu', () => {
|
|||||||
namePlural: 'tasks',
|
namePlural: 'tasks',
|
||||||
labelSingular: 'Task',
|
labelSingular: 'Task',
|
||||||
labelPlural: 'Tasks',
|
labelPlural: 'Tasks',
|
||||||
shouldSyncLabelAndName: true,
|
isLabelSyncedWithName: true,
|
||||||
|
shortcut: 'T',
|
||||||
description: 'A task',
|
description: 'A task',
|
||||||
icon: 'IconCheckbox',
|
icon: 'IconCheckbox',
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
@ -9,7 +9,9 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH
|
|||||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands';
|
import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands';
|
||||||
|
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons';
|
import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons';
|
||||||
import { sortByProperty } from '~/utils/array/sortByProperty';
|
import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||||
@ -27,10 +29,43 @@ export const useCommandMenu = () => {
|
|||||||
goBackToPreviousHotkeyScope,
|
goBackToPreviousHotkeyScope,
|
||||||
} = usePreviousHotkeyScope();
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
const openCommandMenu = useCallback(() => {
|
const mainContextStoreComponentInstanceId = useRecoilValue(
|
||||||
setIsCommandMenuOpened(true);
|
mainContextStoreComponentInstanceIdState,
|
||||||
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
|
);
|
||||||
}, [setHotkeyScopeAndMemorizePreviousScope, setIsCommandMenuOpened]);
|
|
||||||
|
const openCommandMenu = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
() => {
|
||||||
|
if (isDefined(mainContextStoreComponentInstanceId)) {
|
||||||
|
const actionMenuEntries = snapshot.getLoadable(
|
||||||
|
actionMenuEntriesComponentSelector.selectorFamily({
|
||||||
|
instanceId: mainContextStoreComponentInstanceId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const actionCommands = actionMenuEntries
|
||||||
|
.getValue()
|
||||||
|
?.map((actionMenuEntry) => ({
|
||||||
|
id: actionMenuEntry.key,
|
||||||
|
label: actionMenuEntry.label,
|
||||||
|
Icon: actionMenuEntry.Icon,
|
||||||
|
onCommandClick: actionMenuEntry.onClick,
|
||||||
|
type: CommandType.Action,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setCommands(actionCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsCommandMenuOpened(true);
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
mainContextStoreComponentInstanceId,
|
||||||
|
setCommands,
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
setIsCommandMenuOpened,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const closeCommandMenu = useRecoilCallback(
|
const closeCommandMenu = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
@ -83,8 +118,8 @@ export const useCommandMenu = () => {
|
|||||||
to: `/objects/${item.namePlural}`,
|
to: `/objects/${item.namePlural}`,
|
||||||
label: `Go to ${item.labelPlural}`,
|
label: `Go to ${item.labelPlural}`,
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
firstHotKey: 'G',
|
firstHotKey: item.shortcut ? 'G' : undefined,
|
||||||
secondHotKey: item.labelPlural[0],
|
secondHotKey: item.shortcut,
|
||||||
Icon: ALL_ICONS[
|
Icon: ALL_ICONS[
|
||||||
(item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight'
|
(item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight'
|
||||||
],
|
],
|
||||||
|
@ -3,13 +3,14 @@ import { IconComponent } from 'twenty-ui';
|
|||||||
export enum CommandType {
|
export enum CommandType {
|
||||||
Navigate = 'Navigate',
|
Navigate = 'Navigate',
|
||||||
Create = 'Create',
|
Create = 'Create',
|
||||||
|
Action = 'Action',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Command = {
|
export type Command = {
|
||||||
id: string;
|
id: string;
|
||||||
to: string;
|
to?: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: CommandType.Navigate | CommandType.Create;
|
type: CommandType.Navigate | CommandType.Create | CommandType.Action;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
firstHotKey?: string;
|
firstHotKey?: string;
|
||||||
secondHotKey?: string;
|
secondHotKey?: string;
|
||||||
|
@ -3,7 +3,7 @@ import { mainContextStoreComponentInstanceIdState } from '@/context-store/states
|
|||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
export const SetMainContextStoreComponentInstanceIdEffect = () => {
|
export const MainContextStoreComponentInstanceIdSetterEffect = () => {
|
||||||
const setMainContextStoreComponentInstanceId = useSetRecoilState(
|
const setMainContextStoreComponentInstanceId = useSetRecoilState(
|
||||||
mainContextStoreComponentInstanceIdState,
|
mainContextStoreComponentInstanceIdState,
|
||||||
);
|
);
|
@ -1,7 +1,7 @@
|
|||||||
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||||
|
|
||||||
export const computeContextStoreFilters = (
|
export const computeContextStoreFilters = (
|
||||||
@ -12,9 +12,10 @@ export const computeContextStoreFilters = (
|
|||||||
|
|
||||||
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
||||||
queryFilter = makeAndFilterVariables([
|
queryFilter = makeAndFilterVariables([
|
||||||
turnFiltersIntoQueryFilter(
|
computeViewRecordGqlOperationFilter(
|
||||||
contextStoreTargetedRecordsRule.filters,
|
contextStoreTargetedRecordsRule.filters,
|
||||||
objectMetadataItem?.fields ?? [],
|
objectMetadataItem?.fields ?? [],
|
||||||
|
[],
|
||||||
),
|
),
|
||||||
contextStoreTargetedRecordsRule.excludedRecordIds.length > 0
|
contextStoreTargetedRecordsRule.excludedRecordIds.length > 0
|
||||||
? {
|
? {
|
||||||
|
@ -85,238 +85,238 @@ export const mocks = [
|
|||||||
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
|
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
|
||||||
createFavorite(data: $input) {
|
createFavorite(data: $input) {
|
||||||
__typename
|
__typename
|
||||||
company {
|
company {
|
||||||
__typename
|
__typename
|
||||||
accountOwnerId
|
accountOwnerId
|
||||||
address {
|
address {
|
||||||
addressStreet1
|
addressStreet1
|
||||||
addressStreet2
|
addressStreet2
|
||||||
addressCity
|
addressCity
|
||||||
addressState
|
addressState
|
||||||
addressCountry
|
addressCountry
|
||||||
addressPostcode
|
addressPostcode
|
||||||
addressLat
|
addressLat
|
||||||
addressLng
|
addressLng
|
||||||
}
|
}
|
||||||
annualRecurringRevenue {
|
annualRecurringRevenue {
|
||||||
amountMicros
|
amountMicros
|
||||||
currencyCode
|
currencyCode
|
||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
createdBy {
|
createdBy {
|
||||||
source
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
deletedAt
|
||||||
|
domainName {
|
||||||
|
primaryLinkUrl
|
||||||
|
primaryLinkLabel
|
||||||
|
secondaryLinks
|
||||||
|
}
|
||||||
|
employees
|
||||||
|
id
|
||||||
|
idealCustomerProfile
|
||||||
|
introVideo {
|
||||||
|
primaryLinkUrl
|
||||||
|
primaryLinkLabel
|
||||||
|
secondaryLinks
|
||||||
|
}
|
||||||
|
linkedinLink {
|
||||||
|
primaryLinkUrl
|
||||||
|
primaryLinkLabel
|
||||||
|
secondaryLinks
|
||||||
|
}
|
||||||
|
name
|
||||||
|
position
|
||||||
|
tagline
|
||||||
|
updatedAt
|
||||||
|
visaSponsorship
|
||||||
|
workPolicy
|
||||||
|
xLink {
|
||||||
|
primaryLinkUrl
|
||||||
|
primaryLinkLabel
|
||||||
|
secondaryLinks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
companyId
|
||||||
|
createdAt
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
note {
|
||||||
|
__typename
|
||||||
|
body
|
||||||
|
createdAt
|
||||||
|
createdBy {
|
||||||
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
position
|
||||||
|
title
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
noteId
|
||||||
|
opportunity {
|
||||||
|
__typename
|
||||||
|
amount {
|
||||||
|
amountMicros
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
closeDate
|
||||||
|
companyId
|
||||||
|
createdAt
|
||||||
|
createdBy {
|
||||||
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
name
|
||||||
|
pointOfContactId
|
||||||
|
position
|
||||||
|
stage
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
opportunityId
|
||||||
|
person {
|
||||||
|
__typename
|
||||||
|
avatarUrl
|
||||||
|
city
|
||||||
|
companyId
|
||||||
|
createdAt
|
||||||
|
createdBy {
|
||||||
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
deletedAt
|
||||||
|
emails {
|
||||||
|
primaryEmail
|
||||||
|
additionalEmails
|
||||||
|
}
|
||||||
|
id
|
||||||
|
intro
|
||||||
|
jobTitle
|
||||||
|
linkedinLink {
|
||||||
|
primaryLinkUrl
|
||||||
|
primaryLinkLabel
|
||||||
|
secondaryLinks
|
||||||
|
}
|
||||||
|
name {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
performanceRating
|
||||||
|
phones {
|
||||||
|
primaryPhoneNumber
|
||||||
|
primaryPhoneCountryCode
|
||||||
|
additionalPhones
|
||||||
|
}
|
||||||
|
position
|
||||||
|
updatedAt
|
||||||
|
whatsapp {
|
||||||
|
primaryPhoneNumber
|
||||||
|
primaryPhoneCountryCode
|
||||||
|
additionalPhones
|
||||||
|
}
|
||||||
|
workPreference
|
||||||
|
xLink {
|
||||||
|
primaryLinkUrl
|
||||||
|
primaryLinkLabel
|
||||||
|
secondaryLinks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
personId
|
||||||
|
position
|
||||||
|
rocket {
|
||||||
|
__typename
|
||||||
|
createdAt
|
||||||
|
createdBy {
|
||||||
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
name
|
||||||
|
position
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
rocketId
|
||||||
|
task {
|
||||||
|
__typename
|
||||||
|
assigneeId
|
||||||
|
body
|
||||||
|
createdAt
|
||||||
|
createdBy {
|
||||||
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
deletedAt
|
||||||
|
dueAt
|
||||||
|
id
|
||||||
|
position
|
||||||
|
status
|
||||||
|
title
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
taskId
|
||||||
|
updatedAt
|
||||||
|
view {
|
||||||
|
__typename
|
||||||
|
createdAt
|
||||||
|
deletedAt
|
||||||
|
icon
|
||||||
|
id
|
||||||
|
isCompact
|
||||||
|
kanbanFieldMetadataId
|
||||||
|
key
|
||||||
|
name
|
||||||
|
objectMetadataId
|
||||||
|
position
|
||||||
|
type
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
viewId
|
||||||
|
workflow {
|
||||||
|
__typename
|
||||||
|
createdAt
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
lastPublishedVersionId
|
||||||
|
name
|
||||||
|
position
|
||||||
|
statuses
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
workflowId
|
||||||
|
workspaceMember {
|
||||||
|
__typename
|
||||||
|
avatarUrl
|
||||||
|
colorScheme
|
||||||
|
createdAt
|
||||||
|
dateFormat
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
locale
|
||||||
|
name {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
timeFormat
|
||||||
|
timeZone
|
||||||
|
updatedAt
|
||||||
|
userEmail
|
||||||
|
userId
|
||||||
|
}
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
name
|
|
||||||
}
|
|
||||||
deletedAt
|
|
||||||
domainName {
|
|
||||||
primaryLinkUrl
|
|
||||||
primaryLinkLabel
|
|
||||||
secondaryLinks
|
|
||||||
}
|
|
||||||
employees
|
|
||||||
id
|
|
||||||
idealCustomerProfile
|
|
||||||
introVideo {
|
|
||||||
primaryLinkUrl
|
|
||||||
primaryLinkLabel
|
|
||||||
secondaryLinks
|
|
||||||
}
|
|
||||||
linkedinLink {
|
|
||||||
primaryLinkUrl
|
|
||||||
primaryLinkLabel
|
|
||||||
secondaryLinks
|
|
||||||
}
|
|
||||||
name
|
|
||||||
position
|
|
||||||
tagline
|
|
||||||
updatedAt
|
|
||||||
visaSponsorship
|
|
||||||
workPolicy
|
|
||||||
xLink {
|
|
||||||
primaryLinkUrl
|
|
||||||
primaryLinkLabel
|
|
||||||
secondaryLinks
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
companyId
|
|
||||||
createdAt
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
note {
|
|
||||||
__typename
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
createdBy {
|
|
||||||
source
|
|
||||||
workspaceMemberId
|
|
||||||
name
|
|
||||||
}
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
position
|
|
||||||
title
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
noteId
|
|
||||||
opportunity {
|
|
||||||
__typename
|
|
||||||
amount {
|
|
||||||
amountMicros
|
|
||||||
currencyCode
|
|
||||||
}
|
|
||||||
closeDate
|
|
||||||
companyId
|
|
||||||
createdAt
|
|
||||||
createdBy {
|
|
||||||
source
|
|
||||||
workspaceMemberId
|
|
||||||
name
|
|
||||||
}
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
name
|
|
||||||
pointOfContactId
|
|
||||||
position
|
|
||||||
stage
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
opportunityId
|
|
||||||
person {
|
|
||||||
__typename
|
|
||||||
avatarUrl
|
|
||||||
city
|
|
||||||
companyId
|
|
||||||
createdAt
|
|
||||||
createdBy {
|
|
||||||
source
|
|
||||||
workspaceMemberId
|
|
||||||
name
|
|
||||||
}
|
|
||||||
deletedAt
|
|
||||||
emails {
|
|
||||||
primaryEmail
|
|
||||||
additionalEmails
|
|
||||||
}
|
|
||||||
id
|
|
||||||
intro
|
|
||||||
jobTitle
|
|
||||||
linkedinLink {
|
|
||||||
primaryLinkUrl
|
|
||||||
primaryLinkLabel
|
|
||||||
secondaryLinks
|
|
||||||
}
|
|
||||||
name {
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
performanceRating
|
|
||||||
phones {
|
|
||||||
primaryPhoneNumber
|
|
||||||
primaryPhoneCountryCode
|
|
||||||
additionalPhones
|
|
||||||
}
|
|
||||||
position
|
|
||||||
updatedAt
|
|
||||||
whatsapp {
|
|
||||||
primaryPhoneNumber
|
|
||||||
primaryPhoneCountryCode
|
|
||||||
additionalPhones
|
|
||||||
}
|
|
||||||
workPreference
|
|
||||||
xLink {
|
|
||||||
primaryLinkUrl
|
|
||||||
primaryLinkLabel
|
|
||||||
secondaryLinks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
personId
|
|
||||||
position
|
|
||||||
rocket {
|
|
||||||
__typename
|
|
||||||
createdAt
|
|
||||||
createdBy {
|
|
||||||
source
|
|
||||||
workspaceMemberId
|
|
||||||
name
|
|
||||||
}
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
name
|
|
||||||
position
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
rocketId
|
|
||||||
task {
|
|
||||||
__typename
|
|
||||||
assigneeId
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
createdBy {
|
|
||||||
source
|
|
||||||
workspaceMemberId
|
|
||||||
name
|
|
||||||
}
|
|
||||||
deletedAt
|
|
||||||
dueAt
|
|
||||||
id
|
|
||||||
position
|
|
||||||
status
|
|
||||||
title
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
taskId
|
|
||||||
updatedAt
|
|
||||||
view {
|
|
||||||
__typename
|
|
||||||
createdAt
|
|
||||||
deletedAt
|
|
||||||
icon
|
|
||||||
id
|
|
||||||
isCompact
|
|
||||||
kanbanFieldMetadataId
|
|
||||||
key
|
|
||||||
name
|
|
||||||
objectMetadataId
|
|
||||||
position
|
|
||||||
type
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
viewId
|
|
||||||
workflow {
|
|
||||||
__typename
|
|
||||||
createdAt
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
lastPublishedVersionId
|
|
||||||
name
|
|
||||||
position
|
|
||||||
statuses
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
workflowId
|
|
||||||
workspaceMember {
|
|
||||||
__typename
|
|
||||||
avatarUrl
|
|
||||||
colorScheme
|
|
||||||
createdAt
|
|
||||||
dateFormat
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
locale
|
|
||||||
name {
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
timeFormat
|
|
||||||
timeZone
|
|
||||||
updatedAt
|
|
||||||
userEmail
|
|
||||||
userId
|
|
||||||
}
|
|
||||||
workspaceMemberId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
@ -3,13 +3,18 @@ import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableT
|
|||||||
describe('findAvailableTimeZoneOption', () => {
|
describe('findAvailableTimeZoneOption', () => {
|
||||||
it('should find the matching available IANA time zone select option from a given IANA time zone', () => {
|
it('should find the matching available IANA time zone select option from a given IANA time zone', () => {
|
||||||
const ianaTimeZone = 'Europe/Paris';
|
const ianaTimeZone = 'Europe/Paris';
|
||||||
const expectedOption = {
|
const expectedValue = 'Europe/Paris';
|
||||||
label: '(GMT+02:00) Central European Summer Time - Paris',
|
const expectedLabelWinter =
|
||||||
value: 'Europe/Paris',
|
'(GMT+01:00) Central European Standard Time - Paris';
|
||||||
};
|
const expectedLabelSummer =
|
||||||
|
'(GMT+02:00) Central European Summer Time - Paris';
|
||||||
|
|
||||||
const option = findAvailableTimeZoneOption(ianaTimeZone);
|
const option = findAvailableTimeZoneOption(ianaTimeZone);
|
||||||
|
|
||||||
expect(option).toEqual(expectedOption);
|
expect(option.value).toEqual(expectedValue);
|
||||||
|
expect(
|
||||||
|
expectedLabelWinter === option.label ||
|
||||||
|
expectedLabelSummer === option.label,
|
||||||
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,11 +3,17 @@ import { formatTimeZoneLabel } from '@/localization/utils/formatTimeZoneLabel';
|
|||||||
describe('formatTimeZoneLabel', () => {
|
describe('formatTimeZoneLabel', () => {
|
||||||
it('should format the time zone label correctly when location is included in the label', () => {
|
it('should format the time zone label correctly when location is included in the label', () => {
|
||||||
const ianaTimeZone = 'Europe/Paris';
|
const ianaTimeZone = 'Europe/Paris';
|
||||||
const expectedLabel = '(GMT+02:00) Central European Summer Time - Paris';
|
const expectedLabelSummer =
|
||||||
|
'(GMT+02:00) Central European Summer Time - Paris';
|
||||||
|
const expectedLabelWinter =
|
||||||
|
'(GMT+01:00) Central European Standard Time - Paris';
|
||||||
|
|
||||||
const formattedLabel = formatTimeZoneLabel(ianaTimeZone);
|
const formattedLabel = formatTimeZoneLabel(ianaTimeZone);
|
||||||
|
|
||||||
expect(formattedLabel).toEqual(expectedLabel);
|
expect(
|
||||||
|
expectedLabelSummer === formattedLabel ||
|
||||||
|
expectedLabelWinter === formattedLabel,
|
||||||
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format the time zone label correctly when location is not included in the label', () => {
|
it('should format the time zone label correctly when location is not included in the label', () => {
|
||||||
|
@ -4,13 +4,12 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
|
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
|
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import {
|
import {
|
||||||
AppNavigationDrawer,
|
AppNavigationDrawer,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
|
||||||
import indexAppPath from '../indexAppPath';
|
import indexAppPath from '../indexAppPath';
|
||||||
|
|
||||||
describe('getIndexAppPath', () => {
|
describe('getIndexAppPath', () => {
|
||||||
|
@ -43,8 +43,7 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
|||||||
const shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1;
|
const shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1;
|
||||||
|
|
||||||
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
|
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
|
||||||
(viewA, viewB) =>
|
(viewA, viewB) => viewA.position - viewB.position,
|
||||||
viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
|
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
|
||||||
|
@ -24,7 +24,8 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
|||||||
updatedAt
|
updatedAt
|
||||||
labelIdentifierFieldMetadataId
|
labelIdentifierFieldMetadataId
|
||||||
imageIdentifierFieldMetadataId
|
imageIdentifierFieldMetadataId
|
||||||
shouldSyncLabelAndName
|
shortcut
|
||||||
|
isLabelSyncedWithName
|
||||||
indexMetadatas(paging: { first: 100 }) {
|
indexMetadatas(paging: { first: 100 }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
@ -11,5 +11,5 @@ export const query = gql`
|
|||||||
export const variables = { idToDelete: 'idToDelete' };
|
export const variables = { idToDelete: 'idToDelete' };
|
||||||
|
|
||||||
export const responseData = {
|
export const responseData = {
|
||||||
id: 'idToDelete'
|
id: 'idToDelete',
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,8 @@ import { gql } from '@apollo/client';
|
|||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
export const FIELD_METADATA_ID = '2c43466a-fe9e-4005-8d08-c5836067aa6c';
|
export const FIELD_METADATA_ID = '2c43466a-fe9e-4005-8d08-c5836067aa6c';
|
||||||
export const FIELD_RELATION_METADATA_ID = '4da0302d-358a-45cd-9973-9f92723ed3c1';
|
export const FIELD_RELATION_METADATA_ID =
|
||||||
|
'4da0302d-358a-45cd-9973-9f92723ed3c1';
|
||||||
export const RELATION_METADATA_ID = 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6';
|
export const RELATION_METADATA_ID = 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6';
|
||||||
|
|
||||||
const baseFields = `
|
const baseFields = `
|
||||||
@ -29,12 +30,12 @@ export const queries = {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
deleteMetadataFieldRelation: gql`
|
deleteMetadataFieldRelation: gql`
|
||||||
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
|
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
|
||||||
deleteOneRelation(input: { id: $idToDelete }) {
|
deleteOneRelation(input: { id: $idToDelete }) {
|
||||||
id
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`,
|
||||||
`,
|
|
||||||
activateMetadataField: gql`
|
activateMetadataField: gql`
|
||||||
mutation UpdateOneFieldMetadataItem(
|
mutation UpdateOneFieldMetadataItem(
|
||||||
$idToUpdate: UUID!
|
$idToUpdate: UUID!
|
||||||
@ -94,7 +95,7 @@ export const variables = {
|
|||||||
deactivateMetadataField: {
|
deactivateMetadataField: {
|
||||||
idToUpdate: FIELD_METADATA_ID,
|
idToUpdate: FIELD_METADATA_ID,
|
||||||
updatePayload: { isActive: false, label: undefined },
|
updatePayload: { isActive: false, label: undefined },
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultResponseData = {
|
const defaultResponseData = {
|
||||||
@ -127,4 +128,3 @@ export const responseData = {
|
|||||||
options: [],
|
options: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ export const query = gql`
|
|||||||
updatedAt
|
updatedAt
|
||||||
labelIdentifierFieldMetadataId
|
labelIdentifierFieldMetadataId
|
||||||
imageIdentifierFieldMetadataId
|
imageIdentifierFieldMetadataId
|
||||||
|
shortcut
|
||||||
|
isLabelSyncedWithName
|
||||||
fields(paging: { first: 1000 }, filter: $fieldFilter) {
|
fields(paging: { first: 1000 }, filter: $fieldFilter) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
@ -16,6 +16,7 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
|||||||
featureFlags: [],
|
featureFlags: [],
|
||||||
allowImpersonation: false,
|
allowImpersonation: false,
|
||||||
activationStatus: WorkspaceActivationStatus.Active,
|
activationStatus: WorkspaceActivationStatus.Active,
|
||||||
|
hasValidEntrepriseKey: false,
|
||||||
metadataVersion: 1,
|
metadataVersion: 1,
|
||||||
isPublicInviteLinkEnabled: false,
|
isPublicInviteLinkEnabled: false,
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,9 @@ export enum CoreObjectNameSingular {
|
|||||||
View = 'view',
|
View = 'view',
|
||||||
ViewField = 'viewField',
|
ViewField = 'viewField',
|
||||||
ViewFilter = 'viewFilter',
|
ViewFilter = 'viewFilter',
|
||||||
|
ViewFilterGroup = 'viewFilterGroup',
|
||||||
ViewSort = 'viewSort',
|
ViewSort = 'viewSort',
|
||||||
|
ViewGroup = 'viewGroup',
|
||||||
Webhook = 'webhook',
|
Webhook = 'webhook',
|
||||||
WorkspaceMember = 'workspaceMember',
|
WorkspaceMember = 'workspaceMember',
|
||||||
MessageThreadSubscriber = 'messageThreadSubscriber',
|
MessageThreadSubscriber = 'messageThreadSubscriber',
|
||||||
|
@ -30,7 +30,7 @@ describe('objectMetadataItemSchema', () => {
|
|||||||
namePlural: 'notCamelCase',
|
namePlural: 'notCamelCase',
|
||||||
nameSingular: 'notCamelCase',
|
nameSingular: 'notCamelCase',
|
||||||
updatedAt: 'invalid date',
|
updatedAt: 'invalid date',
|
||||||
shouldSyncLabelAndName: 'not a boolean',
|
isLabelSyncedWithName: 'not a boolean',
|
||||||
};
|
};
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
@ -26,5 +26,6 @@ export const objectMetadataItemSchema = z.object({
|
|||||||
namePlural: camelCaseStringSchema,
|
namePlural: camelCaseStringSchema,
|
||||||
nameSingular: camelCaseStringSchema,
|
nameSingular: camelCaseStringSchema,
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.string().datetime(),
|
||||||
shouldSyncLabelAndName: z.boolean(),
|
shortcut: z.string().nullable().optional(),
|
||||||
|
isLabelSyncedWithName: z.boolean(),
|
||||||
}) satisfies z.ZodType<ObjectMetadataItem>;
|
}) satisfies z.ZodType<ObjectMetadataItem>;
|
||||||
|
@ -0,0 +1,161 @@
|
|||||||
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
|
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
|
||||||
|
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
|
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||||
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
|
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||||
|
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { IconLibraryPlus, IconPlus, isDefined, LightButton } from 'twenty-ui';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
type AdvancedFilterAddFilterRuleSelectProps = {
|
||||||
|
viewFilterGroup: ViewFilterGroup;
|
||||||
|
lastChildPosition?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterAddFilterRuleSelect = ({
|
||||||
|
viewFilterGroup,
|
||||||
|
lastChildPosition = 0,
|
||||||
|
}: AdvancedFilterAddFilterRuleSelectProps) => {
|
||||||
|
const dropdownId = `advanced-filter-add-filter-rule-${viewFilterGroup.id}`;
|
||||||
|
|
||||||
|
const { currentViewId } = useGetCurrentView();
|
||||||
|
|
||||||
|
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup();
|
||||||
|
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters();
|
||||||
|
|
||||||
|
const newPositionInViewFilterGroup = lastChildPosition + 1;
|
||||||
|
|
||||||
|
const { closeDropdown } = useDropdown(dropdownId);
|
||||||
|
|
||||||
|
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||||
|
|
||||||
|
const objectMetadataId =
|
||||||
|
currentViewWithCombinedFiltersAndSorts?.objectMetadataId;
|
||||||
|
|
||||||
|
if (!objectMetadataId) {
|
||||||
|
throw new Error('Object metadata id is missing from current view');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||||
|
objectId: objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||||
|
availableFilterDefinitionsComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDefaultFilterDefinition = useCallback(() => {
|
||||||
|
const defaultFilterDefinition =
|
||||||
|
availableFilterDefinitions.find(
|
||||||
|
(filterDefinition) =>
|
||||||
|
filterDefinition.fieldMetadataId ===
|
||||||
|
objectMetadataItem?.labelIdentifierFieldMetadataId,
|
||||||
|
) ?? availableFilterDefinitions?.[0];
|
||||||
|
|
||||||
|
if (!defaultFilterDefinition) {
|
||||||
|
throw new Error('Missing default filter definition');
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultFilterDefinition;
|
||||||
|
}, [availableFilterDefinitions, objectMetadataItem]);
|
||||||
|
|
||||||
|
const handleAddFilter = () => {
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
const defaultFilterDefinition = getDefaultFilterDefinition();
|
||||||
|
|
||||||
|
upsertCombinedViewFilter({
|
||||||
|
id: v4(),
|
||||||
|
fieldMetadataId: defaultFilterDefinition.fieldMetadataId,
|
||||||
|
operand: getOperandsForFilterDefinition(defaultFilterDefinition)[0],
|
||||||
|
definition: defaultFilterDefinition,
|
||||||
|
value: '',
|
||||||
|
displayValue: '',
|
||||||
|
viewFilterGroupId: viewFilterGroup.id,
|
||||||
|
positionInViewFilterGroup: newPositionInViewFilterGroup,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddFilterGroup = () => {
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
if (!currentViewId) {
|
||||||
|
throw new Error('Missing view id');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newViewFilterGroup = {
|
||||||
|
id: v4(),
|
||||||
|
viewId: currentViewId,
|
||||||
|
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||||
|
parentViewFilterGroupId: viewFilterGroup.id,
|
||||||
|
positionInViewFilterGroup: newPositionInViewFilterGroup,
|
||||||
|
};
|
||||||
|
|
||||||
|
upsertCombinedViewFilterGroup(newViewFilterGroup);
|
||||||
|
|
||||||
|
const defaultFilterDefinition = getDefaultFilterDefinition();
|
||||||
|
|
||||||
|
upsertCombinedViewFilter({
|
||||||
|
id: v4(),
|
||||||
|
fieldMetadataId: defaultFilterDefinition.fieldMetadataId,
|
||||||
|
operand: getOperandsForFilterDefinition(defaultFilterDefinition)[0],
|
||||||
|
definition: defaultFilterDefinition,
|
||||||
|
value: '',
|
||||||
|
displayValue: '',
|
||||||
|
viewFilterGroupId: newViewFilterGroup.id,
|
||||||
|
positionInViewFilterGroup: newPositionInViewFilterGroup,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFilterRuleGroupOptionVisible = !isDefined(
|
||||||
|
viewFilterGroup.parentViewFilterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isFilterRuleGroupOptionVisible) {
|
||||||
|
return (
|
||||||
|
<LightButton
|
||||||
|
Icon={IconPlus}
|
||||||
|
title="Add filter rule"
|
||||||
|
onClick={handleAddFilter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
disableBlur
|
||||||
|
dropdownId={dropdownId}
|
||||||
|
clickableComponent={
|
||||||
|
<LightButton Icon={IconPlus} title="Add filter rule" />
|
||||||
|
}
|
||||||
|
dropdownComponents={
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<MenuItem
|
||||||
|
LeftIcon={IconPlus}
|
||||||
|
text="Add rule"
|
||||||
|
onClick={handleAddFilter}
|
||||||
|
/>
|
||||||
|
{isFilterRuleGroupOptionVisible && (
|
||||||
|
<MenuItem
|
||||||
|
LeftIcon={IconLibraryPlus}
|
||||||
|
text="Add rule group"
|
||||||
|
onClick={handleAddFilterGroup}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||||
|
dropdownOffset={{ y: 8, x: 0 }}
|
||||||
|
dropdownPlacement="bottom-start"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
import { AdvancedFilterLogicalOperatorDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorDropdown';
|
||||||
|
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
|
const StyledText = styled.div`
|
||||||
|
height: ${({ theme }) => theme.spacing(8)};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
align-items: start;
|
||||||
|
display: flex;
|
||||||
|
min-width: ${({ theme }) => theme.spacing(20)};
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AdvancedFilterLogicalOperatorCellProps = {
|
||||||
|
index: number;
|
||||||
|
viewFilterGroup: ViewFilterGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterLogicalOperatorCell = ({
|
||||||
|
index,
|
||||||
|
viewFilterGroup,
|
||||||
|
}: AdvancedFilterLogicalOperatorCellProps) => (
|
||||||
|
<StyledContainer>
|
||||||
|
{index === 0 ? (
|
||||||
|
<StyledText>Where</StyledText>
|
||||||
|
) : index === 1 ? (
|
||||||
|
<AdvancedFilterLogicalOperatorDropdown
|
||||||
|
viewFilterGroup={viewFilterGroup}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StyledText>
|
||||||
|
{capitalize(viewFilterGroup.logicalOperator.toLowerCase())}
|
||||||
|
</StyledText>
|
||||||
|
)}
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
@ -0,0 +1,33 @@
|
|||||||
|
import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions';
|
||||||
|
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
|
||||||
|
import { Select } from '@/ui/input/components/Select';
|
||||||
|
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||||
|
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
|
||||||
|
|
||||||
|
type AdvancedFilterLogicalOperatorDropdownProps = {
|
||||||
|
viewFilterGroup: ViewFilterGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterLogicalOperatorDropdown = ({
|
||||||
|
viewFilterGroup,
|
||||||
|
}: AdvancedFilterLogicalOperatorDropdownProps) => {
|
||||||
|
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup();
|
||||||
|
|
||||||
|
const handleChange = (value: ViewFilterGroupLogicalOperator) => {
|
||||||
|
upsertCombinedViewFilterGroup({
|
||||||
|
...viewFilterGroup,
|
||||||
|
logicalOperator: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
disableBlur
|
||||||
|
fullWidth
|
||||||
|
dropdownId={`advanced-filter-logical-operator-${viewFilterGroup.id}`}
|
||||||
|
value={viewFilterGroup.logicalOperator}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,78 @@
|
|||||||
|
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
|
||||||
|
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
|
||||||
|
import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown';
|
||||||
|
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
|
||||||
|
import { AdvancedFilterViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup';
|
||||||
|
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
|
||||||
|
align-items: start;
|
||||||
|
background-color: ${({ theme, isGrayBackground }) =>
|
||||||
|
isGrayBackground ? theme.background.transparent.lighter : 'transparent'};
|
||||||
|
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AdvancedFilterRootLevelViewFilterGroupProps = {
|
||||||
|
rootLevelViewFilterGroupId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterRootLevelViewFilterGroup = ({
|
||||||
|
rootLevelViewFilterGroupId,
|
||||||
|
}: AdvancedFilterRootLevelViewFilterGroupProps) => {
|
||||||
|
const {
|
||||||
|
currentViewFilterGroup: rootLevelViewFilterGroup,
|
||||||
|
childViewFiltersAndViewFilterGroups,
|
||||||
|
lastChildPosition,
|
||||||
|
} = useCurrentViewViewFilterGroup({
|
||||||
|
viewFilterGroupId: rootLevelViewFilterGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDefined(rootLevelViewFilterGroup)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
{childViewFiltersAndViewFilterGroups.map((child, i) =>
|
||||||
|
child.__typename === 'ViewFilterGroup' ? (
|
||||||
|
<StyledRow key={child.id}>
|
||||||
|
<AdvancedFilterLogicalOperatorCell
|
||||||
|
index={i}
|
||||||
|
viewFilterGroup={rootLevelViewFilterGroup}
|
||||||
|
/>
|
||||||
|
<AdvancedFilterViewFilterGroup viewFilterGroupId={child.id} />
|
||||||
|
<AdvancedFilterRuleOptionsDropdown viewFilterGroupId={child.id} />
|
||||||
|
</StyledRow>
|
||||||
|
) : (
|
||||||
|
<StyledRow key={child.id}>
|
||||||
|
<AdvancedFilterLogicalOperatorCell
|
||||||
|
index={i}
|
||||||
|
viewFilterGroup={rootLevelViewFilterGroup}
|
||||||
|
/>
|
||||||
|
<AdvancedFilterViewFilter viewFilterId={child.id} />
|
||||||
|
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} />
|
||||||
|
</StyledRow>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
<AdvancedFilterAddFilterRuleSelect
|
||||||
|
viewFilterGroup={rootLevelViewFilterGroup}
|
||||||
|
lastChildPosition={lastChildPosition}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,87 @@
|
|||||||
|
import { AdvancedFilterRuleOptionsDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton';
|
||||||
|
import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter';
|
||||||
|
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
|
||||||
|
import { useDeleteCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useDeleteCombinedViewFilterGroup';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
|
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
type AdvancedFilterRuleOptionsDropdownProps =
|
||||||
|
| {
|
||||||
|
viewFilterId: string;
|
||||||
|
viewFilterGroupId?: never;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
viewFilterId?: never;
|
||||||
|
viewFilterGroupId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterRuleOptionsDropdown = ({
|
||||||
|
viewFilterId,
|
||||||
|
viewFilterGroupId,
|
||||||
|
}: AdvancedFilterRuleOptionsDropdownProps) => {
|
||||||
|
const dropdownId = `advanced-filter-rule-options-${viewFilterId ?? viewFilterGroupId}`;
|
||||||
|
|
||||||
|
const { deleteCombinedViewFilter } = useDeleteCombinedViewFilters();
|
||||||
|
const { deleteCombinedViewFilterGroup } = useDeleteCombinedViewFilterGroup();
|
||||||
|
|
||||||
|
const { currentViewFilterGroup, childViewFiltersAndViewFilterGroups } =
|
||||||
|
useCurrentViewViewFilterGroup({
|
||||||
|
viewFilterGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentViewFilter = useCurrentViewFilter({
|
||||||
|
viewFilterId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRemove = async () => {
|
||||||
|
if (isDefined(viewFilterId)) {
|
||||||
|
deleteCombinedViewFilter(viewFilterId);
|
||||||
|
|
||||||
|
const isOnlyViewFilterInGroup =
|
||||||
|
childViewFiltersAndViewFilterGroups.length === 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isOnlyViewFilterInGroup &&
|
||||||
|
isDefined(currentViewFilter?.viewFilterGroupId)
|
||||||
|
) {
|
||||||
|
deleteCombinedViewFilterGroup(currentViewFilter.viewFilterGroupId);
|
||||||
|
}
|
||||||
|
} else if (isDefined(currentViewFilterGroup)) {
|
||||||
|
deleteCombinedViewFilterGroup(currentViewFilterGroup.id);
|
||||||
|
|
||||||
|
const childViewFilters = childViewFiltersAndViewFilterGroups.filter(
|
||||||
|
(child) => child.__typename === 'ViewFilter',
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const childViewFilter of childViewFilters) {
|
||||||
|
await deleteCombinedViewFilter(childViewFilter.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('No view filter or view filter group to remove');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeButtonLabel = viewFilterId ? 'Remove rule' : 'Remove rule group';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
disableBlur
|
||||||
|
dropdownId={dropdownId}
|
||||||
|
clickableComponent={
|
||||||
|
<AdvancedFilterRuleOptionsDropdownButton dropdownId={dropdownId} />
|
||||||
|
}
|
||||||
|
dropdownComponents={
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<MenuItem text={removeButtonLabel} onClick={handleRemove} />
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||||
|
dropdownOffset={{ y: 8, x: 0 }}
|
||||||
|
dropdownPlacement="bottom-start"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { IconButton, IconDotsVertical } from 'twenty-ui';
|
||||||
|
|
||||||
|
type AdvancedFilterRuleOptionsDropdownButtonProps = {
|
||||||
|
dropdownId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterRuleOptionsDropdownButton = ({
|
||||||
|
dropdownId,
|
||||||
|
}: AdvancedFilterRuleOptionsDropdownButtonProps) => {
|
||||||
|
const { toggleDropdown } = useDropdown(dropdownId);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
toggleDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label="Filter rule options"
|
||||||
|
variant="tertiary"
|
||||||
|
Icon={IconDotsVertical}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,47 @@
|
|||||||
|
import { AdvancedFilterViewFilterFieldSelect } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterFieldSelect';
|
||||||
|
import { AdvancedFilterViewFilterOperandSelect } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterOperandSelect';
|
||||||
|
import { AdvancedFilterViewFilterValueInput } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterValueInput';
|
||||||
|
import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter';
|
||||||
|
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
|
||||||
|
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledValueDropdownContainer = styled.div`
|
||||||
|
flex: 3;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledRow = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AdvancedFilterViewFilterProps = {
|
||||||
|
viewFilterId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterViewFilter = ({
|
||||||
|
viewFilterId,
|
||||||
|
}: AdvancedFilterViewFilterProps) => {
|
||||||
|
const filter = useCurrentViewFilter({ viewFilterId });
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectFilterDropdownScope filterScopeId={filter.id}>
|
||||||
|
<StyledRow>
|
||||||
|
<AdvancedFilterViewFilterFieldSelect viewFilterId={filter.id} />
|
||||||
|
<AdvancedFilterViewFilterOperandSelect viewFilterId={filter.id} />
|
||||||
|
<StyledValueDropdownContainer>
|
||||||
|
{configurableViewFilterOperands.has(filter.operand) && (
|
||||||
|
<AdvancedFilterViewFilterValueInput viewFilterId={filter.id} />
|
||||||
|
)}
|
||||||
|
</StyledValueDropdownContainer>
|
||||||
|
</StyledRow>
|
||||||
|
</ObjectFilterDropdownScope>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,71 @@
|
|||||||
|
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
|
||||||
|
import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter';
|
||||||
|
import { ObjectFilterDropdownFilterSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
|
||||||
|
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
|
||||||
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
|
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||||
|
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
flex: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AdvancedFilterViewFilterFieldSelectProps = {
|
||||||
|
viewFilterId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterViewFilterFieldSelect = ({
|
||||||
|
viewFilterId,
|
||||||
|
}: AdvancedFilterViewFilterFieldSelectProps) => {
|
||||||
|
const { advancedFilterDropdownId } = useAdvancedFilterDropdown(viewFilterId);
|
||||||
|
|
||||||
|
const filter = useCurrentViewFilter({ viewFilterId });
|
||||||
|
|
||||||
|
const selectedFieldLabel = filter?.definition.label ?? '';
|
||||||
|
|
||||||
|
const { setAdvancedFilterViewFilterGroupId, setAdvancedFilterViewFilterId } =
|
||||||
|
useFilterDropdown();
|
||||||
|
|
||||||
|
const [objectFilterDropdownIsSelectingCompositeField] =
|
||||||
|
useRecoilComponentStateV2(
|
||||||
|
objectFilterDropdownIsSelectingCompositeFieldComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldShowCompositeSelectionSubMenu =
|
||||||
|
objectFilterDropdownIsSelectingCompositeField;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<Dropdown
|
||||||
|
disableBlur
|
||||||
|
dropdownId={advancedFilterDropdownId}
|
||||||
|
clickableComponent={
|
||||||
|
<SelectControl
|
||||||
|
selectedOption={{
|
||||||
|
label: selectedFieldLabel,
|
||||||
|
value: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onOpen={() => {
|
||||||
|
setAdvancedFilterViewFilterId(filter?.id);
|
||||||
|
setAdvancedFilterViewFilterGroupId(filter?.viewFilterGroupId);
|
||||||
|
}}
|
||||||
|
dropdownComponents={
|
||||||
|
shouldShowCompositeSelectionSubMenu ? (
|
||||||
|
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
|
||||||
|
) : (
|
||||||
|
<ObjectFilterDropdownFilterSelect />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||||
|
dropdownOffset={{ y: 8, x: 0 }}
|
||||||
|
dropdownPlacement="bottom-start"
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,67 @@
|
|||||||
|
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
|
||||||
|
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
|
||||||
|
import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown';
|
||||||
|
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
|
||||||
|
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
|
||||||
|
align-items: start;
|
||||||
|
background-color: ${({ theme, isGrayBackground }) =>
|
||||||
|
isGrayBackground ? theme.background.transparent.lighter : 'transparent'};
|
||||||
|
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AdvancedFilterViewFilterGroupProps = {
|
||||||
|
viewFilterGroupId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterViewFilterGroup = ({
|
||||||
|
viewFilterGroupId,
|
||||||
|
}: AdvancedFilterViewFilterGroupProps) => {
|
||||||
|
const {
|
||||||
|
currentViewFilterGroup,
|
||||||
|
childViewFiltersAndViewFilterGroups,
|
||||||
|
lastChildPosition,
|
||||||
|
} = useCurrentViewViewFilterGroup({
|
||||||
|
viewFilterGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentViewFilterGroup) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer
|
||||||
|
isGrayBackground={!!currentViewFilterGroup.parentViewFilterGroupId}
|
||||||
|
>
|
||||||
|
{childViewFiltersAndViewFilterGroups.map((child, i) => (
|
||||||
|
<StyledRow key={child.id}>
|
||||||
|
<AdvancedFilterLogicalOperatorCell
|
||||||
|
index={i}
|
||||||
|
viewFilterGroup={currentViewFilterGroup}
|
||||||
|
/>
|
||||||
|
<AdvancedFilterViewFilter viewFilterId={child.id} />
|
||||||
|
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} />
|
||||||
|
</StyledRow>
|
||||||
|
))}
|
||||||
|
<AdvancedFilterAddFilterRuleSelect
|
||||||
|
viewFilterGroup={currentViewFilterGroup}
|
||||||
|
lastChildPosition={lastChildPosition}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,111 @@
|
|||||||
|
import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter';
|
||||||
|
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
||||||
|
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
|
||||||
|
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||||
|
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
|
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||||
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AdvancedFilterViewFilterOperandSelectProps = {
|
||||||
|
viewFilterId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterViewFilterOperandSelect = ({
|
||||||
|
viewFilterId,
|
||||||
|
}: AdvancedFilterViewFilterOperandSelectProps) => {
|
||||||
|
const dropdownId = `advanced-filter-view-filter-operand-${viewFilterId}`;
|
||||||
|
|
||||||
|
const filter = useCurrentViewFilter({ viewFilterId });
|
||||||
|
|
||||||
|
const isDisabled = !filter?.fieldMetadataId;
|
||||||
|
|
||||||
|
const { closeDropdown } = useDropdown(dropdownId);
|
||||||
|
|
||||||
|
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters();
|
||||||
|
|
||||||
|
const handleOperandChange = (operand: ViewFilterOperand) => {
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
throw new Error('Filter is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, displayValue } = getInitialFilterValue(
|
||||||
|
filter.definition.type,
|
||||||
|
operand,
|
||||||
|
filter.value,
|
||||||
|
filter.displayValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
upsertCombinedViewFilter({
|
||||||
|
...filter,
|
||||||
|
operand,
|
||||||
|
value,
|
||||||
|
displayValue,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const operandsForFilterType = isDefined(filter?.definition)
|
||||||
|
? getOperandsForFilterDefinition(filter.definition)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (isDisabled === true) {
|
||||||
|
return (
|
||||||
|
<SelectControl
|
||||||
|
selectedOption={{
|
||||||
|
label: filter?.operand
|
||||||
|
? getOperandLabel(filter.operand)
|
||||||
|
: 'Select operand',
|
||||||
|
value: null,
|
||||||
|
}}
|
||||||
|
isDisabled
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<Dropdown
|
||||||
|
disableBlur
|
||||||
|
dropdownId={dropdownId}
|
||||||
|
clickableComponent={
|
||||||
|
<SelectControl
|
||||||
|
selectedOption={{
|
||||||
|
label: filter.operand
|
||||||
|
? getOperandLabel(filter.operand)
|
||||||
|
: 'Select operand',
|
||||||
|
value: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dropdownComponents={
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{operandsForFilterType.map((filterOperand, index) => (
|
||||||
|
<MenuItem
|
||||||
|
key={`select-filter-operand-${index}`}
|
||||||
|
onClick={() => {
|
||||||
|
handleOperandChange(filterOperand);
|
||||||
|
}}
|
||||||
|
text={getOperandLabel(filterOperand)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||||
|
dropdownOffset={{ y: 8, x: 0 }}
|
||||||
|
dropdownPlacement="bottom-start"
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,70 @@
|
|||||||
|
import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter';
|
||||||
|
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||||
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
|
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
|
|
||||||
|
type AdvancedFilterViewFilterValueInputProps = {
|
||||||
|
viewFilterId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdvancedFilterViewFilterValueInput = ({
|
||||||
|
viewFilterId,
|
||||||
|
}: AdvancedFilterViewFilterValueInputProps) => {
|
||||||
|
const dropdownId = `advanced-filter-view-filter-value-input-${viewFilterId}`;
|
||||||
|
|
||||||
|
const filter = useCurrentViewFilter({ viewFilterId });
|
||||||
|
|
||||||
|
const isDisabled = !filter?.fieldMetadataId || !filter.operand;
|
||||||
|
|
||||||
|
const {
|
||||||
|
setFilterDefinitionUsedInDropdown,
|
||||||
|
setSelectedOperandInDropdown,
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||||
|
setSelectedFilter,
|
||||||
|
} = useFilterDropdown();
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
return (
|
||||||
|
<SelectControl
|
||||||
|
isDisabled
|
||||||
|
selectedOption={{
|
||||||
|
label: filter?.displayValue ?? '',
|
||||||
|
value: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
disableBlur
|
||||||
|
dropdownId={dropdownId}
|
||||||
|
clickableComponent={
|
||||||
|
<SelectControl
|
||||||
|
selectedOption={{
|
||||||
|
label: filter?.displayValue ?? '',
|
||||||
|
value: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onOpen={() => {
|
||||||
|
setFilterDefinitionUsedInDropdown(filter.definition);
|
||||||
|
setSelectedOperandInDropdown(filter.operand);
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded(true);
|
||||||
|
setSelectedFilter(filter);
|
||||||
|
}}
|
||||||
|
dropdownComponents={
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<ObjectFilterDropdownFilterInput />
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||||
|
dropdownOffset={{ y: 8, x: 0 }}
|
||||||
|
dropdownPlacement="bottom-start"
|
||||||
|
dropdownMenuWidth={280}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
|
||||||
|
|
||||||
|
export const ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS = [
|
||||||
|
{
|
||||||
|
value: ViewFilterGroupLogicalOperator.AND,
|
||||||
|
label: 'And',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ViewFilterGroupLogicalOperator.OR,
|
||||||
|
label: 'Or',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,14 @@
|
|||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
|
||||||
|
export const useAdvancedFilterDropdown = (viewFilterId?: string) => {
|
||||||
|
const advancedFilterDropdownId = `advanced-filter-view-filter-field-${viewFilterId}`;
|
||||||
|
|
||||||
|
const { closeDropdown: closeAdvancedFilterDropdown } = useDropdown(
|
||||||
|
advancedFilterDropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeAdvancedFilterDropdown,
|
||||||
|
advancedFilterDropdownId,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
|
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||||
|
|
||||||
|
export const useCurrentViewFilter = ({
|
||||||
|
viewFilterId,
|
||||||
|
}: {
|
||||||
|
viewFilterId?: string;
|
||||||
|
}) => {
|
||||||
|
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||||
|
availableFilterDefinitionsComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||||
|
|
||||||
|
const viewFilter = currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||||
|
(viewFilter) => viewFilter.id === viewFilterId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!viewFilter) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [filter] = mapViewFiltersToFilters(
|
||||||
|
[viewFilter],
|
||||||
|
availableFilterDefinitions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
};
|
@ -0,0 +1,59 @@
|
|||||||
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
|
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const useCurrentViewViewFilterGroup = ({
|
||||||
|
viewFilterGroupId,
|
||||||
|
}: {
|
||||||
|
viewFilterGroupId?: string;
|
||||||
|
}) => {
|
||||||
|
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||||
|
|
||||||
|
const viewFilterGroup =
|
||||||
|
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.find(
|
||||||
|
(viewFilterGroup) => viewFilterGroup.id === viewFilterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(viewFilterGroup)) {
|
||||||
|
return {
|
||||||
|
currentViewFilterGroup: undefined,
|
||||||
|
childViewFiltersAndViewFilterGroups: [] as (
|
||||||
|
| ViewFilter
|
||||||
|
| ViewFilterGroup
|
||||||
|
)[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const childViewFilters =
|
||||||
|
currentViewWithCombinedFiltersAndSorts?.viewFilters.filter(
|
||||||
|
(viewFilterToFilter) =>
|
||||||
|
viewFilterToFilter.viewFilterGroupId === viewFilterGroup.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const childViewFilterGroups =
|
||||||
|
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.filter(
|
||||||
|
(viewFilterGroupToFilter) =>
|
||||||
|
viewFilterGroupToFilter.parentViewFilterGroupId === viewFilterGroup.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const childViewFiltersAndViewFilterGroups = [
|
||||||
|
...(childViewFilterGroups ?? []),
|
||||||
|
...(childViewFilters ?? []),
|
||||||
|
].sort((a, b) => {
|
||||||
|
const positionA = a.positionInViewFilterGroup ?? 0;
|
||||||
|
const positionB = b.positionInViewFilterGroup ?? 0;
|
||||||
|
return positionA - positionB;
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastChildPosition =
|
||||||
|
childViewFiltersAndViewFilterGroups[
|
||||||
|
childViewFiltersAndViewFilterGroups.length - 1
|
||||||
|
]?.positionInViewFilterGroup ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentViewFilterGroup: viewFilterGroup,
|
||||||
|
childViewFiltersAndViewFilterGroups,
|
||||||
|
lastChildPosition,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,111 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
|
||||||
|
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||||
|
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
|
||||||
|
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const useDeleteCombinedViewFilterGroup = (
|
||||||
|
viewBarComponentId?: string,
|
||||||
|
) => {
|
||||||
|
const unsavedToUpsertViewFilterGroupsCallbackState =
|
||||||
|
useRecoilComponentCallbackStateV2(
|
||||||
|
unsavedToUpsertViewFilterGroupsComponentFamilyState,
|
||||||
|
viewBarComponentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsavedToDeleteViewFilterGroupIdsCallbackState =
|
||||||
|
useRecoilComponentCallbackStateV2(
|
||||||
|
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
|
||||||
|
viewBarComponentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
|
||||||
|
currentViewIdComponentState,
|
||||||
|
viewBarComponentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getViewFromCache } = useGetViewFromCache();
|
||||||
|
|
||||||
|
const deleteCombinedViewFilterGroup = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
async (filterGroupId: string) => {
|
||||||
|
const currentViewId = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
currentViewIdCallbackState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
unsavedToUpsertViewFilterGroupsCallbackState({
|
||||||
|
viewId: currentViewId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsavedToDeleteViewFilterGroupIds = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
unsavedToDeleteViewFilterGroupIdsCallbackState({
|
||||||
|
viewId: currentViewId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentViewId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentView = await getViewFromCache(currentViewId);
|
||||||
|
|
||||||
|
if (!currentView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingFilterGroupInCurrentView =
|
||||||
|
currentView.viewFilterGroups?.find(
|
||||||
|
(viewFilterGroup) => viewFilterGroup.id === filterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchingFilterGroupInUnsavedFilterGroups =
|
||||||
|
unsavedToUpsertViewFilterGroups.find(
|
||||||
|
(viewFilterGroup) => viewFilterGroup.id === filterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isDefined(matchingFilterGroupInUnsavedFilterGroups)) {
|
||||||
|
set(
|
||||||
|
unsavedToUpsertViewFilterGroupsCallbackState({
|
||||||
|
viewId: currentViewId,
|
||||||
|
}),
|
||||||
|
unsavedToUpsertViewFilterGroups.filter(
|
||||||
|
(viewFilterGroup) => viewFilterGroup.id !== filterGroupId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(matchingFilterGroupInCurrentView)) {
|
||||||
|
set(
|
||||||
|
unsavedToDeleteViewFilterGroupIdsCallbackState({
|
||||||
|
viewId: currentViewId,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
...new Set([
|
||||||
|
...unsavedToDeleteViewFilterGroupIds,
|
||||||
|
matchingFilterGroupInCurrentView.id,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
currentViewIdCallbackState,
|
||||||
|
getViewFromCache,
|
||||||
|
unsavedToDeleteViewFilterGroupIdsCallbackState,
|
||||||
|
unsavedToUpsertViewFilterGroupsCallbackState,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleteCombinedViewFilterGroup,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,54 @@
|
|||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
|
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
|
||||||
|
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
export const useUpsertCombinedViewFilterGroup = () => {
|
||||||
|
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||||
|
ViewComponentInstanceContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsavedToUpsertViewFilterGroupsCallbackState =
|
||||||
|
useRecoilComponentCallbackStateV2(
|
||||||
|
unsavedToUpsertViewFilterGroupsComponentFamilyState,
|
||||||
|
instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const upsertCombinedViewFilterGroup = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
(newViewFilterGroup: Omit<ViewFilterGroup, '__typename'>) => {
|
||||||
|
const currentViewUnsavedToUpsertViewFilterGroups =
|
||||||
|
unsavedToUpsertViewFilterGroupsCallbackState({
|
||||||
|
viewId: newViewFilterGroup.viewId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
currentViewUnsavedToUpsertViewFilterGroups,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newViewFilterWithTypename: ViewFilterGroup = {
|
||||||
|
...newViewFilterGroup,
|
||||||
|
__typename: 'ViewFilterGroup',
|
||||||
|
};
|
||||||
|
|
||||||
|
set(
|
||||||
|
unsavedToUpsertViewFilterGroupsCallbackState({
|
||||||
|
viewId: newViewFilterGroup.viewId,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
...unsavedToUpsertViewFilterGroups.filter(
|
||||||
|
(viewFilterGroup) => viewFilterGroup.id !== newViewFilterGroup.id,
|
||||||
|
),
|
||||||
|
newViewFilterWithTypename,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[unsavedToUpsertViewFilterGroupsCallbackState],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { upsertCombinedViewFilterGroup };
|
||||||
|
};
|
@ -45,7 +45,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
|
|||||||
primaryLinkLabel
|
primaryLinkLabel
|
||||||
secondaryLinks
|
secondaryLinks
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
||||||
__typename
|
__typename
|
||||||
@ -324,4 +324,4 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
|||||||
primaryLinkLabel
|
primaryLinkLabel
|
||||||
secondaryLinks
|
secondaryLinks
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
@ -3,10 +3,19 @@ import { gql } from '@apollo/client';
|
|||||||
|
|
||||||
import { peopleQueryResult } from '~/testing/mock-data/people';
|
import { peopleQueryResult } from '~/testing/mock-data/people';
|
||||||
|
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) {
|
query FindManyPeople(
|
||||||
people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
$filter: PersonFilterInput
|
||||||
|
$orderBy: [PersonOrderByInput]
|
||||||
|
$lastCursor: String
|
||||||
|
$limit: Int
|
||||||
|
) {
|
||||||
|
people(
|
||||||
|
filter: $filter
|
||||||
|
orderBy: $orderBy
|
||||||
|
first: $limit
|
||||||
|
after: $lastCursor
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
@ -27,38 +36,51 @@ export const query = gql`
|
|||||||
|
|
||||||
export const mockPageSize = 2;
|
export const mockPageSize = 2;
|
||||||
|
|
||||||
export const peopleMockWithIdsOnly: RecordGqlConnection = { ...peopleQueryResult.people,edges: peopleQueryResult.people.edges.map((edge) => ({ ...edge, node: { __typename: 'Person', id: edge.node.id } })) };
|
export const peopleMockWithIdsOnly: RecordGqlConnection = {
|
||||||
|
...peopleQueryResult.people,
|
||||||
|
edges: peopleQueryResult.people.edges.map((edge) => ({
|
||||||
|
...edge,
|
||||||
|
node: { __typename: 'Person', id: edge.node.id },
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
export const firstRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize].cursor;
|
export const firstRequestLastCursor =
|
||||||
export const secondRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
peopleMockWithIdsOnly.edges[mockPageSize].cursor;
|
||||||
export const thirdRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
export const secondRequestLastCursor =
|
||||||
|
peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
||||||
|
export const thirdRequestLastCursor =
|
||||||
|
peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
||||||
|
|
||||||
export const variablesFirstRequest = {
|
export const variablesFirstRequest = {
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
limit: mockPageSize,
|
limit: mockPageSize,
|
||||||
orderBy: undefined
|
orderBy: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variablesSecondRequest = {
|
export const variablesSecondRequest = {
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
limit: mockPageSize,
|
limit: mockPageSize,
|
||||||
orderBy: undefined,
|
orderBy: undefined,
|
||||||
lastCursor: firstRequestLastCursor
|
lastCursor: firstRequestLastCursor,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variablesThirdRequest = {
|
export const variablesThirdRequest = {
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
limit: mockPageSize,
|
limit: mockPageSize,
|
||||||
orderBy: undefined,
|
orderBy: undefined,
|
||||||
lastCursor: secondRequestLastCursor
|
lastCursor: secondRequestLastCursor,
|
||||||
}
|
};
|
||||||
|
|
||||||
const paginateRequestResponse = (response: RecordGqlConnection, start: number, end: number, hasNextPage: boolean, totalCount: number) => {
|
const paginateRequestResponse = (
|
||||||
|
response: RecordGqlConnection,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
hasNextPage: boolean,
|
||||||
|
totalCount: number,
|
||||||
|
) => {
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
edges: [
|
edges: [...response.edges.slice(start, end)],
|
||||||
...response.edges.slice(start, end)
|
|
||||||
],
|
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
...response.pageInfo,
|
...response.pageInfo,
|
||||||
startCursor: response.edges[start].cursor,
|
startCursor: response.edges[start].cursor,
|
||||||
@ -66,17 +88,35 @@ const paginateRequestResponse = (response: RecordGqlConnection, start: number, e
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
} satisfies RecordGqlConnection['pageInfo'],
|
} satisfies RecordGqlConnection['pageInfo'],
|
||||||
totalCount,
|
totalCount,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const responseFirstRequest = {
|
export const responseFirstRequest = {
|
||||||
people: paginateRequestResponse(peopleMockWithIdsOnly, 0, mockPageSize, true, 6),
|
people: paginateRequestResponse(
|
||||||
|
peopleMockWithIdsOnly,
|
||||||
|
0,
|
||||||
|
mockPageSize,
|
||||||
|
true,
|
||||||
|
6,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const responseSecondRequest = {
|
export const responseSecondRequest = {
|
||||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize, mockPageSize * 2, true, 6),
|
people: paginateRequestResponse(
|
||||||
|
peopleMockWithIdsOnly,
|
||||||
|
mockPageSize,
|
||||||
|
mockPageSize * 2,
|
||||||
|
true,
|
||||||
|
6,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const responseThirdRequest = {
|
export const responseThirdRequest = {
|
||||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize * 2, mockPageSize * 3, false, 6),
|
people: paginateRequestResponse(
|
||||||
|
peopleMockWithIdsOnly,
|
||||||
|
mockPageSize * 2,
|
||||||
|
mockPageSize * 3,
|
||||||
|
false,
|
||||||
|
6,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
|
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
|
||||||
|
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||||
|
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/components/MenuItemLeftContent';
|
||||||
|
import { StyledMenuItemBase } from '@/ui/navigation/menu-item/internals/components/StyledMenuItemBase';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
|
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||||
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
|
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { IconFilter, Pill } from 'twenty-ui';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export const StyledContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
|
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledMenuItemSelect = styled(StyledMenuItemBase)`
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.background.transparent.light};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledPill = styled(Pill)`
|
||||||
|
background: ${({ theme }) => theme.color.blueAccent10};
|
||||||
|
color: ${({ theme }) => theme.color.blue};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AdvancedFilterButton = () => {
|
||||||
|
const advancedFilterQuerySubFilterCount = 0; // TODO
|
||||||
|
|
||||||
|
const { openDropdown: openAdvancedFilterDropdown } = useDropdown(
|
||||||
|
ADVANCED_FILTER_DROPDOWN_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { closeDropdown: closeObjectFilterDropdown } = useDropdown(
|
||||||
|
OBJECT_FILTER_DROPDOWN_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { currentViewId, currentViewWithCombinedFiltersAndSorts } =
|
||||||
|
useGetCurrentView();
|
||||||
|
|
||||||
|
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup();
|
||||||
|
|
||||||
|
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters();
|
||||||
|
|
||||||
|
const objectMetadataId =
|
||||||
|
currentViewWithCombinedFiltersAndSorts?.objectMetadataId;
|
||||||
|
|
||||||
|
if (!objectMetadataId) {
|
||||||
|
throw new Error('Object metadata id is missing from current view');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||||
|
objectId: objectMetadataId ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||||
|
availableFilterDefinitionsComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (!currentViewId) {
|
||||||
|
throw new Error('Missing current view id');
|
||||||
|
}
|
||||||
|
|
||||||
|
const alreadyHasAdvancedFilterGroup =
|
||||||
|
(currentViewWithCombinedFiltersAndSorts?.viewFilterGroups?.length ?? 0) >
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (!alreadyHasAdvancedFilterGroup) {
|
||||||
|
const newViewFilterGroup = {
|
||||||
|
id: v4(),
|
||||||
|
viewId: currentViewId,
|
||||||
|
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||||
|
};
|
||||||
|
|
||||||
|
upsertCombinedViewFilterGroup(newViewFilterGroup);
|
||||||
|
|
||||||
|
const defaultFilterDefinition =
|
||||||
|
availableFilterDefinitions.find(
|
||||||
|
(filterDefinition) =>
|
||||||
|
filterDefinition.fieldMetadataId ===
|
||||||
|
objectMetadataItem?.labelIdentifierFieldMetadataId,
|
||||||
|
) ?? availableFilterDefinitions?.[0];
|
||||||
|
|
||||||
|
if (!defaultFilterDefinition) {
|
||||||
|
throw new Error('Missing default filter definition');
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertCombinedViewFilter({
|
||||||
|
id: v4(),
|
||||||
|
fieldMetadataId: defaultFilterDefinition.fieldMetadataId,
|
||||||
|
operand: getOperandsForFilterDefinition(defaultFilterDefinition)[0],
|
||||||
|
definition: defaultFilterDefinition,
|
||||||
|
value: '',
|
||||||
|
displayValue: '',
|
||||||
|
viewFilterGroupId: newViewFilterGroup.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openAdvancedFilterDropdown();
|
||||||
|
closeObjectFilterDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledMenuItemSelect onClick={handleClick}>
|
||||||
|
<MenuItemLeftContent LeftIcon={IconFilter} text="Advanced filter" />
|
||||||
|
{advancedFilterQuerySubFilterCount > 0 && (
|
||||||
|
<StyledPill label={advancedFilterQuerySubFilterCount.toString()} />
|
||||||
|
)}
|
||||||
|
</StyledMenuItemSelect>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
|
|
||||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
|
||||||
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
|
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
|
||||||
|
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
|
||||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
@ -48,11 +48,13 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
{shoudShowFilterInput ? (
|
{shoudShowFilterInput ? (
|
||||||
<ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} />
|
<ObjectFilterOperandSelectAndInput
|
||||||
|
filterDropdownId={filterDropdownId}
|
||||||
|
/>
|
||||||
) : shouldShowCompositeSelectionSubMenu ? (
|
) : shouldShowCompositeSelectionSubMenu ? (
|
||||||
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
|
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
|
||||||
) : (
|
) : (
|
||||||
<ObjectFilterDropdownFilterSelect />
|
<ObjectFilterDropdownFilterSelect isAdvancedFilterButtonVisible />
|
||||||
)}
|
)}
|
||||||
<MultipleFiltersDropdownFilterOnFilterChangedEffect
|
<MultipleFiltersDropdownFilterOnFilterChangedEffect
|
||||||
filterDefinitionUsedInDropdownType={
|
filterDefinitionUsedInDropdownType={
|
||||||
|
@ -63,6 +63,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
|||||||
: newDate.toLocaleDateString()
|
: newDate.toLocaleDateString()
|
||||||
: '',
|
: '',
|
||||||
definition: filterDefinitionUsedInDropdown,
|
definition: filterDefinitionUsedInDropdown,
|
||||||
|
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsObjectFilterDropdownUnfolded(false);
|
setIsObjectFilterDropdownUnfolded(false);
|
||||||
@ -92,6 +93,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
|||||||
operand: selectedOperandInDropdown,
|
operand: selectedOperandInDropdown,
|
||||||
displayValue: getRelativeDateDisplayValue(relativeDate),
|
displayValue: getRelativeDateDisplayValue(relativeDate),
|
||||||
definition: filterDefinitionUsedInDropdown,
|
definition: filterDefinitionUsedInDropdown,
|
||||||
|
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsObjectFilterDropdownUnfolded(false);
|
setIsObjectFilterDropdownUnfolded(false);
|
||||||
|
@ -2,8 +2,6 @@ import { useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { ObjectFilterDropdownDateInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput';
|
import { ObjectFilterDropdownDateInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput';
|
||||||
import { ObjectFilterDropdownNumberInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput';
|
import { ObjectFilterDropdownNumberInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput';
|
||||||
import { ObjectFilterDropdownOperandButton } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton';
|
|
||||||
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
|
|
||||||
import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect';
|
import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect';
|
||||||
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||||
@ -14,19 +12,11 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
|
|||||||
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
|
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledOperandSelectContainer = styled.div`
|
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes';
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
left: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1000;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type ObjectFilterDropdownFilterInputProps = {
|
type ObjectFilterDropdownFilterInputProps = {
|
||||||
filterDropdownId?: string;
|
filterDropdownId?: string;
|
||||||
@ -38,13 +28,8 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
const {
|
const {
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
|
||||||
} = useFilterDropdown({ filterDropdownId });
|
} = useFilterDropdown({ filterDropdownId });
|
||||||
|
|
||||||
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
|
||||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
);
|
);
|
||||||
@ -74,40 +59,21 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ObjectFilterDropdownOperandButton />
|
|
||||||
{isObjectFilterDropdownOperandSelectUnfolded && (
|
|
||||||
<StyledOperandSelectContainer>
|
|
||||||
<ObjectFilterDropdownOperandSelect />
|
|
||||||
</StyledOperandSelectContainer>
|
|
||||||
)}
|
|
||||||
{isConfigurable && selectedOperandInDropdown && (
|
{isConfigurable && selectedOperandInDropdown && (
|
||||||
<>
|
<>
|
||||||
{[
|
{TEXT_FILTER_TYPES.includes(filterDefinitionUsedInDropdown.type) &&
|
||||||
'TEXT',
|
|
||||||
'EMAIL',
|
|
||||||
'EMAILS',
|
|
||||||
'PHONE',
|
|
||||||
'FULL_NAME',
|
|
||||||
'LINK',
|
|
||||||
'LINKS',
|
|
||||||
'ADDRESS',
|
|
||||||
'ACTOR',
|
|
||||||
'ARRAY',
|
|
||||||
'RAW_JSON',
|
|
||||||
'PHONES',
|
|
||||||
].includes(filterDefinitionUsedInDropdown.type) &&
|
|
||||||
!isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
|
!isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
|
||||||
<ObjectFilterDropdownTextSearchInput />
|
<ObjectFilterDropdownTextSearchInput />
|
||||||
)}
|
)}
|
||||||
{['NUMBER', 'CURRENCY'].includes(
|
{NUMBER_FILTER_TYPES.includes(
|
||||||
filterDefinitionUsedInDropdown.type,
|
filterDefinitionUsedInDropdown.type,
|
||||||
) && <ObjectFilterDropdownNumberInput />}
|
) && <ObjectFilterDropdownNumberInput />}
|
||||||
{filterDefinitionUsedInDropdown.type === 'RATING' && (
|
{filterDefinitionUsedInDropdown.type === 'RATING' && (
|
||||||
<ObjectFilterDropdownRatingInput />
|
<ObjectFilterDropdownRatingInput />
|
||||||
)}
|
)}
|
||||||
{['DATE_TIME', 'DATE'].includes(
|
{DATE_FILTER_TYPES.includes(filterDefinitionUsedInDropdown.type) && (
|
||||||
filterDefinitionUsedInDropdown.type,
|
<ObjectFilterDropdownDateInput />
|
||||||
) && <ObjectFilterDropdownDateInput />}
|
)}
|
||||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||||
<>
|
<>
|
||||||
<ObjectFilterDropdownSearchInput />
|
<ObjectFilterDropdownSearchInput />
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import { ObjectFilterDropdownOperandButton } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton';
|
||||||
|
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
|
||||||
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
const StyledOperandSelectContainer = styled.div`
|
||||||
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
left: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ObjectFilterDropdownFilterOperandSelect = ({
|
||||||
|
filterDropdownId,
|
||||||
|
}: {
|
||||||
|
filterDropdownId?: string;
|
||||||
|
}) => {
|
||||||
|
const { isObjectFilterDropdownOperandSelectUnfoldedState } =
|
||||||
|
useFilterDropdown({ filterDropdownId });
|
||||||
|
|
||||||
|
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ObjectFilterDropdownOperandButton />
|
||||||
|
{isObjectFilterDropdownOperandSelectUnfolded && (
|
||||||
|
<StyledOperandSelectContainer>
|
||||||
|
<ObjectFilterDropdownOperandSelect />
|
||||||
|
</StyledOperandSelectContainer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user