From 6a8a8f07280e7e45439c911bd5ba272b2f5000d7 Mon Sep 17 00:00:00 2001 From: Sammy Teillet Date: Thu, 4 May 2023 13:54:46 +0200 Subject: [PATCH] Add Filters on Table views (#95) * Add filter search logic WIP Filter search Implement filters test: fix sorts tests test: fix filter test feature: search person and display firstname in results feature: fix test for filter component test: mock search filters refactor: create a useSearch hook refactor: move debounce in useSearch and reset status of filter selection feature: debounce set filters refactor: remove useless setSorts feature: add where variable to people query feature: strongly type Filters feature: update WhereTemplate method feature: implement filtering on full name feature: type the useSearch hook feature: use where reducer refactor: create a type for readability feature: use query and mapper from filters feature: implement filter by company feature: search filter results on filter select feature: add loading and results to search results in filters refactor: move render search results in a function feature: display a LOADING when it loads feature: split search input and search filter for different debounce refactor: remove some warnings refactor: remove some warnings * Write test 1 * Write test 2 * test: useSearch is tested * test: update names of default people data * test: add a filter search * Test 3 * Fix tests --------- Co-authored-by: Charles Bochet --- front/src/__stories__/App.stories.tsx | 66 ++++++++ front/src/__tests__/App.test.tsx | 17 ++ front/src/components/chips/PipeChip.tsx | 12 +- front/src/components/table/Table.tsx | 32 +++- .../__stories__/EditableCell.stories.tsx | 17 +- .../table/__tests__/EditableCell.test.tsx | 6 +- .../table-header/FilterDropdownButton.tsx | 156 +++++++++-------- .../table/table-header/SortAndFilterBar.tsx | 20 ++- .../table/table-header/SortDropdownButton.tsx | 15 +- .../table/table-header/TableHeader.tsx | 80 +++++---- .../FilterDropdownButton.stories.tsx | 157 +++++++++++++----- .../__stories__/SortAndFilterBar.stories.tsx | 19 ++- .../SortDropdownButton.stories.tsx | 8 +- .../__tests__/FilterDropdownButton.test.tsx | 130 +++++++++++---- .../__tests__/SortDropdownButton.test.tsx | 28 ++-- .../table/table-header/interface.ts | 44 +++-- .../src/interfaces/company.interface.test.ts | 1 + front/src/interfaces/company.interface.ts | 13 +- front/src/interfaces/user.interface.test.ts | 36 ++++ front/src/interfaces/user.interface.ts | 40 ++++- front/src/interfaces/workspace.interface.ts | 19 ++- .../src/layout/navbar/WorkspaceContainer.tsx | 2 +- .../navbar/__stories__/Navbar.stories.tsx | 3 +- .../pages/companies/__stories__/mock-data.ts | 1 + front/src/pages/companies/companies-table.tsx | 2 +- front/src/pages/people/People.tsx | 25 ++- .../people/__stories__/People.stories.tsx | 15 ++ front/src/pages/people/default-data.ts | 18 +- front/src/pages/people/people-table.tsx | 93 ++++++++--- front/src/services/people/select.ts | 32 +++- front/src/services/search/search.ts | 105 ++++++++++++ .../src/services/users/__tests_/index.test.ts | 11 -- front/src/services/users/index.tsx | 6 +- 33 files changed, 913 insertions(+), 316 deletions(-) create mode 100644 front/src/__stories__/App.stories.tsx create mode 100644 front/src/__tests__/App.test.tsx create mode 100644 front/src/interfaces/user.interface.test.ts create mode 100644 front/src/services/search/search.ts delete mode 100644 front/src/services/users/__tests_/index.test.ts diff --git a/front/src/__stories__/App.stories.tsx b/front/src/__stories__/App.stories.tsx new file mode 100644 index 000000000..2c1964016 --- /dev/null +++ b/front/src/__stories__/App.stories.tsx @@ -0,0 +1,66 @@ +import { MockedProvider } from '@apollo/client/testing'; +import App from '../App'; +import { GET_CURRENT_USER } from '../services/users'; +import { MemoryRouter } from 'react-router-dom'; +import { GET_PEOPLE } from '../services/people'; + +const component = { + title: 'App', + component: App, +}; + +localStorage.setItem('refreshToken', 'xxx-refresh'); +localStorage.setItem('accessToken', 'xxx-access'); + +const mocks = [ + { + request: { + query: GET_CURRENT_USER, + }, + result: { + data: { + users: [ + { + id: '16506ba8-196c-4c13-a4a7-a22cb5eccfa1', + email: 'charles@twenty.com', + displayName: 'Charles Bochet', + workspace_member: { + workspace: { + id: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419', + domain_name: 'twenty.com', + display_name: 'Twenty', + logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAJ82lDQ1BJQ0MgUHJvZmlsZQAASImFlnk8lWkbx+/nOfuG43DsHPuWfTvIvm/Z11SOY986Tra0SypMSpJQosZaNJUlI0mKygiFFjVkhDKNFqlU5jnMTE3v+3nf63yuz/X9XM99/851388fzw8AUgCLw4mDBQCIT0jiejlYMwICgxi4KYACkoAKmIDKYm/iWHl4uAIk/q7/jnfDAOLVuxo8rf98/j+DGha+iQ0A5IEwk83hJiG8D2Gf1CQOj0cRFuIiQyE8x+PIZYbRPA5dYdHlNT5eNgivAgBPZrG4kQAQmUifkcKORHSIAQhrJ4RFJyDM0zePiEtOQ7iH14+P3xiG8HuEVZD1HABIDN48od9oRv5LP/QffRYr8h+Oj0tm/3Uu3o2QwxN8vZEqjqQkiACaIA4kgzTAABzABRuRTjTSCUfu/r/vYy7vs0FWcsBmZEc0iARRIAnZb/+NlveyUhJIBSxkTTjScUV+Nrz3uCL5hr6sCtFvfe1lvAXALGxpaanta89VAYCLe5CzPPvaU24HgE8EgJ4cdjI3ZaXHu3qAAUTAD4SAGJAG8kAFaABdYAhMgSWwA87AHfiAQLAesJF545GpUsFWsAtkgRxwEBwBxaAMnALV4Cw4D5pBG7gKboDboB8MgUdgDEyCF2AOvAOLEAThIApEg8QgGUgRUod0ISZkDtlBrpAXFAiFQJFQApQMbYV2QzlQPlQMlUM10E/QJegqdBMagB5A49AM9Br6CKNgMiwES8FKsBbMhK1gF9gHXgdHwolwOpwJH4CL4Ar4DNwEX4Vvw0PwGPwCnkcBFAlFR8miNFBMlA3KHRWEikBxUdtR2ahCVAWqHtWK6kbdRY2hZlEf0Fg0Dc1Aa6BN0Y5oXzQbnYjejs5FF6Or0U3oLvRd9Dh6Dv0FQ8FIYtQxJhgnTAAmEpOKycIUYioxjZjrmCHMJOYdFoulY5WxRlhHbCA2BrsFm4s9jm3AdmAHsBPYeRwOJ4ZTx5nh3HEsXBIuC3cMdwZ3BTeIm8S9x5PwMnhdvD0+CJ+Az8AX4mvx7fhB/BR+kSBAUCSYENwJYYTNhDzCaUIr4Q5hkrBIpBKViWZEH2IMcRexiFhPvE4cJb4hkUhyJGOSJymatJNURDpH6iGNkz6QBclqZBtyMDmZfIBcRe4gPyC/oVAoShRLShAliXKAUkO5RnlCec9H49Pkc+IL49vBV8LXxDfI95KfwK/Ib8W/nj+dv5D/Av8d/lkBgoCSgI0AS2C7QInAJYERgXkqjapDdafGU3OptdSb1GlBnKCSoJ1gmGCm4CnBa4ITNBRNnmZDY9N2007TrtMmhbBCykJOQjFCOUJnhfqE5oQFhfWF/YTThEuELwuP0VF0JboTPY6eRz9PH6Z/FJESsRIJF9kvUi8yKLIgKiFqKRoumi3aIDok+lGMIWYnFit2SKxZ7LE4WlxN3FM8VfyE+HXxWQkhCVMJtkS2xHmJh5KwpJqkl+QWyVOSvZLzUtJSDlIcqWNS16RmpenSltIx0gXS7dIzMjQZc5lomQKZKzLPGcIMK0Yco4jRxZiTlZR1lE2WLZftk12UU5bzlcuQa5B7LE+UZ8pHyBfId8rPKcgouClsVahTeKhIUGQqRikeVexWXFBSVvJX2qvUrDStLKrspJyuXKc8qkJRsVBJVKlQuaeKVWWqxqoeV+1Xg9UM1KLUStTuqMPqhurR6sfVB1ZhVhmvSlhVsWpEg6xhpZGiUacxrknXdNXM0GzWfKmloBWkdUirW+uLtoF2nPZp7Uc6gjrOOhk6rTqvddV02boluvf0KHr2ejv0WvRe6avrh+uf0L9vQDNwM9hr0Gnw2dDIkGtYbzhjpGAUYlRqNMIUYnowc5k9xhhja+Mdxm3GH0wMTZJMzpv8YaphGmtaazq9Wnl1+OrTqyfM5MxYZuVmY+YM8xDzk+ZjFrIWLIsKi6eW8pZhlpWWU1aqVjFWZ6xeWmtbc60brRdsTGy22XTYomwdbLNt++wE7Xztiu2e2MvZR9rX2c85GDhscehwxDi6OB5yHHGScmI71TjNORs5b3PuciG7eLsUuzx1VXPlura6wW7ObofdRtcorklY0+wO3J3cD7s/9lD2SPT42RPr6eFZ4vnMS8drq1e3N817g3et9zsfa588n0e+Kr7Jvp1+/H7BfjV+C/62/vn+YwFaAdsCbgeKB0YHtgThgvyCKoPm19qtPbJ2MtggOCt4eJ3yurR1N9eLr49bf3kD/wbWhgshmBD/kNqQTyx3VgVrPtQptDR0jm3DPsp+EWYZVhA2E24Wnh8+FWEWkR8xHWkWeThyJsoiqjBqNtomujj6VYxjTFnMQqx7bFXsUpx/XEM8Pj4k/lKCYEJsQtdG6Y1pGwc46pwszliiSeKRxDmuC7dyE7Rp3aaWJCHk49mbrJK8J3k8xTylJOV9ql/qhTRqWkJa72a1zfs3T6Xbp/+4Bb2FvaVzq+zWXVvHt1ltK98ObQ/d3rlDfkfmjsmdDjurdxF3xe76JUM7Iz/j7W7/3a2ZUpk7Myf2OOypy+LL4maN7DXdW7YPvS96X99+vf3H9n/JDsu+laOdU5jzKZede+sHnR+Kflg6EHGgL88w78RB7MGEg8OHLA5V51Pz0/MnDrsdbipgFGQXvD2y4cjNQv3CsqPEo8lHx4pci1qOKRw7eOxTcVTxUIl1SUOpZOn+0oXjYccHT1ieqC+TKssp+3gy+uT9cofypgqlisJT2FMpp56d9jvd/SPzx5pK8cqcys9VCVVj1V7VXTVGNTW1krV5dXBdct3MmeAz/Wdtz7bUa9SXN9Abcs6Bc8nnnv8U8tPweZfznReYF+ovKl4sbaQ1ZjdBTZub5pqjmsdaAlsGLjlf6mw1bW38WfPnqjbZtpLLwpfz2ontme1LV9KvzHdwOmavRl6d6NzQ+ehawLV7XZ5dfdddrvfcsL9xrduq+0qPWU/bTZObl24xbzXfNrzd1GvQ2/iLwS+NfYZ9TXeM7rT0G/e3DqweaB+0GLx61/bujXtO924PrRkaGPYdvj8SPDJ2P+z+9IO4B68epjxcfLRzFDOa/VjgceETyScVv6r+2jBmOHZ53Ha896n300cT7IkXv2367dNk5jPKs8Ipmamaad3pthn7mf7na59PvuC8WJzN+p36e+lLlZcX/7D8o3cuYG7yFffV0uvcN2Jvqt7qv+2c95h/8i7+3eJC9nux99UfmB+6P/p/nFpM/YT7VPRZ9XPrF5cvo0vxS0scFpe1bAVQSMIREQC8rgKAEggArR/xD2tXPNdffgb6xtn8zeDO+FcuMlvxZcthCEA9UryQtOkA4BySSjsRbUsAeBbRxxLAenr/5F/xf//vO17xe7zAIs73pC2PHhz2LAffxYoX/Oac31fAm0IffF//BHDgtSSRPWioAAAAeGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAAqACAAQAAAABAAAAMqADAAQAAAABAAAAMgAAAADJOUoaAAAACXBIWXMAAAsTAAALEwEAmpwYAAACBGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MjAwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjIwMDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgqA8BIZAAAOx0lEQVRoBe1ZaWxU1xX+3jozHtt4B2OzeGEnELYAIaQQwqI0C1FSGmWp0kqRmkpVm0ptf1SpUKVKVaWqP1oprdq0StTsLUlElUYJAZJCUkLCTlhsdvAGxsbbzLzt9jt3bLALBI8SGinKlWfG8967955zz3fO+c4ZQz32G4UvwTC/BDpoFb5S5GqW/KJwOjyLUDqlDCiDL/S/DCDi6/Jh6euKT/4/lbIvF2ToFRFG2ZS4X2XqwsG3kO/h5aIaKoSSZx2+RfyUZy5/TBb5XMenKkIjwLAtGGkfqisDlaH0/FMubyRtmPnxrDDRJUkji/eDCFFzCmaMcwtcwBqe4T+LZldWRA7RNGAohajpAvzacqjF02CMLoUR45TOXkTHmmHuPAZHLJNPYamMsqhgVxr+/Mkwbp2O4OMDcHafgJnxudZnEfPac4coInuJDxiyaxQhHfD74ythL5wGuyBfAHURJYqQCZrPIvP39+DsaISZR+sImnooNBWJ3TgB4MtvaELwqxfgyk7EnHjY9RhDbC6C6gt8CzJU6Yl7kFgxHyaVELgPDBHFpLO41RUwZtfBbEshok8YXoCwegSsutEDjwIXumC1pzRELx3Dpduf13+XWcSgYwctPQgeW47EjHqEQQiTfqJ6CZnjLTD7UgiLCmBVldMKMZi8Hzm0IqGITvrRqomwSkfAJywtKht8dAAJl/OpvRzU9RpDFNE78VSj6iINJyILFpXwjzcj+NN6OIfawKAEwzXhTRkF66HlhGFWNJOx2Pd588Z6hmmxrIHwbCfsHSegimIwfNq0/1m5P/BF/PDzGEMUMUwT6gJPfN4EODx1gVmUyiB8+g3kNZyDqizQTi0u5Bxug/+L52GMKoQ1ktfTHsIxRbBqqvU8ES9oPAm7pQtGdSFC+pRpUD0uqsM2I5t+kJCMxDMDMRk/s1rmrJt2iYFZEv+VF8EupWD9w2+7APvoOaAsCXhh9mRFiMIYHEYrq6OP4ZjQ6UzDmFULq6SQMGIE4ytiEDDyHR0BZSPV2YPwdCe8TAZBkpY2GTDOUFHOVVRIJ92BjXP8HGIRPZdmVwyl/SgQr84uSQzJKV+8LphiZJP8LY8EPACBlRaY96LWDlgSeovi8BmuDcNFuHymjmRGRQn9y0UURvBbziLcuAv2+0dglsSz1rq4yfC1GaKIIfhnnghbOrggF2Fys0cVwZtWCftDClVVSC2pjmTrs73wixNQI+lPze2IxhbBrK3SO4vCUcNpwqobQYGFcPF0WHcuhF1VoZWW+/KSza2KYoTT6+GP2wLrpX/DKaHlxeI5jqHQUjzhfEJm90n47ed1yDVtB/ajdyCzsA5eZwqZ873IhAFSC2uhnnwY5iImyr3tUHPrYNOvIipq0FLhnkaKohB+Zzmc794DR0I1T1oOWzK/ygRaGXke9E139WJEt06G6khpJiCK5jKGWEQmClYtChts+BjRg4xKVM6qLIPxozWIztBX+jIweGqxsmIdlv09hxD4BNjMer2vQWnDtg7Yu04gKmC0mjheCywQVG3nkd55GGggK+jloUwchcRtN5HG5GsIWysWINp8mGFb+73kz2EzgiGKaBohYbIiD+brHyEoGQF3FTeiiJZjwxg/SvuAfBd6JeiToaaUwq69lATDw4xW9AuBoDp2BvaYCqS37IHxt42I7z+HwOLkGAPFuv3otWMouOsWSKjHmJEIpo6EeZwHFnNkZbk6rDFEkew0ikkntOh46ul3kGk6C2vFbNgVtIrjZKm7POh5iE60AJv3I1pBClNIbBMmkmeUUJYEHSzDQznShBRzjP3jlxDVJ5G6axrMWfUw6qvhlJdQXvpkGNIKPH76pz+pCmpPE1DpwvS4kZzaMMYQRbJzOFliOU/TKUvAfnM3/E37uMFIhKNLuBmzeYp8qqkdFiGiSE/Mb92e3U9gdbYdJqOVEafScZNBohHqH9sR3kdfoh/EJ43TBx0SZsGOg+RzPgqXztO5hKrDqhnLaPYf2JRBxBimHjpwXF1fWsYoy4NLHEWHW+HsPcPExaWlPkkQaskYM3wSJqEzsKEirNBBWFUWZq+dT8G7exbcR1bBSsSQ3sfc8vIWoLEVdkMH3Admwp89FXacZxqPAWPJtIsYDembegGx/jDGEItc8XlGmEhiW9LVgg84oKGdRMEmRQ7++E948yYCk8Yi3NGAOHOEODdIWcLyfNj3LdVKeMdaEP3yZeQxgaIyiaiPSXRCFXzWO5GXgktFTPplVFMOHKOfiHLD9JNrK8KlTB3WKZpQCFFALy90n3ag/e39J6E+PIKIJ+nm0VKF8awvSW2yYBLizPayhMr0wWkmExhXAHWoHalxRci7ZTZS3SkEPd2I0w/FyT36iSN+QlZA9qn3u9bbsBQZWESimuBW4qLU74xdMBgrRXDYDNsUXLVTZJ64WZLHUM7lSUK18rSqM2Ec/LWr0XXgOJzR5YjNv4GhPB/m9v2SSoBptTphWgzZUbhN8vGwR06KZFfVmmSTGzdXzT3wmETVDWMACc+EVaadrHfncbjtvXD3noLX1Y1YMa3CA7CWzEXhrXMu8q9AWMS696Hm18E/0cZIeBpg1EMZoyYDjuw2nGHk2mmURCyNBaPXQ4aMNbxzDpxFN8AaTVhIfd+/a9jZDf+tj2D9eROihxbAXrNM+4nclmfClIfg4AmEL2xEovUCaxobmdY+xM710ddIhRj1xPKW3rB/0U/5yN0ijFhGex/66sibvr0KiQnVevkBBTSi5Y10JbZmKTLcwf7dRoRHmxHOJalkSRy098Dax6RJa8XK81gq+PCWTYe7ZA68de/C2dIAK84+gECZS+m1L/5zZW1yU0RoLiOMf+NYuI+vhlUkJTDZL7cKSdG9ti6YBLY9lhCTzglpurvyZqT3HkfyQAsTXSudN4DDwgzSXRnNk2eID0YkYa9cwLqmBMb3v4Fw1k4Yf31bw9QQfxQIM3pKrXK1mj83RSicojWimyYgRiWk5gBL4L6N2+EwaVpnOqGOXEDvPdOQ98T9bBcldMi2bp2BcNdpmGTImtnKPGHQcgDN3VA//DpcKhH1l9XBuU4Ep3oYmqkY632bymJkXpYXSfgbMP8g4+SmCBc0RyQQvn8Q4ZKb2FggE/7Da0i8fQiKhZKqK0e0ejbi08az2UClKasEN2tqDYKyfMRoTT1EB7FudxrBnBrYN1NRhnUpq7s/PgjV5yHx1GNQBYQdc01m+wFYr++AI3uIH/angEF6XCOzD35S/ucCitncOdiCYN9RRB/sRvyVPfDunQKsoNPPmAxLkmEz6Qed3alkoSQCspAK5tVBbdoLo4icTE6YGvrS8HtgCWsg8iou73UQmqRAyYdWIeQFOXh5qbpq+NNrkfr9eiSkVpG4LIcxaFhr56xcO+j7tf8lTk02qYxdR6DorP73liHGesWuqUJ46Dj8lzfBeHId20IFMGortSQWIan7xBv3sbWU0L4TnqVP3bsA8SWz9J4RywNv/VbY2z9BsHUf+2MpWKNIVEkkI5JKd1QpWQJ9aoOsIYFAm5VzsxrlBi3ZUrK7wKKtG+E3FyF+/1JWkyFSz7wB+7ltiI/miVeRMzmsDM+xHqcjo8CGNbEaUS1DdCczO080HF2M2ErCUwb9JfPiBsRe+ZAOz0Ygv/vvNaKXpDLv8fvIxPNpxAgOG36ZOWNgH26CSpAyDYKYWDSnIaaWbB2OK4Wzks07fs2QKDrPb4M7nRyJDQmvzYM5eTwC3kyT5eozy09CLWQF2MPGQ0daQ8os5QlzBMeb4K7fBau+WFN5RWpiTypG/gfHkH51sz58iYwm6YtJKyt2MwVems/pFWjk/s/hfVCiiJRE9WZ4uiNhJUlDOFMacuH4EnhHOxCc572fEmqTxsBhBwYHTmici+kNUpCgi1Fv0UQY86awd5GFRcBnTFL+bHefPiARrZf+w/Ds7j2JoJdWJAq0z7Axris6maorwazouUGLK4k5FfOAwbwhTqtYENl0ZmPtI4hOs9AaWYH42DJNEo1tn8Bt7oTPjqW//wjxf4iJMg/W3YtgEnoimH++B1FNJQKGaidinWjb2Zwh8BX2TAZsayLWH33bL0DFqZQmfZc6O7lZRJSXUyS3spgXQkLC5IaST+zKErjzpsISJfhI8OZ2mJv3wW5lE+NnzyD+l82wtxxDtGYRLCmLiXlRJH30DPImjYV69DYER5g72OAQ8ZQK4B/j3OVzYEuDXLbu6YNB/5BWreDNkOCn7+QKLU6SiQZDkJnHmP7sW/DYThVnD2kd6UoG9Bfvqddh/nUD7BHSp4rgFhDbbJt6Myph3T5Pw9HkKQenWhHsPMTDsOAuYi757cPom80K0XThJ5ivfnInEl+bpXOM7OvvPwq7sT1bp+io1a8FP3KDFidoaAqGaXLnVDuCnz+H9EzyLVZ/DmFkNLQgxl3NElZ5GnqEAU8uIz83/GAFf56gXxEyEaHl/+sDJMmSwwczMJmfYuxvqSk17Ej2wiaJNAv5LC1n0epBVy/MV7ZwXdY6NDnZT7akoEyiZM6KyCSZqUNfwoH0OhwKo6mHVHRs2ulBJfSz4i0kS0aSAnTx5wcmNPEPb3cD7HdYh5ABpN/dgcQdCzVsTYk//dFM0dKw+Jsko2TmhbeRONMBU9bn2jKy6+t/c1ckO63/XZxBBn1GcxExd/8m2Rt8F9LHLO4ywvi/fhX+7YdhEPfha1vZx6BjM1y7z76LDE/dWTaXSvIap4mQBpUIOi4gfHEj8t45AIwi32L/eagK/CpXcq1HstOG/y6lkUTJSLAg/IvOrFjHmKwqhYeFvM56ElFLH9ILx8NePIORj73hDNtNR9il2bADsVYm1mLCjKRSl9dX2D5naF1hjWtcYvuUucfiQUqn3ygkNOS4tf/Qlxg45DcSsyqJ5F5Gwq2NCNmhkVziCnVnFycqFUsInAaDaei2110RipltXmisUJRB0LuYz+SekEH5mYI/VzDlXZJZrNf/I5FOHYK7K4zrrojsOXCOFwW/giD6nvjYgN9d7ZkrXJdLuSfEqyz0RV/+SpEv2gL/u/+XxiL/BXU7ACIRR4XYAAAAAElFTkSuQmCC', + __typename: 'workspaces', + }, + __typename: 'workspace_members', + }, + __typename: 'users', + }, + ], + }, + }, + }, + { + request: { + query: GET_PEOPLE, + variables: { orderBy: [{ created_at: 'desc' }], where: {} }, + }, + result: { + data: { + people: [], + }, + }, + }, +]; + +export default component; + +export const RegularApp = () => { + return ( + + + + + + ); +}; diff --git a/front/src/__tests__/App.test.tsx b/front/src/__tests__/App.test.tsx new file mode 100644 index 000000000..d88a1017c --- /dev/null +++ b/front/src/__tests__/App.test.tsx @@ -0,0 +1,17 @@ +import { render, waitFor } from '@testing-library/react'; +import { RegularApp } from '../__stories__/App.stories'; + +const assignMock = jest.fn(); + +delete window.location; +window.location = { assign: assignMock }; + +it('Checks the App component renders', async () => { + const { getByText } = render(); + + expect(getByText('Companies')).toBeDefined(); + expect(getByText('Opportunities')).toBeDefined(); + await waitFor(() => { + expect(getByText('Twenty')).toBeDefined(); + }); +}); diff --git a/front/src/components/chips/PipeChip.tsx b/front/src/components/chips/PipeChip.tsx index 50c2fafed..758c363d6 100644 --- a/front/src/components/chips/PipeChip.tsx +++ b/front/src/components/chips/PipeChip.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import styled from '@emotion/styled'; +import { Opportunity } from '../../interfaces/company.interface'; type OwnProps = { - name: string; - picture?: string; + opportunity: Opportunity; }; const StyledContainer = styled.span` @@ -20,11 +20,11 @@ const StyledContainer = styled.span` } `; -function PipeChip({ name, picture }: OwnProps) { +function PipeChip({ opportunity }: OwnProps) { return ( - - {picture && {picture}} - {name} + + {opportunity.icon && {opportunity.icon}} + {opportunity.name} ); } diff --git a/front/src/components/table/Table.tsx b/front/src/components/table/Table.tsx index 4fde47658..e2164b0fc 100644 --- a/front/src/components/table/Table.tsx +++ b/front/src/components/table/Table.tsx @@ -10,18 +10,30 @@ import TableHeader from './table-header/TableHeader'; import styled from '@emotion/styled'; import { FilterType, + SelectedFilterType, SelectedSortType, SortType, } from './table-header/interface'; -type OwnProps = { +type OwnProps = { data: Array; columns: Array>; viewName: string; viewIcon?: React.ReactNode; - onSortsUpdate?: (sorts: Array>) => void; availableSorts?: Array>; - availableFilters?: FilterType[]; + availableFilters?: FilterType[]; + filterSearchResults?: { + results: { displayValue: string; value: any }[]; + loading: boolean; + }; + onSortsUpdate?: (sorts: Array>) => void; + onFiltersUpdate?: ( + sorts: Array>, + ) => void; + onFilterSearch?: ( + filter: FilterType | null, + searchValue: string, + ) => void; }; const StyledTable = styled.table` @@ -75,15 +87,18 @@ const StyledTableScrollableContainer = styled.div` flex: 1; `; -function Table({ +function Table({ data, columns, viewName, viewIcon, - onSortsUpdate, availableSorts, availableFilters, -}: OwnProps) { + filterSearchResults, + onSortsUpdate, + onFiltersUpdate, + onFilterSearch, +}: OwnProps) { const table = useReactTable({ data, columns, @@ -95,9 +110,12 @@ function Table({ diff --git a/front/src/components/table/__stories__/EditableCell.stories.tsx b/front/src/components/table/__stories__/EditableCell.stories.tsx index 9cd427caa..c9e51ca60 100644 --- a/front/src/components/table/__stories__/EditableCell.stories.tsx +++ b/front/src/components/table/__stories__/EditableCell.stories.tsx @@ -1,24 +1,31 @@ import EditableCell from '../EditableCell'; import { ThemeProvider } from '@emotion/react'; import { lightTheme } from '../../../layout/styles/themes'; +import { StoryFn } from '@storybook/react'; const component = { title: 'EditableCell', component: EditableCell, }; -export default component; - type OwnProps = { - changeHandler: () => void; + content: string; + changeHandler: (updated: string) => void; }; -export const RegularEditableCell = ({ changeHandler }: OwnProps) => { +export default component; + +const Template: StoryFn = (args: OwnProps) => { return (
- , +
); }; + +export const EditableCellStory = Template.bind({}); +EditableCellStory.args = { + content: 'Test string', +}; diff --git a/front/src/components/table/__tests__/EditableCell.test.tsx b/front/src/components/table/__tests__/EditableCell.test.tsx index fa9d1c2f4..eb48568f1 100644 --- a/front/src/components/table/__tests__/EditableCell.test.tsx +++ b/front/src/components/table/__tests__/EditableCell.test.tsx @@ -1,10 +1,12 @@ import { fireEvent, render } from '@testing-library/react'; -import { RegularEditableCell } from '../__stories__/EditableCell.stories'; +import { EditableCellStory } from '../__stories__/EditableCell.stories'; it('Checks the EditableCell editing event bubbles up', async () => { const func = jest.fn(() => null); - const { getByTestId } = render(); + const { getByTestId } = render( + , + ); const parent = getByTestId('content-editable-parent'); const editableInput = parent.querySelector('input'); diff --git a/front/src/components/table/table-header/FilterDropdownButton.tsx b/front/src/components/table/table-header/FilterDropdownButton.tsx index cb0b50b46..5fe6480a0 100644 --- a/front/src/components/table/table-header/FilterDropdownButton.tsx +++ b/front/src/components/table/table-header/FilterDropdownButton.tsx @@ -1,47 +1,40 @@ -import { useCallback, useState } from 'react'; +import { ChangeEvent, useCallback, useState } from 'react'; import DropdownButton from './DropdownButton'; -import { FilterType, SelectedFilterType } from './interface'; +import { FilterOperandType, FilterType, SelectedFilterType } from './interface'; -type OwnProps = { - filters: SelectedFilterType[]; - setFilters: (sorts: SelectedFilterType[]) => void; - availableFilters: FilterType[]; +type OwnProps = { + isFilterSelected: boolean; + availableFilters: FilterType[]; + filterSearchResults?: { + results: { displayValue: string; value: any }[]; + loading: boolean; + }; + onFilterSelect: (filter: SelectedFilterType) => void; + onFilterSearch: ( + filter: FilterType | null, + searchValue: string, + ) => void; }; -type FilterOperandType = { label: string; id: string }; - const filterOperands: FilterOperandType[] = [ - { label: 'Include', id: 'include' }, - { label: "Doesn't include", id: 'not-include' }, + { label: 'Include', id: 'include', keyWord: 'ilike' }, + { label: "Doesn't include", id: 'not-include', keyWord: 'not_ilike' }, ]; -const someFieldRandomValue = [ - 'John Doe', - 'Jane Doe', - 'John Smith', - 'Jane Smith', - 'John Johnson', - 'Jane Johnson', - 'John Williams', - 'Jane Williams', - 'John Brown', - 'Jane Brown', - 'John Jones', - 'Jane Jones', -]; - -export function FilterDropdownButton({ +export function FilterDropdownButton({ availableFilters, - setFilters, - filters, -}: OwnProps) { + filterSearchResults, + onFilterSearch, + onFilterSelect, + isFilterSelected, +}: OwnProps) { const [isUnfolded, setIsUnfolded] = useState(false); const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); - const [selectedFilter, setSelectedFilter] = useState( - undefined, - ); + const [selectedFilter, setSelectedFilter] = useState< + FilterType | undefined + >(undefined); const [selectedFilterOperand, setSelectedFilterOperand] = useState(filterOperands[0]); @@ -50,7 +43,8 @@ export function FilterDropdownButton({ setIsOptionUnfolded(false); setSelectedFilter(undefined); setSelectedFilterOperand(filterOperands[0]); - }, []); + onFilterSearch(null, ''); + }, [onFilterSearch]); const renderSelectOptionItems = filterOperands.map((filterOperand, index) => ( )); + const renderSearchResults = ( + filterSearchResults: NonNullable< + OwnProps['filterSearchResults'] + >, + selectedFilter: FilterType, + ) => { + if (filterSearchResults.loading) { + return 'LOADING'; + } + return filterSearchResults.results.map((value, index) => ( + { + onFilterSelect({ + key: value.displayValue, + operand: selectedFilterOperand, + searchQuery: selectedFilter.searchQuery, + searchTemplate: selectedFilter.searchTemplate, + whereTemplate: selectedFilter.whereTemplate, + label: selectedFilter.label, + value: value.displayValue, + icon: selectedFilter.icon, + where: selectedFilter.whereTemplate( + selectedFilterOperand, + value.value, + ), + searchResultMapper: selectedFilter.searchResultMapper, + }); + setIsUnfolded(false); + setSelectedFilter(undefined); + }} + > + {value.displayValue} + + )); + }; + const renderSelectFilterITems = availableFilters.map((filter, index) => ( { setSelectedFilter(filter); + onFilterSearch(filter, ''); }} > {filter.icon} @@ -76,46 +108,36 @@ export function FilterDropdownButton({ )); - function renderFilterDropdown(selectedFilter: FilterType) { - return [ - setIsOptionUnfolded(true)} - > - {selectedFilterOperand.label} - - - , - - - , - someFieldRandomValue.map((value, index) => ( - { - setFilters([ - { - id: value, - operand: selectedFilterOperand, - label: selectedFilter.label, - value: value, - icon: selectedFilter.icon, - }, - ]); - setIsUnfolded(false); - setSelectedFilter(undefined); - }} + function renderFilterDropdown(selectedFilter: FilterType) { + return ( + <> + setIsOptionUnfolded(true)} > - {value} - - )), - ]; + {selectedFilterOperand.label} + + + + + ) => + onFilterSearch(selectedFilter, event.target.value) + } + /> + + {filterSearchResults && + renderSearchResults(filterSearchResults, selectedFilter)} + + ); } return ( 0} + isActive={isFilterSelected} isUnfolded={isUnfolded} setIsUnfolded={setIsUnfolded} resetState={resetState} diff --git a/front/src/components/table/table-header/SortAndFilterBar.tsx b/front/src/components/table/table-header/SortAndFilterBar.tsx index 5fd5ea34a..447c19b33 100644 --- a/front/src/components/table/table-header/SortAndFilterBar.tsx +++ b/front/src/components/table/table-header/SortAndFilterBar.tsx @@ -3,11 +3,13 @@ import SortOrFilterChip from './SortOrFilterChip'; import { FaArrowDown, FaArrowUp } from 'react-icons/fa'; import { SelectedFilterType, SelectedSortType } from './interface'; -type OwnProps = { +type OwnProps = { sorts: Array>; onRemoveSort: (sortId: SelectedSortType['key']) => void; - filters: Array; - onRemoveFilter: (filterId: SelectedFilterType['id']) => void; + filters: Array>; + onRemoveFilter: ( + filterId: SelectedFilterType['key'], + ) => void; }; const StyledBar = styled.div` @@ -39,12 +41,12 @@ const StyledCancelButton = styled.button` } `; -function SortAndFilterBar({ +function SortAndFilterBar({ sorts, onRemoveSort, filters, onRemoveFilter, -}: OwnProps) { +}: OwnProps) { return ( {sorts.map((sort) => { @@ -61,12 +63,12 @@ function SortAndFilterBar({ {filters.map((filter) => { return ( onRemoveFilter(filter.id)} + onRemove={() => onRemoveFilter(filter.key)} /> ); })} @@ -75,7 +77,7 @@ function SortAndFilterBar({ data-testid={'cancel-button'} onClick={() => { sorts.forEach((i) => onRemoveSort(i.key)); - filters.forEach((i) => onRemoveFilter(i.id)); + filters.forEach((i) => onRemoveFilter(i.key)); }} > Cancel diff --git a/front/src/components/table/table-header/SortDropdownButton.tsx b/front/src/components/table/table-header/SortDropdownButton.tsx index 70a50a6f8..8e67cf9b0 100644 --- a/front/src/components/table/table-header/SortDropdownButton.tsx +++ b/front/src/components/table/table-header/SortDropdownButton.tsx @@ -3,17 +3,17 @@ import DropdownButton from './DropdownButton'; import { SelectedSortType, SortType } from './interface'; type OwnProps = { - sorts: SelectedSortType[]; - setSorts: (sorts: SelectedSortType[]) => void; + isSortSelected: boolean; + onSortSelect: (sort: SelectedSortType) => void; availableSorts: SortType[]; }; const options: Array['order']> = ['asc', 'desc']; export function SortDropdownButton({ + isSortSelected, availableSorts, - setSorts, - sorts, + onSortSelect, }: OwnProps) { const [isUnfolded, setIsUnfolded] = useState(false); @@ -24,10 +24,9 @@ export function SortDropdownButton({ const onSortItemSelect = useCallback( (sort: SortType) => { - const newSorts = [{ ...sort, order: selectedSortDirection }]; - setSorts(newSorts); + onSortSelect({ ...sort, order: selectedSortDirection }); }, - [setSorts, selectedSortDirection], + [onSortSelect, selectedSortDirection], ); const resetState = useCallback(() => { @@ -38,7 +37,7 @@ export function SortDropdownButton({ return ( 0} + isActive={isSortSelected} isUnfolded={isUnfolded} setIsUnfolded={setIsUnfolded} resetState={resetState} diff --git a/front/src/components/table/table-header/TableHeader.tsx b/front/src/components/table/table-header/TableHeader.tsx index d4e340b52..5181dfcf2 100644 --- a/front/src/components/table/table-header/TableHeader.tsx +++ b/front/src/components/table/table-header/TableHeader.tsx @@ -11,13 +11,23 @@ import { SortDropdownButton } from './SortDropdownButton'; import { FilterDropdownButton } from './FilterDropdownButton'; import SortAndFilterBar from './SortAndFilterBar'; -type OwnProps = { +type OwnProps = { viewName: string; viewIcon?: ReactNode; - onSortsUpdate?: (sorts: Array>) => void; - onFiltersUpdate?: (sorts: Array) => void; availableSorts?: Array>; - availableFilters?: FilterType[]; + availableFilters?: FilterType[]; + filterSearchResults?: { + results: { displayValue: string; value: any }[]; + loading: boolean; + }; + onSortsUpdate?: (sorts: Array>) => void; + onFiltersUpdate?: ( + sorts: Array>, + ) => void; + onFilterSearch?: ( + filter: FilterType | null, + searchValue: string, + ) => void; }; const StyledContainer = styled.div` @@ -56,27 +66,32 @@ const StyledFilters = styled.div` margin-right: ${(props) => props.theme.spacing(2)}; `; -function TableHeader({ +function TableHeader({ viewName, viewIcon, - onSortsUpdate, - onFiltersUpdate, availableSorts, availableFilters, -}: OwnProps) { + filterSearchResults, + onSortsUpdate, + onFiltersUpdate, + onFilterSearch, +}: OwnProps) { const [sorts, innerSetSorts] = useState>>( [], ); + const [filters, innerSetFilters] = useState< + Array> + >([]); - const setSorts = useCallback( - (sorts: SelectedSortType[]) => { - innerSetSorts(sorts); - onSortsUpdate && onSortsUpdate(sorts); + const sortSelect = useCallback( + (sort: SelectedSortType) => { + innerSetSorts([sort]); + onSortsUpdate && onSortsUpdate([sort]); }, [onSortsUpdate], ); - const onSortItemUnSelect = useCallback( + const sortUnselect = useCallback( (sortId: string) => { const newSorts = [] as SelectedSortType[]; innerSetSorts(newSorts); @@ -85,25 +100,30 @@ function TableHeader({ [onSortsUpdate], ); - const [filters, innerSetFilters] = useState>([]); - - const setFilters = useCallback( - (filters: SelectedFilterType[]) => { - innerSetFilters(filters); - onFiltersUpdate && onFiltersUpdate(filters); + const filterSelect = useCallback( + (filter: SelectedFilterType) => { + innerSetFilters([filter]); + onFiltersUpdate && onFiltersUpdate([filter]); }, [onFiltersUpdate], ); - const onFilterItemUnSelect = useCallback( - (filterId: SelectedFilterType['id']) => { - const newFilters = [] as SelectedFilterType[]; + const filterUnselect = useCallback( + (filterId: SelectedFilterType['key']) => { + const newFilters = [] as SelectedFilterType[]; innerSetFilters(newFilters); onFiltersUpdate && onFiltersUpdate(newFilters); }, [onFiltersUpdate], ); + const filterSearch = useCallback( + (filter: FilterType | null, searchValue: string) => { + onFilterSearch && onFilterSearch(filter, searchValue); + }, + [onFilterSearch], + ); + return ( @@ -113,14 +133,16 @@ function TableHeader({ 0} availableFilters={availableFilters || []} + filterSearchResults={filterSearchResults} + onFilterSelect={filterSelect} + onFilterSearch={filterSearch} /> - + isSortSelected={sorts.length > 0} availableSorts={availableSorts || []} + onSortSelect={sortSelect} /> @@ -129,9 +151,9 @@ function TableHeader({ {sorts.length + filters.length > 0 && ( )} diff --git a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx index f04c49204..b2a1cb026 100644 --- a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx +++ b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx @@ -3,15 +3,15 @@ import { lightTheme } from '../../../../layout/styles/themes'; import { FilterDropdownButton } from '../FilterDropdownButton'; import styled from '@emotion/styled'; import { FilterType, SelectedFilterType } from '../interface'; -import { - FaRegUser, - FaRegBuilding, - FaEnvelope, - FaPhone, - FaCalendar, - FaMapPin, -} from 'react-icons/fa'; import { useCallback, useState } from 'react'; +import { People_Bool_Exp } from '../../../../generated/graphql'; +import { FaUsers } from 'react-icons/fa'; +import { + SEARCH_PEOPLE_QUERY, + useSearch, +} from '../../../../services/search/search'; +import { MockedProvider } from '@apollo/client/testing'; +import { defaultData } from '../../../../pages/people/default-data'; const component = { title: 'FilterDropdownButton', @@ -20,58 +20,129 @@ const component = { export default component; -type OwnProps = { - setFilters: (filters: SelectedFilterType[]) => void; +type OwnProps = { + setFilter: (filters: SelectedFilterType) => void; }; +const mocks = [ + { + request: { + query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters + variables: { + where: undefined, + }, + }, + result: { + data: { + searchResults: defaultData, + }, + }, + }, + { + request: { + query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters + variables: { + where: { + _or: [ + { firstname: { _ilike: '%%' } }, + { lastname: { _ilike: '%%' } }, + ], + }, + }, + }, + result: { + data: { + searchResults: defaultData, + }, + }, + }, + { + request: { + query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters + variables: { + where: { + _or: [ + { firstname: { _ilike: '%Jane%' } }, + { lastname: { _ilike: '%Jane%' } }, + ], + }, + }, + }, + result: { + data: { + searchResults: [defaultData.find((p) => p.firstname === 'Jane')], + }, + }, + }, +]; + const availableFilters = [ { key: 'fullname', label: 'People', - icon: , + icon: , + searchQuery: SEARCH_PEOPLE_QUERY, + searchTemplate: (searchInput: string) => ({ + _or: [ + { firstname: { _ilike: `%${searchInput}%` } }, + { lastname: { _ilike: `%${searchInput}%` } }, + ], + }), + whereTemplate: () => ({ + _or: [ + { firstname: { _ilike: 'value' } }, + { lastname: { _ilike: 'value' } }, + ], + }), + searchResultMapper: (data) => ({ + displayValue: data.firstname + ' ' + data.lastname, + value: data.firstname, + }), }, - { - key: 'company_name', - label: 'Company', - icon: , - }, - { - key: 'email', - label: 'Email', - icon: , - }, - { key: 'phone', label: 'Phone', icon: }, - { - key: 'created_at', - label: 'Created at', - icon: , - }, - { key: 'city', label: 'City', icon: }, -] satisfies FilterType[]; +] satisfies FilterType[]; const StyleDiv = styled.div` height: 200px; width: 200px; `; -export const RegularFilterDropdownButton = ({ setFilters }: OwnProps) => { - const [filters, innerSetFilters] = useState([]); +const InnerRegularFilterDropdownButton = ({ + setFilter: setFilters, +}: OwnProps) => { + const [, innerSetFilters] = useState>(); + const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch(); + const outerSetFilters = useCallback( - (filters: SelectedFilterType[]) => { - innerSetFilters(filters); - setFilters(filters); + (filter: SelectedFilterType) => { + innerSetFilters(filter); + setFilters(filter); }, [setFilters], ); return ( - - - - - + + { + setSearhInput(searchValue); + setFilterSearch(filter); + }} + /> + + ); +}; + +export const RegularFilterDropdownButton = ({ + setFilter: setFilters, +}: OwnProps) => { + return ( + + + + + ); }; diff --git a/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx b/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx index 694224e7c..df605d4b1 100644 --- a/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx +++ b/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx @@ -1,6 +1,7 @@ import SortAndFilterBar from '../SortAndFilterBar'; import { ThemeProvider } from '@emotion/react'; import { lightTheme } from '../../../../layout/styles/themes'; +import { GET_PEOPLE } from '../../../../services/people'; import { FaArrowDown } from 'react-icons/fa'; const component = { @@ -37,10 +38,24 @@ export const RegularSortAndFilterBar = ({ removeFunction }: OwnProps) => { filters={[ { label: 'People', - operand: { id: 'include', label: 'Include' }, - id: 'test_filter', + operand: { label: 'Include', id: 'include', keyWord: 'ilike' }, + key: 'test_filter', icon: , value: 'John Doe', + where: { + firstname: { _ilike: 'John Doe' }, + }, + searchQuery: GET_PEOPLE, + searchTemplate: () => ({ + firstname: { _ilike: 'John Doe' }, + }), + whereTemplate: () => { + return { firstname: { _ilike: 'John Doe' } }; + }, + searchResultMapper: (data) => ({ + displayValue: 'John Doe', + value: data.firstname, + }), }, ]} /> diff --git a/front/src/components/table/table-header/__stories__/SortDropdownButton.stories.tsx b/front/src/components/table/table-header/__stories__/SortDropdownButton.stories.tsx index 512d011a7..b7d9a0ced 100644 --- a/front/src/components/table/table-header/__stories__/SortDropdownButton.stories.tsx +++ b/front/src/components/table/table-header/__stories__/SortDropdownButton.stories.tsx @@ -1,4 +1,4 @@ -import { SelectedSortType, SortType } from '../interface'; +import { SortType } from '../interface'; import { ThemeProvider } from '@emotion/react'; import { lightTheme } from '../../../../layout/styles/themes'; import { @@ -23,8 +23,6 @@ type OwnProps = { setSorts: () => void; }; -const sorts = [] satisfies SelectedSortType[]; - const availableSorts = [ { key: 'fullname', @@ -60,9 +58,9 @@ export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => { diff --git a/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx b/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx index 0e5e4aa9a..a4641cedb 100644 --- a/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx +++ b/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx @@ -1,44 +1,53 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { RegularFilterDropdownButton } from '../__stories__/FilterDropdownButton.stories'; -import { FaEnvelope } from 'react-icons/fa'; +import { FaUsers } from 'react-icons/fa'; it('Checks the default top option is Include', async () => { - const setSorts = jest.fn(); + const setFilters = jest.fn(); const { getByText } = render( - , + , ); const sortDropdownButton = getByText('Filter'); fireEvent.click(sortDropdownButton); - const sortByEmail = getByText('Email'); - fireEvent.click(sortByEmail); + const filterByPeople = getByText('People'); + fireEvent.click(filterByPeople); - const filterByJohn = getByText('John Doe'); + await waitFor(() => { + const firstSearchResult = getByText('Alexandre Prot'); + expect(firstSearchResult).toBeDefined(); + }); + + const filterByJohn = getByText('Alexandre Prot'); fireEvent.click(filterByJohn); - expect(setSorts).toHaveBeenCalledWith([ - { - id: 'John Doe', - value: 'John Doe', - label: 'Email', - operand: { id: 'include', label: 'Include' }, - icon: , - }, - ]); + expect(setFilters).toHaveBeenCalledWith( + expect.objectContaining({ + key: 'Alexandre Prot', + value: 'Alexandre Prot', + label: 'People', + operand: { + id: 'include', + keyWord: 'ilike', + label: 'Include', + }, + icon: , + }), + ); }); it('Checks the selection of top option for Doesnot include', async () => { - const setSorts = jest.fn(); + const setFilters = jest.fn(); const { getByText } = render( - , + , ); const sortDropdownButton = getByText('Filter'); fireEvent.click(sortDropdownButton); - const sortByEmail = getByText('Email'); - fireEvent.click(sortByEmail); + const filterByPeople = getByText('People'); + fireEvent.click(filterByPeople); const openOperandOptions = getByText('Include'); fireEvent.click(openOperandOptions); @@ -46,19 +55,80 @@ it('Checks the selection of top option for Doesnot include', async () => { const selectOperand = getByText("Doesn't include"); fireEvent.click(selectOperand); - const filterByJohn = getByText('John Doe'); + await waitFor(() => { + const firstSearchResult = getByText('Alexandre Prot'); + expect(firstSearchResult).toBeDefined(); + }); + + const filterByJohn = getByText('Alexandre Prot'); fireEvent.click(filterByJohn); - expect(setSorts).toHaveBeenCalledWith([ - { - id: 'John Doe', - value: 'John Doe', - label: 'Email', - operand: { id: 'not-include', label: "Doesn't include" }, - icon: , - }, - ]); - + expect(setFilters).toHaveBeenCalledWith( + expect.objectContaining({ + key: 'Alexandre Prot', + value: 'Alexandre Prot', + label: 'People', + operand: { + id: 'not-include', + keyWord: 'not_ilike', + label: "Doesn't include", + }, + icon: , + }), + ); + const blueSortDropdownButton = getByText('Filter'); + await waitFor(() => { + expect(blueSortDropdownButton).toHaveAttribute('aria-selected', 'true'); + }); +}); + +it('Calls the filters when typing a new name', async () => { + const setFilters = jest.fn(); + const { getByText, getByPlaceholderText, queryByText } = render( + , + ); + + const sortDropdownButton = getByText('Filter'); + fireEvent.click(sortDropdownButton); + + const filterByPeople = getByText('People'); + fireEvent.click(filterByPeople); + + const filterSearch = getByPlaceholderText('People'); + fireEvent.click(filterSearch); + + fireEvent.change(filterSearch, { target: { value: 'Jane' } }); + + await waitFor(() => { + const loadingDiv = getByText('LOADING'); + expect(loadingDiv).toBeDefined(); + }); + + await waitFor(() => { + const firstSearchResult = getByText('Jane Doe'); + expect(firstSearchResult).toBeDefined(); + + const alexandreSearchResult = queryByText('Alexandre Prot'); + expect(alexandreSearchResult).not.toBeInTheDocument(); + }); + + const filterByJane = getByText('Jane Doe'); + + fireEvent.click(filterByJane); + + expect(setFilters).toHaveBeenCalledWith( + expect.objectContaining({ + key: 'Jane Doe', + value: 'Jane Doe', + label: 'People', + operand: { + id: 'include', + keyWord: 'ilike', + label: 'Include', + }, + icon: , + }), + ); const blueSortDropdownButton = getByText('Filter'); await waitFor(() => { expect(blueSortDropdownButton).toHaveAttribute('aria-selected', 'true'); diff --git a/front/src/components/table/table-header/__tests__/SortDropdownButton.test.tsx b/front/src/components/table/table-header/__tests__/SortDropdownButton.test.tsx index 86719a153..6633660b6 100644 --- a/front/src/components/table/table-header/__tests__/SortDropdownButton.test.tsx +++ b/front/src/components/table/table-header/__tests__/SortDropdownButton.test.tsx @@ -14,14 +14,12 @@ it('Checks the default top option is Ascending', async () => { const sortByEmail = getByText('Email'); fireEvent.click(sortByEmail); - expect(setSorts).toHaveBeenCalledWith([ - { - label: 'Email', - key: 'email', - icon: , - order: 'asc', - }, - ]); + expect(setSorts).toHaveBeenCalledWith({ + label: 'Email', + key: 'email', + icon: , + order: 'asc', + }); }); it('Checks the selection of Descending', async () => { @@ -42,12 +40,10 @@ it('Checks the selection of Descending', async () => { const sortByEmail = getByText('Email'); fireEvent.click(sortByEmail); - expect(setSorts).toHaveBeenCalledWith([ - { - label: 'Email', - key: 'email', - icon: , - order: 'desc', - }, - ]); + expect(setSorts).toHaveBeenCalledWith({ + label: 'Email', + key: 'email', + icon: , + order: 'desc', + }); }); diff --git a/front/src/components/table/table-header/interface.ts b/front/src/components/table/table-header/interface.ts index 485e79606..b274843f1 100644 --- a/front/src/components/table/table-header/interface.ts +++ b/front/src/components/table/table-header/interface.ts @@ -1,4 +1,10 @@ +import { DocumentNode } from 'graphql'; import { ReactNode } from 'react'; +import { + Companies_Bool_Exp, + People_Bool_Exp, + Users_Bool_Exp, +} from '../../../generated/graphql'; export type SortType = { label: string; @@ -6,20 +12,30 @@ export type SortType = { icon?: ReactNode; }; -export type FilterType = { - label: string; - key: FilterKey; - icon: ReactNode; -}; - -export type SelectedFilterType = { - id: string; - label: string; - value: string; - operand: { id: string; label: string }; - icon: ReactNode; -}; - export type SelectedSortType = SortType & { order: 'asc' | 'desc'; }; + +export type FilterType> = { + label: string; + key: string; + icon: ReactNode; + whereTemplate: (operand: FilterOperandType, value: T) => WhereTemplate; + searchQuery: DocumentNode; + searchTemplate: ( + searchInput: string, + ) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp; + searchResultMapper: (data: any) => { displayValue: string; value: T }; +}; + +export type FilterOperandType = { + label: string; + id: string; + keyWord: 'ilike' | 'not_ilike'; +}; + +export type SelectedFilterType = FilterType & { + value: string; + operand: FilterOperandType; + where: WhereTemplate; +}; diff --git a/front/src/interfaces/company.interface.test.ts b/front/src/interfaces/company.interface.test.ts index 489589eb0..23d7c0c64 100644 --- a/front/src/interfaces/company.interface.test.ts +++ b/front/src/interfaces/company.interface.test.ts @@ -13,6 +13,7 @@ describe('mapCompany', () => { id: '7af20dea-0412-4c4c-8b13-d6f0e6e09e87', email: 'john@example.com', displayName: 'John Doe', + __typename: 'User', }, employees: 10, address: '1 Infinite Loop, 95014 Cupertino, California, USA', diff --git a/front/src/interfaces/company.interface.ts b/front/src/interfaces/company.interface.ts index c68545105..2952448eb 100644 --- a/front/src/interfaces/company.interface.ts +++ b/front/src/interfaces/company.interface.ts @@ -1,6 +1,7 @@ -import { User } from './user.interface'; +import { GraphqlQueryUser, User } from './user.interface'; export interface Opportunity { + id: string; name: string; icon: string; } @@ -16,17 +17,11 @@ export interface Company { creationDate: Date; } -export type GraphqlQueryAccountOwner = { - id: string; - email: string; - displayName: string; -}; - export type GraphqlQueryCompany = { id: string; name: string; domain_name: string; - account_owner?: GraphqlQueryAccountOwner; + account_owner?: GraphqlQueryUser; employees: number; address: string; created_at: string; @@ -54,7 +49,7 @@ export const mapCompany = (company: GraphqlQueryCompany): Company => ({ } : undefined, creationDate: new Date(company.created_at), - opportunities: [{ name: 'Sales Pipeline', icon: '' }], + opportunities: [], }); export const mapGqlCompany = (company: Company): GraphqlMutationCompany => ({ diff --git a/front/src/interfaces/user.interface.test.ts b/front/src/interfaces/user.interface.test.ts new file mode 100644 index 000000000..fa7c2cd03 --- /dev/null +++ b/front/src/interfaces/user.interface.test.ts @@ -0,0 +1,36 @@ +import { mapUser } from './user.interface'; + +describe('mapUser', () => { + it('should map person', () => { + const graphQLUser = { + id: '16506ba8-196c-4c13-a4a7-a22cb5eccfa1', + email: 'charles@twenty.com', + displayName: 'Charles Bochet', + workspace_member: { + workspace: { + id: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419', + domain_name: 'twenty.com', + display_name: 'Twenty', + logo: 'data:image/png;base64,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', + __typename: 'workspaces', + }, + __typename: 'workspace_members', + }, + __typename: 'users', + }; + const user = mapUser(graphQLUser); + expect(user).toStrictEqual({ + id: graphQLUser.id, + email: graphQLUser.email, + displayName: graphQLUser.displayName, + workspace_member: { + workspace: { + id: graphQLUser?.workspace_member?.workspace.id, + displayName: graphQLUser?.workspace_member?.workspace.display_name, + domainName: graphQLUser?.workspace_member?.workspace.domain_name, + logo: graphQLUser?.workspace_member?.workspace.logo, + }, + }, + }); + }); +}); diff --git a/front/src/interfaces/user.interface.ts b/front/src/interfaces/user.interface.ts index 715c9c62d..e64498cca 100644 --- a/front/src/interfaces/user.interface.ts +++ b/front/src/interfaces/user.interface.ts @@ -1,15 +1,39 @@ -import { GraphqlQueryAccountOwner } from './company.interface'; -import { Workspace } from './workspace.interface'; +import { + GraphqlQueryWorkspaceMember, + WorkspaceMember, +} from './workspace.interface'; + +export type GraphqlQueryUser = { + id: string; + email: string; + displayName: string; + workspace_member?: GraphqlQueryWorkspaceMember; + __typename: string; +}; export interface User { id: string; email: string; displayName: string; - workspace_member?: { - workspace: Workspace; - }; + workspace_member?: WorkspaceMember; } -export const mapUser = (user: GraphqlQueryAccountOwner): User => ({ - ...user, -}); +export const mapUser = (user: GraphqlQueryUser): User => { + const mappedUser = { + id: user.id, + email: user.email, + displayName: user.displayName, + } as User; + if (user.workspace_member) { + mappedUser['workspace_member'] = { + workspace: { + id: user.workspace_member.workspace.id, + displayName: user.workspace_member.workspace.display_name, + domainName: user.workspace_member.workspace.domain_name, + logo: user.workspace_member.workspace.logo, + }, + }; + } + + return mappedUser; +}; diff --git a/front/src/interfaces/workspace.interface.ts b/front/src/interfaces/workspace.interface.ts index 437c396c2..32f9fc041 100644 --- a/front/src/interfaces/workspace.interface.ts +++ b/front/src/interfaces/workspace.interface.ts @@ -1,5 +1,22 @@ export interface Workspace { id: string; - display_name: string; + domainName: string; + displayName: string; logo: string; } + +export interface WorkspaceMember { + workspace: Workspace; +} + +export type GraphqlQueryWorkspace = { + id: string; + display_name: string; + domain_name: string; + logo: string; + __typename: string; +}; +export type GraphqlQueryWorkspaceMember = { + workspace: GraphqlQueryWorkspace; + __typename: string; +}; diff --git a/front/src/layout/navbar/WorkspaceContainer.tsx b/front/src/layout/navbar/WorkspaceContainer.tsx index 30936d9b4..9b7f870b3 100644 --- a/front/src/layout/navbar/WorkspaceContainer.tsx +++ b/front/src/layout/navbar/WorkspaceContainer.tsx @@ -43,7 +43,7 @@ function WorkspaceContainer({ workspace }: OwnProps) { return ( - {workspace?.display_name} + {workspace?.displayName} ); } diff --git a/front/src/layout/navbar/__stories__/Navbar.stories.tsx b/front/src/layout/navbar/__stories__/Navbar.stories.tsx index c44af3cb3..305a04ca7 100644 --- a/front/src/layout/navbar/__stories__/Navbar.stories.tsx +++ b/front/src/layout/navbar/__stories__/Navbar.stories.tsx @@ -23,7 +23,8 @@ export const NavbarOnCompanies = () => { workspace_member: { workspace: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', - display_name: 'Claap', + displayName: 'Claap', + domainName: 'claap.com', logo: 'data:image/png;base64,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', }, }, diff --git a/front/src/pages/companies/__stories__/mock-data.ts b/front/src/pages/companies/__stories__/mock-data.ts index a0aa9a36b..9ada5e568 100644 --- a/front/src/pages/companies/__stories__/mock-data.ts +++ b/front/src/pages/companies/__stories__/mock-data.ts @@ -9,6 +9,7 @@ export const defaultData: Array = [ id: '91510aa5-ede6-451f-8029-a7fa69e4bad6', email: 'john@example.com', displayName: 'John Doe', + __typename: 'User', }, employees: 10, address: '1 Infinity Loop, 95014 Cupertino, California', diff --git a/front/src/pages/companies/companies-table.tsx b/front/src/pages/companies/companies-table.tsx index 0650ef111..c5d2d4181 100644 --- a/front/src/pages/companies/companies-table.tsx +++ b/front/src/pages/companies/companies-table.tsx @@ -96,7 +96,7 @@ export const companiesColumns = [ cell: (props) => ( {props.row.original.opportunities.map((opportunity) => ( - + ))} ), diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx index 1e55b5015..190b06f84 100644 --- a/front/src/pages/people/People.tsx +++ b/front/src/pages/people/People.tsx @@ -12,9 +12,13 @@ import { useCallback, useState } from 'react'; import { PeopleSelectedSortType, defaultOrderBy, + reduceFiltersToWhere, reduceSortsToOrderBy, usePeopleQuery, } from '../../services/people'; +import { useSearch } from '../../services/search/search'; +import { People_Bool_Exp } from '../../generated/graphql'; +import { SelectedFilterType } from '../../components/table/table-header/interface'; const StyledPeopleContainer = styled.div` display: flex; @@ -23,15 +27,22 @@ const StyledPeopleContainer = styled.div` `; function People() { - const [, setSorts] = useState([] as Array); const [orderBy, setOrderBy] = useState(defaultOrderBy); + const [where, setWhere] = useState({}); + const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch(); const updateSorts = useCallback((sorts: Array) => { - setSorts(sorts); setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy); }, []); - const { data } = usePeopleQuery(orderBy); + const updateFilters = useCallback( + (filters: Array>) => { + setWhere(reduceFiltersToWhere(filters)); + }, + [], + ); + + const { data } = usePeopleQuery(orderBy, where); return ( }> @@ -42,9 +53,15 @@ function People() { columns={peopleColumns} viewName="All People" viewIcon={} - onSortsUpdate={updateSorts} availableSorts={availableSorts} availableFilters={availableFilters} + filterSearchResults={filterSearchResults} + onSortsUpdate={updateSorts} + onFiltersUpdate={updateFilters} + onFilterSearch={(filter, searchValue) => { + setSearhInput(searchValue); + setFilterSearch(filter); + }} /> } diff --git a/front/src/pages/people/__stories__/People.stories.tsx b/front/src/pages/people/__stories__/People.stories.tsx index 9f9cf8a35..f33525e3c 100644 --- a/front/src/pages/people/__stories__/People.stories.tsx +++ b/front/src/pages/people/__stories__/People.stories.tsx @@ -5,6 +5,7 @@ import { lightTheme } from '../../../layout/styles/themes'; import { MockedProvider } from '@apollo/client/testing'; import { defaultData } from '../default-data'; import { GET_PEOPLE } from '../../../services/people'; +import { SEARCH_PEOPLE_QUERY } from '../../../services/search/search'; const component = { title: 'People', @@ -19,6 +20,7 @@ const mocks = [ query: GET_PEOPLE, variables: { orderBy: [{ created_at: 'desc' }], + where: {}, }, }, result: { @@ -27,6 +29,19 @@ const mocks = [ }, }, }, + { + request: { + query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters + variables: { + where: undefined, + }, + }, + result: { + data: { + people: [], + }, + }, + }, ]; export const PeopleDefault = () => ( diff --git a/front/src/pages/people/default-data.ts b/front/src/pages/people/default-data.ts index f0e608ce7..de446cae6 100644 --- a/front/src/pages/people/default-data.ts +++ b/front/src/pages/people/default-data.ts @@ -21,9 +21,9 @@ export const defaultData: Array = [ { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', __typename: 'Person', - firstname: 'Alexandre', - lastname: 'Prot', - email: 'alexandre@qonto.com', + firstname: 'John', + lastname: 'Doe', + email: 'john@linkedin.com', company: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6e', name: 'LinkedIn', @@ -38,9 +38,9 @@ export const defaultData: Array = [ { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6f', __typename: 'Person', - firstname: 'Alexandre', - lastname: 'Prot', - email: 'alexandre@qonto.com', + firstname: 'Jane', + lastname: 'Doe', + email: 'jane@sequoiacap.com', company: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6g', name: 'Sequoia', @@ -56,9 +56,9 @@ export const defaultData: Array = [ { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6h', __typename: 'Person', - firstname: 'Alexandre', - lastname: 'Prot', - email: 'alexandre@qonto.com', + firstname: 'Janice', + lastname: 'Dane', + email: 'janice@facebook.com', company: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6i', name: 'Facebook', diff --git a/front/src/pages/people/people-table.tsx b/front/src/pages/people/people-table.tsx index 727e8bc57..767e0c8f1 100644 --- a/front/src/pages/people/people-table.tsx +++ b/front/src/pages/people/people-table.tsx @@ -6,6 +6,8 @@ import { FaMapPin, FaPhone, FaStream, + FaUser, + FaBuilding, } from 'react-icons/fa'; import { createColumnHelper } from '@tanstack/react-table'; import ClickableCell from '../../components/table/ClickableCell'; @@ -15,7 +17,7 @@ import Checkbox from '../../components/form/Checkbox'; import HorizontalyAlignedContainer from '../../layout/containers/HorizontalyAlignedContainer'; import CompanyChip from '../../components/chips/CompanyChip'; import PersonChip from '../../components/chips/PersonChip'; -import { Person } from '../../interfaces/person.interface'; +import { GraphqlQueryPerson, Person } from '../../interfaces/person.interface'; import PipeChip from '../../components/chips/PipeChip'; import EditableCell from '../../components/table/EditableCell'; import { OrderByFields, updatePerson } from '../../services/people'; @@ -23,6 +25,12 @@ import { FilterType, SortType, } from '../../components/table/table-header/interface'; +import { People_Bool_Exp } from '../../generated/graphql'; +import { + SEARCH_COMPANY_QUERY, + SEARCH_PEOPLE_QUERY, +} from '../../services/search/search'; +import { GraphqlQueryCompany } from '../../interfaces/company.interface'; export const availableSorts = [ { @@ -53,26 +61,74 @@ export const availableFilters = [ { key: 'fullname', label: 'People', - icon: , + icon: , + whereTemplate: (_operand, { firstname, lastname }) => ({ + _and: [ + { firstname: { _ilike: `${firstname}` } }, + { lastname: { _ilike: `${lastname}` } }, + ], + }), + searchQuery: SEARCH_PEOPLE_QUERY, + searchTemplate: (searchInput: string) => ({ + _or: [ + { firstname: { _ilike: `%${searchInput}%` } }, + { lastname: { _ilike: `%${searchInput}%` } }, + ], + }), + searchResultMapper: (person: GraphqlQueryPerson) => ({ + displayValue: `${person.firstname} ${person.lastname}`, + value: { firstname: person.firstname, lastname: person.lastname }, + }), }, { key: 'company_name', label: 'Company', - icon: , + icon: , + whereTemplate: (_operand, { companyName }) => ({ + company: { name: { _ilike: `%${companyName}%` } }, + }), + searchQuery: SEARCH_COMPANY_QUERY, + searchTemplate: (searchInput: string) => ({ + name: { _ilike: `%${searchInput}%` }, + }), + searchResultMapper: (company: GraphqlQueryCompany) => ({ + displayValue: company.name, + value: { companyName: company.name }, + }), }, - { - key: 'email', - label: 'Email', - icon: , - }, - { key: 'phone', label: 'Phone', icon: }, - { - key: 'created_at', - label: 'Created at', - icon: , - }, - { key: 'city', label: 'City', icon: }, -] satisfies FilterType[]; + // { + // key: 'email', + // label: 'Email', + // icon: faEnvelope, + // whereTemplate: () => ({ email: { _ilike: '%value%' } }), + // searchQuery: GET_PEOPLE, + // searchTemplate: { email: { _ilike: '%value%' } }, + // }, + // { + // key: 'phone', + // label: 'Phone', + // icon: faPhone, + // whereTemplate: () => ({ phone: { _ilike: '%value%' } }), + // searchQuery: GET_PEOPLE, + // searchTemplate: { phone: { _ilike: '%value%' } }, + // }, + // { + // key: 'created_at', + // label: 'Created at', + // icon: faCalendar, + // whereTemplate: () => ({ created_at: { _eq: '%value%' } }), + // searchQuery: GET_PEOPLE, + // searchTemplate: { created_at: { _eq: '%value%' } }, + // }, + // { + // key: 'city', + // label: 'City', + // icon: faMapPin, + // whereTemplate: () => ({ city: { _ilike: '%value%' } }), + // searchQuery: GET_PEOPLE, + // searchTemplate: { city: { _ilike: '%value%' } }, + // }, +] satisfies FilterType[]; const columnHelper = createColumnHelper(); export const peopleColumns = [ @@ -151,10 +207,7 @@ export const peopleColumns = [ header: () => } />, cell: (props) => ( - + ), }), diff --git a/front/src/services/people/select.ts b/front/src/services/people/select.ts index 06866ce08..728f84651 100644 --- a/front/src/services/people/select.ts +++ b/front/src/services/people/select.ts @@ -1,7 +1,14 @@ import { QueryResult, gql, useQuery } from '@apollo/client'; import { GraphqlQueryPerson } from '../../interfaces/person.interface'; -import { Order_By, People_Order_By } from '../../generated/graphql'; -import { SelectedSortType } from '../../components/table/table-header/interface'; +import { + Order_By, + People_Bool_Exp, + People_Order_By, +} from '../../generated/graphql'; +import { + SelectedFilterType, + SelectedSortType, +} from '../../components/table/table-header/interface'; export type OrderByFields = keyof People_Order_By | 'fullname' | 'company_name'; @@ -11,6 +18,16 @@ const mapOrder = (order: 'asc' | 'desc'): Order_By => { return order === 'asc' ? Order_By.Asc : Order_By.Desc; }; +export const reduceFiltersToWhere = ( + filters: Array>, +): T => { + const where = filters.reduce((acc, filter) => { + const { where } = filter; + return { ...acc, ...where }; + }, {} as T); + return where; +}; + export const reduceSortsToOrderBy = ( sorts: Array, ): People_Order_By[] => { @@ -31,8 +48,12 @@ export const reduceSortsToOrderBy = ( }; export const GET_PEOPLE = gql` - query GetPeople($orderBy: [people_order_by!]) { - people(order_by: $orderBy) { + query GetPeople( + $orderBy: [people_order_by!] + $where: people_bool_exp + $limit: Int + ) { + people(order_by: $orderBy, where: $where, limit: $limit) { id phone email @@ -51,9 +72,10 @@ export const GET_PEOPLE = gql` export function usePeopleQuery( orderBy: People_Order_By[], + where: People_Bool_Exp, ): QueryResult<{ people: GraphqlQueryPerson[] }> { return useQuery<{ people: GraphqlQueryPerson[] }>(GET_PEOPLE, { - variables: { orderBy }, + variables: { orderBy, where }, }); } diff --git a/front/src/services/search/search.ts b/front/src/services/search/search.ts new file mode 100644 index 000000000..9353efbb9 --- /dev/null +++ b/front/src/services/search/search.ts @@ -0,0 +1,105 @@ +import { gql, useQuery } from '@apollo/client'; +import { People_Bool_Exp } from '../../generated/graphql'; +import {} from '../../interfaces/company.interface'; +import { useMemo, useState } from 'react'; +import { FilterType } from '../../components/table/table-header/interface'; + +export const SEARCH_PEOPLE_QUERY = gql` + query SearchQuery($where: people_bool_exp, $limit: Int) { + searchResults: people(where: $where, limit: $limit) { + id + phone + email + city + firstname + lastname + created_at + } + } +`; + +const EMPTY_QUERY = gql` + query EmptyQuery { + _ + } +`; + +export const SEARCH_COMPANY_QUERY = gql` + query SearchQuery($where: companies_bool_exp, $limit: Int) { + searchResults: companies(where: $where, limit: $limit) { + id + name + } + } +`; + +const debounce = ( + func: (...args: FuncArgs) => void, + delay: number, +) => { + let timeoutId: ReturnType; + return (...args: FuncArgs) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func(...args); + }, delay); + }; +}; + +export const useSearch = (): [ + { results: { displayValue: string; value: any }[]; loading: boolean }, + React.Dispatch>, + React.Dispatch | null>>, +] => { + const [filter, setFilter] = useState | null>( + null, + ); + const [searchInput, setSearchInput] = useState(''); + + const debouncedsetSearchInput = useMemo( + () => debounce(setSearchInput, 500), + [], + ); + + const where = useMemo(() => { + return ( + filter && filter.searchTemplate && filter.searchTemplate(searchInput) + ); + }, [filter, searchInput]); + + const searchFilterQueryResults = useQuery( + filter?.searchQuery || EMPTY_QUERY, + { + variables: { + where, + }, + skip: !filter, + }, + ); + + const searchFilterResults = useMemo<{ + results: { displayValue: string; value: any }[]; + loading: boolean; + }>(() => { + if (filter == null) { + return { + loading: false, + results: [], + }; + } + if (searchFilterQueryResults.loading) { + return { + loading: true, + results: [], + }; + } + return { + loading: false, + results: searchFilterQueryResults.data.searchResults.map( + filter.searchResultMapper, + ), + }; + }, [filter, searchFilterQueryResults]); + + return [searchFilterResults, debouncedsetSearchInput, setFilter]; +}; diff --git a/front/src/services/users/__tests_/index.test.ts b/front/src/services/users/__tests_/index.test.ts deleted file mode 100644 index 8e29157a8..000000000 --- a/front/src/services/users/__tests_/index.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -describe('Get Current user', () => { - it('should return a parsed user if api returns it', () => { - // TBD - }); - - it('should not return a user if api does not return it', () => { - // TBD - }); -}); - -export {}; diff --git a/front/src/services/users/index.tsx b/front/src/services/users/index.tsx index 2d2afb4c1..744b202ec 100644 --- a/front/src/services/users/index.tsx +++ b/front/src/services/users/index.tsx @@ -1,5 +1,5 @@ import { QueryResult, gql, useQuery } from '@apollo/client'; -import { GraphqlQueryAccountOwner } from '../../interfaces/company.interface'; +import { GraphqlQueryUser } from '../../interfaces/user.interface'; export const GET_CURRENT_USER = gql` query GetCurrentUser { @@ -20,7 +20,7 @@ export const GET_CURRENT_USER = gql` `; export function useGetCurrentUserQuery(): QueryResult<{ - users: GraphqlQueryAccountOwner[]; + users: GraphqlQueryUser[]; }> { - return useQuery<{ users: GraphqlQueryAccountOwner[] }>(GET_CURRENT_USER); + return useQuery<{ users: GraphqlQueryUser[] }>(GET_CURRENT_USER); }