Skip to content

Commit f18d55b

Browse files
authored
Feature/cls2 1300 company files tab (#7590)
* Adding files tab to company to list SharePoint types
1 parent 1a478ee commit f18d55b

File tree

21 files changed

+783
-0
lines changed

21 files changed

+783
-0
lines changed

src/apps/companies/constants.js

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const LOCAL_NAV = [
4949
permissions: ['company.view_contact'],
5050
ariaDescription: 'Company contacts',
5151
},
52+
{
53+
path: 'files',
54+
label: 'Files',
55+
ariaDescription: 'Files',
56+
},
5257
{
5358
path: 'account-management',
5459
label: 'Account management',

src/apps/routers.js

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ const reactRoutes = [
125125
'/companies/:companyId/activity',
126126
'/companies/:companyId/contacts',
127127
'/companies/:companyId/orders',
128+
'/companies/:companyId/files',
128129
'/companies/:companyId/account-management',
129130
'/companies/:companyId/investments',
130131
'/companies/:companyId/investments/projects',

src/client/actions.js

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export const CONTACTS__LOADED = 'CONTACTS__LOADED'
3131
export const CONTACTS__METADATA_LOADED = 'CONTACTS__METADATA_LOADED'
3232
export const CONTACTS__ACTIVITIES_LOADED = 'CONTACTS__ACTIVITIES_LOADED'
3333

34+
export const FILES__LOADED = 'FILES__LOADED'
35+
3436
export const ORDERS__LOADED = 'ORDERS__LOADED'
3537
export const ORDERS__METADATA_LOADED = 'ORDERS__METADATA_LOADED'
3638

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import Link from '@govuk-react/link'
4+
5+
const CollectionSummaryCardItem = ({ title, links, rows }) => (
6+
<div
7+
key={`collection-summary-card-item-${title}`}
8+
data-test="collection-item"
9+
className="govuk-summary-card govuk-!-margin-top-6"
10+
>
11+
<div className="govuk-summary-card__title-wrapper">
12+
<h2 className="govuk-summary-card__title">{title}</h2>
13+
<div className="govuk-summary-card__title-actions">
14+
{links &&
15+
links.length > 0 &&
16+
links.map((link, index) => (
17+
<span key={`link-${index}`}>
18+
<Link href={link.url} {...link.attrs}>
19+
{link.text}
20+
</Link>
21+
{index < links.length - 1 && ' | '}
22+
</span>
23+
))}
24+
</div>
25+
</div>
26+
27+
<div className="govuk-summary-card__content">
28+
<dl className="govuk-summary-list">
29+
{rows.map((row, index) => (
30+
<div
31+
data-test="metadata"
32+
className="govuk-summary-list__row"
33+
key={`meta-data-row-item-${index}`}
34+
>
35+
<dt className="govuk-summary-list__key">{row.label}</dt>
36+
<dd className="govuk-summary-list__value">{row.value}</dd>
37+
</div>
38+
))}
39+
</dl>
40+
</div>
41+
</div>
42+
)
43+
44+
CollectionSummaryCardItem.propTypes = {
45+
title: PropTypes.string.isRequired,
46+
links: PropTypes.arrayOf(
47+
PropTypes.shape({
48+
url: PropTypes.string.isRequired,
49+
text: PropTypes.string.isRequired,
50+
attrs: PropTypes.object,
51+
})
52+
),
53+
rows: PropTypes.arrayOf(
54+
PropTypes.shape({
55+
label: PropTypes.string,
56+
value: PropTypes.string,
57+
})
58+
).isRequired,
59+
}
60+
61+
export default CollectionSummaryCardItem

src/client/components/CompanyTabbedLocalNavigation/constants.js

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export const localNavItems = (companyId) => {
2828
permissions: ['company.view_contact'],
2929
ariaDescription: 'Company contacts',
3030
},
31+
// {
32+
// path: 'files',
33+
// url: urls.companies.files(companyId),
34+
// label: 'Files',
35+
// ariaDescription: 'Files',
36+
// },
3137
{
3238
path: 'account-management',
3339
url: urls.companies.accountManagement.index(companyId),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react'
2+
import { connect } from 'react-redux'
3+
import { Link, Details } from 'govuk-react'
4+
import { useParams } from 'react-router-dom'
5+
6+
import { FILES__LOADED } from '../../../actions'
7+
import { FilteredCollectionList } from '../../../components'
8+
import { listSkeletonPlaceholder } from '../../../components/SkeletonPlaceholder'
9+
import { CompanyResource } from '../../../components/Resource'
10+
import CompanyLayout from '../../../components/Layout/CompanyLayout'
11+
import DefaultLayoutBase from '../../../components/Layout/DefaultLayoutBase'
12+
13+
import { FILES_LIST_ID, TASK_GET_FILES_LIST, filesState2props } from './state'
14+
import CollectionSummaryCardItem from '../../../components/CollectionList/CollectionSummaryCardItem'
15+
16+
const CompanyFilesCollection = ({
17+
payload,
18+
optionMetadata,
19+
selectedFilters,
20+
...props
21+
}) => {
22+
const { companyId } = useParams()
23+
24+
const collectionListTask = {
25+
name: TASK_GET_FILES_LIST,
26+
id: FILES_LIST_ID,
27+
progressMessage: 'loading files',
28+
renderProgress: listSkeletonPlaceholder(),
29+
startOnRender: {
30+
payload: {
31+
...payload,
32+
relatedObjectId: companyId,
33+
},
34+
onSuccessDispatch: FILES__LOADED,
35+
},
36+
}
37+
38+
const collectionSummaryCardItemTemplateDefault = (item) => {
39+
return <CollectionSummaryCardItem {...item} key={item.id} />
40+
}
41+
42+
return (
43+
<DefaultLayoutBase>
44+
<CompanyResource id={companyId}>
45+
{(company) => (
46+
<CompanyLayout
47+
company={company}
48+
breadcrumbs={[{ text: 'Files' }]}
49+
pageTitle="Files"
50+
>
51+
{company.archived ? (
52+
<Details
53+
summary="Why can I not add a file?"
54+
data-test="archived-details"
55+
>
56+
Files cannot be added to an archived company.{' '}
57+
<Link href={`/companies/${company.id}/unarchive`}>
58+
Click here to unarchive
59+
</Link>
60+
</Details>
61+
) : null}
62+
<FilteredCollectionList
63+
{...props}
64+
collectionName="File"
65+
sortOptions={optionMetadata.sortOptions}
66+
taskProps={collectionListTask}
67+
collectionItemTemplate={collectionSummaryCardItemTemplateDefault}
68+
selectedFilters={selectedFilters}
69+
addItemUrl={
70+
company.archived ? null : `/files/create?company=${company.id}`
71+
}
72+
entityName="file"
73+
defaultQueryParams={{
74+
sortby: '-created_on',
75+
page: 1,
76+
}}
77+
/>
78+
</CompanyLayout>
79+
)}
80+
</CompanyResource>
81+
</DefaultLayoutBase>
82+
)
83+
}
84+
85+
export default connect(filesState2props)(CompanyFilesCollection)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const SORT_OPTIONS = [
2+
{ value: '-created_on', name: 'Recently created' },
3+
{ value: 'created_on', name: 'Oldest' },
4+
]
5+
6+
export const DOCUMENT_TYPES = {
7+
SHAREPOINT: 'documents.sharepointdocument',
8+
FILE_UPLOAD: 'documents.document',
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { FILES__LOADED } from '../../../actions'
2+
3+
const initialState = {
4+
results: [],
5+
isComplete: false,
6+
}
7+
8+
export default (state = initialState, { type, result }) => {
9+
switch (type) {
10+
case FILES__LOADED:
11+
return {
12+
...state,
13+
...result,
14+
isComplete: true,
15+
}
16+
default:
17+
return state
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { SORT_OPTIONS } from './constants'
2+
import { parseQueryString } from '../../../utils'
3+
4+
export const FILES_LIST_ID = 'filesList'
5+
6+
export const TASK_GET_FILES_LIST = 'TASK_GET_FILES_LIST'
7+
8+
export const filesState2props = ({ router, ...state }) => {
9+
return {
10+
...state[FILES_LIST_ID],
11+
payload: {
12+
...parseQueryString(router.location.search.slice(1)),
13+
},
14+
selectedFilters: {},
15+
optionMetadata: {
16+
sortOptions: SORT_OPTIONS,
17+
},
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { transformResponseToCollection } from './transformers'
2+
import { apiProxyAxios } from '../../../components/Task/utils'
3+
4+
const handleError = (e) => Promise.reject(Error(e.response.data.detail))
5+
6+
export const getFiles = ({ page, limit = 10, sortby, relatedObjectId }) =>
7+
apiProxyAxios
8+
.get(
9+
`v4/document/?related_object_id=${relatedObjectId}&limit=${limit}&offset=${limit * (page - 1)}&sortby=${sortby}`
10+
)
11+
.then(
12+
({ data }) => transformResponseToCollection(relatedObjectId, data),
13+
handleError
14+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
formatDate,
3+
DATE_FORMAT_MEDIUM_WITH_TIME,
4+
} from '../../../utils/date-utils'
5+
import { DOCUMENT_TYPES } from './constants'
6+
7+
export const transformFileToListItem = () => (file) => {
8+
let title = ''
9+
const links = []
10+
let summaryRows = []
11+
12+
// Function to format the summary rows
13+
const addSummaryRow = (label, value) => ({ label, value })
14+
15+
// Handle different document types
16+
switch (file.document_type) {
17+
case DOCUMENT_TYPES.SHAREPOINT:
18+
title = 'SharePoint link'
19+
if (file.document.title) {
20+
title += ` - ${file.document.title}`
21+
}
22+
23+
// Add links for SharePoint document
24+
links.push(
25+
{
26+
text: 'View file (opens in new tab)',
27+
url: file.document.url,
28+
attrs: { target: '_blank', rel: 'noopener noreferrer' },
29+
},
30+
{ text: 'Delete', url: '#' }
31+
)
32+
33+
// Add summary rows for SharePoint document
34+
summaryRows = [
35+
addSummaryRow(
36+
'Date added',
37+
formatDate(file.document.created_on, DATE_FORMAT_MEDIUM_WITH_TIME)
38+
),
39+
addSummaryRow('Added by', file?.created_by?.name),
40+
addSummaryRow('SharePoint url', file?.document?.url),
41+
]
42+
break
43+
default:
44+
break
45+
}
46+
47+
return {
48+
id: file.id,
49+
title,
50+
links,
51+
rows: summaryRows,
52+
}
53+
}
54+
55+
export const transformResponseToCollection = (
56+
file,
57+
{ count, results = [] }
58+
) => ({
59+
count,
60+
results: results.map(transformFileToListItem(file)),
61+
})

src/client/reducers.js

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ import {
9090
} from './modules/Contacts/CollectionList/state'
9191
import contactsReducer from './modules/Contacts/CollectionList/reducer'
9292

93+
import { FILES_LIST_ID } from './modules/Files/CollectionList/state'
94+
import filesReducer from './modules/Files/CollectionList/reducer'
95+
9396
import { ID as INTERACTIONS_ID } from './modules/Interactions/CollectionList/state'
9497
import interactionsReducer from './modules/Interactions/CollectionList/reducer'
9598

@@ -223,6 +226,7 @@ export const reducers = {
223226
[INVESTMENT_REMINDERS_ID]: investmentRemindersReducer,
224227
[REMINDER_SUMMARY_ID]: reminderSummaryReducer,
225228
[CONTACTS_LIST_ID]: contactsReducer,
229+
[FILES_LIST_ID]: filesReducer,
226230
[CONTACT_ACTIVITIES_ID]: contactActivitiesReducer,
227231
[COMPANY_CONTACTS_LIST_ID]: contactsReducer,
228232
[INTERACTIONS_ID]: interactionsReducer,

src/client/routes.js

+9
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import RemoveGlobalHQ from './modules/Companies/CompanyBusinessDetails/LinkGloba
9191
import CompanyActivityCollectionNoAs from './modules/Companies/CompanyActivity/index'
9292
import CompanyContactsCollection from './modules/Contacts/CollectionList/CompanyContactsCollection'
9393
import CompanyOrdersCollection from './modules/Omis/CollectionList/CompanyOrdersCollection'
94+
import CompanyFilesCollection from './modules/Files/CollectionList/CompanyFilesCollection'
9495
import AccountManagement from './modules/Companies/AccountManagement'
9596
import CompanyProjectsCollection from './modules/Companies/CompanyInvestments/CompanyProjectsCollection'
9697
import LargeCapitalProfile from './modules/Companies/CompanyInvestments/LargeCapitalProfile'
@@ -284,6 +285,14 @@ function Routes() {
284285
</ProtectedRoute>
285286
),
286287
},
288+
{
289+
path: '/companies/:companyId/files',
290+
element: (
291+
<ProtectedRoute module={'datahub:companies'}>
292+
<CompanyFilesCollection />
293+
</ProtectedRoute>
294+
),
295+
},
287296
{
288297
path: '/companies/:companyId/orders',
289298
element: (

src/client/tasks.js

+4
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ import {
152152
getContacts,
153153
getContactsMetadata,
154154
} from './modules/Contacts/CollectionList/tasks'
155+
import { getFiles } from './modules/Files/CollectionList/tasks'
155156
import {
156157
getInteractions,
157158
getInteractionsMetadata,
@@ -182,6 +183,8 @@ import {
182183
TASK_GET_CONTACTS_METADATA,
183184
} from './modules/Contacts/CollectionList/state'
184185

186+
import { TASK_GET_FILES_LIST } from './modules/Files/CollectionList/state'
187+
185188
import {
186189
TASK_GET_INTERACTIONS_LIST,
187190
TASK_GET_INTERACTIONS_ADVISER_NAME,
@@ -532,6 +535,7 @@ export const tasks = {
532535
'Large investment profiles filters':
533536
investmentProfilesTasks.loadFilterOptions,
534537
[TASK_GET_CONTACTS_LIST]: getContacts,
538+
[TASK_GET_FILES_LIST]: getFiles,
535539
[TASK_GET_CONTACTS_METADATA]: getContactsMetadata,
536540
[TASK_GET_INTERACTIONS_LIST]: getInteractions,
537541
[TASK_GET_INTERACTIONS_ADVISER_NAME]: getAdviserNames,

src/lib/urls.js

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ module.exports = {
174174
details: url('/companies', '/:companyId/details'),
175175
archive: url('/companies', '/:companyId/archive'),
176176
contacts: url('/companies', '/:companyId/contacts'),
177+
files: url('/companies', '/:companyId/files'),
177178
unarchive: url('/companies', '/:companyId/unarchive'),
178179
businessDetails: url('/companies', '/:companyId/business-details'),
179180
editOneList: url('/companies', '/:companyId/edit-one-list'),

src/middleware/api-proxy.js

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const ALLOWLIST = [
9393
'/v4/investment-lead/eyb/:eybLeadId/audit',
9494
'/v4/dnb/company-investigation',
9595
'/v4/company-activity/stova-events/:id',
96+
'/v4/document',
9697
]
9798

9899
module.exports = (app) => {

0 commit comments

Comments
 (0)