From cafc33d794960fc162c4c66e4feeafc74834d4ce Mon Sep 17 00:00:00 2001
From: dleadbetter <derek@performantsoftware.com>
Date: Mon, 9 Dec 2024 14:55:57 -0500
Subject: [PATCH] BASIRA #270 - Refactoring artwork, physical component, visual
 context, and document detail page layouts

---
 client/src/components/ArtworkCreators.css |   3 +
 client/src/components/ArtworkCreators.js  |  16 +-
 client/src/components/ArtworkTitles.css   |   4 +
 client/src/components/ArtworkTitles.js    |   1 -
 client/src/components/AttributesGrid.css  |  28 +
 client/src/components/AttributesGrid.js   |  25 +-
 client/src/components/DocumentActions.css |   3 +
 client/src/components/DocumentActions.js  |  47 +-
 client/src/components/Locations.css       |   3 +
 client/src/components/Locations.js        |  16 +-
 client/src/components/RecordPage.css      |  28 +-
 client/src/components/RecordPage.js       |  36 +-
 client/src/i18n/en.json                   |   1 +
 client/src/pages/Artwork.js               | 143 +++--
 client/src/pages/Document.js              | 688 +++++++++++-----------
 client/src/pages/PhysicalComponent.js     |  54 +-
 client/src/pages/VisualContext.js         | 104 ++--
 17 files changed, 639 insertions(+), 561 deletions(-)

diff --git a/client/src/components/ArtworkCreators.css b/client/src/components/ArtworkCreators.css
index e69de29..40fbb1b 100644
--- a/client/src/components/ArtworkCreators.css
+++ b/client/src/components/ArtworkCreators.css
@@ -0,0 +1,3 @@
+.artwork-creators.ui.items > .item > .content > .header > .ui.header.tiny {
+  font-size: 1rem;
+}
\ No newline at end of file
diff --git a/client/src/components/ArtworkCreators.js b/client/src/components/ArtworkCreators.js
index 3a1b399..0e726b0 100644
--- a/client/src/components/ArtworkCreators.js
+++ b/client/src/components/ArtworkCreators.js
@@ -1,13 +1,14 @@
 // @flow
 
 import React from 'react';
-import { Item } from 'semantic-ui-react';
+import { Header, Item } from 'semantic-ui-react';
 import _ from 'underscore';
 import type { Participation } from '../types/Participation';
 import Qualifiables from '../utils/Qualifiables';
 import CertaintyLabel from './CertaintyLabel';
 import RolesView from './RolesView';
 import SimpleLink from './SimpleLink';
+import './ArtworkCreators.css';
 
 type Props = {
   items: Array<Participation>
@@ -20,6 +21,7 @@ const ArtworkCreators = (props: Props) => {
 
   return (
     <Item.Group
+      className='artwork-creators'
       divided
       relaxed='very'
     >
@@ -27,11 +29,15 @@ const ArtworkCreators = (props: Props) => {
         <Item>
           <Item.Content>
             <Item.Header>
-              <SimpleLink
-                url={`/people/${item.person.id}`}
+              <Header
+                className='tiny'
               >
-                { item.person.display_name }
-              </SimpleLink>
+                <SimpleLink
+                  url={`/people/${item.person.id}`}
+                >
+                  { item.person.display_name }
+                </SimpleLink>
+              </Header>
             </Item.Header>
             <Item.Meta>
               { Qualifiables.getValueListValue(item.person, 'Person', 'Nationality') }
diff --git a/client/src/components/ArtworkTitles.css b/client/src/components/ArtworkTitles.css
index c661718..f8f8663 100644
--- a/client/src/components/ArtworkTitles.css
+++ b/client/src/components/ArtworkTitles.css
@@ -1,3 +1,7 @@
 .artwork-titles .icon.white {
   color: #FFFFFF;
 }
+
+.artwork-titles.list > .item > .content > .description {
+  margin-top: 0.5rem;
+}
\ No newline at end of file
diff --git a/client/src/components/ArtworkTitles.js b/client/src/components/ArtworkTitles.js
index 02d4edf..29f83b9 100644
--- a/client/src/components/ArtworkTitles.js
+++ b/client/src/components/ArtworkTitles.js
@@ -27,7 +27,6 @@ const ArtworkTitles = (props: Props) => {
           <List.Icon
             color={item.primary ? 'green' : 'white'}
             name='check circle'
-            verticalAlign='middle'
           />
           <List.Content>
             <List.Header
diff --git a/client/src/components/AttributesGrid.css b/client/src/components/AttributesGrid.css
index e69de29..e69d9eb 100644
--- a/client/src/components/AttributesGrid.css
+++ b/client/src/components/AttributesGrid.css
@@ -0,0 +1,28 @@
+.attributes-grid.ui.segment {
+  font-size: 13px;
+}
+
+.attributes-grid.ui.segment .ui[class*="vertically divided"].grid > .row {
+  padding: 0px;
+}
+
+.attributes-grid.ui.segment .ui[class*="vertically divided"].grid > .row:before {
+  width: 100%;
+  margin: 0px;
+}
+
+.attributes-grid.ui.segment .ui[class*="vertically divided"].grid > .row:first-child > .column {
+  margin-top: 1rem;
+}
+
+.attributes-grid.ui.segment .ui[class*="vertically divided"].grid > .row > .column {
+  margin: 0.5rem 0px;
+}
+
+.attributes-grid.ui.segment .ui[class*="vertically divided"].grid > .row > .column.label {
+  color: #6c757d;
+}
+
+.attributes-grid.ui.segment .ui[class*="vertically divided"].grid > .row > .column.value {
+  font-weight: 400;
+}
\ No newline at end of file
diff --git a/client/src/components/AttributesGrid.js b/client/src/components/AttributesGrid.js
index 46f2d9d..3de35ab 100644
--- a/client/src/components/AttributesGrid.js
+++ b/client/src/components/AttributesGrid.js
@@ -1,8 +1,9 @@
 // @flow
 
 import React, { useCallback, type Element } from 'react';
-import { Grid, Segment } from 'semantic-ui-react';
+import { Grid, Header, Segment } from 'semantic-ui-react';
 import _ from 'underscore';
+import './AttributesGrid.css';
 
 type Attribute = {
   name: string,
@@ -91,13 +92,12 @@ const AttributesGrid = (props: Props) => {
       >
         <Grid.Column
           className='label'
-          floated='right'
-          textAlign='right'
           width={8}
         >
           { attribute.label }
         </Grid.Column>
         <Grid.Column
+          className='value'
           width={8}
           floated='left'
           textAlign='left'
@@ -109,11 +109,24 @@ const AttributesGrid = (props: Props) => {
   }, [renderAttributeValue]);
 
   return (
-    <Segment>
+    <Segment
+      className='attributes-grid'
+    >
       <Grid
-        className='attributes-grid'
-        divided
+        divided='vertically'
       >
+        { props.title && (
+          <Grid.Row
+            className='title'
+          >
+            <Grid.Column>
+              <Header
+                content={props.title}
+                size='tiny'
+              />
+            </Grid.Column>
+          </Grid.Row>
+        )}
         { _.map(props.attributes, renderRow.bind(this)) }
       </Grid>
     </Segment>
diff --git a/client/src/components/DocumentActions.css b/client/src/components/DocumentActions.css
index e69de29..600759b 100644
--- a/client/src/components/DocumentActions.css
+++ b/client/src/components/DocumentActions.css
@@ -0,0 +1,3 @@
+.document-actions.ui.cards > .card > .content > .header > .ui.header.tiny {
+  font-size: 1rem;
+}
\ No newline at end of file
diff --git a/client/src/components/DocumentActions.js b/client/src/components/DocumentActions.js
index 2091ea1..5b2543b 100644
--- a/client/src/components/DocumentActions.js
+++ b/client/src/components/DocumentActions.js
@@ -2,12 +2,12 @@
 
 import React, { useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
-import { Item, Label } from 'semantic-ui-react';
+import { Card, Header, Label } from 'semantic-ui-react';
 import _ from 'underscore';
 import type { Action } from '../types/Action';
+import './DocumentActions.css';
 
 type Props = {
-  // $FlowIgnore - Not sure why this is throwing a Flow error
   items: Array<Action>
 };
 
@@ -50,28 +50,37 @@ const DocumentActions = (props: Props) => {
   }
 
   return (
-    <Item.Group
-      divided
+    <Card.Group
+      className='document-actions'
+      itemsPerRow={4}
     >
       { _.map(props.items, (item, index) => (
-        <Item
+        <Card
           key={index}
         >
-          <Item.Content>
-            <Item.Header>
-              { renderActionText(item) }
-            </Item.Header>
-            <Item.Description>
-              { item.notes }
-            </Item.Description>
-            <Item.Extra>
-              { renderDescriptors(item, 'Characteristic', 'blue') }
-              { renderDescriptors(item, 'Body', 'green') }
-            </Item.Extra>
-          </Item.Content>
-        </Item>
+          <Card.Content>
+            <Card.Header
+            >
+              <Header
+                content={renderActionText(item)}
+                size='tiny'
+              />
+            </Card.Header>
+            { item.notes && (
+              <Card.Description
+                content={item.notes}
+              />
+            )}
+          </Card.Content>
+          <Card.Content
+            extra
+          >
+            { renderDescriptors(item, 'Characteristic', 'blue') }
+            { renderDescriptors(item, 'Body', 'green') }
+          </Card.Content>
+        </Card>
       ))}
-    </Item.Group>
+    </Card.Group>
   );
 };
 
diff --git a/client/src/components/Locations.css b/client/src/components/Locations.css
index e69de29..0cca5d6 100644
--- a/client/src/components/Locations.css
+++ b/client/src/components/Locations.css
@@ -0,0 +1,3 @@
+.locations.ui.items > .item > .content > .header > .ui.header.tiny {
+  font-size: 1rem;
+}
\ No newline at end of file
diff --git a/client/src/components/Locations.js b/client/src/components/Locations.js
index f9612a9..8b1be2b 100644
--- a/client/src/components/Locations.js
+++ b/client/src/components/Locations.js
@@ -2,13 +2,14 @@
 
 import React from 'react';
 import { useTranslation } from 'react-i18next';
-import { Item } from 'semantic-ui-react';
+import { Header, Item } from 'semantic-ui-react';
 import _ from 'underscore';
 import CertaintyLabel from './CertaintyLabel';
 import type { Location } from '../types/Location';
 import RolesView from './RolesView';
 import Qualifiables from '../utils/Qualifiables';
 import SimpleLink from './SimpleLink';
+import './Locations.css';
 
 type Props = {
   items: Array<Location>
@@ -23,6 +24,7 @@ const Locations = (props: Props) => {
 
   return (
     <Item.Group
+      className='locations'
       divided
       relaxed='very'
     >
@@ -30,11 +32,15 @@ const Locations = (props: Props) => {
         <Item>
           <Item.Content>
             <Item.Header>
-              <SimpleLink
-                url={`/places/${item.place_id}`}
+              <Header
+                size='tiny'
               >
-                { item.place?.name }
-              </SimpleLink>
+                <SimpleLink
+                  url={`/places/${item.place_id}`}
+                >
+                  { item.place?.name }
+                </SimpleLink>
+              </Header>
             </Item.Header>
             <Item.Meta
               content={item.place?.country}
diff --git a/client/src/components/RecordPage.css b/client/src/components/RecordPage.css
index f029a53..87acb84 100644
--- a/client/src/components/RecordPage.css
+++ b/client/src/components/RecordPage.css
@@ -11,29 +11,13 @@
   margin-bottom: 2em;
 }
 
-.record-page .attributes-grid > .row:nth-child(odd) {
-  background-color: #f1f1f1;
-}
-
-.record-page .attributes-grid > .row > .column.label {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  font-weight: bold;
-}
-
-.record-page .record-page-image {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-
 .record-page .lazy-image {
-  width: fit-content;
+  width: 250px;
+  height: 250px;
 }
 
-.record-page .lazy-image img {
-  max-height: 500px;
+.record-page .lazy-image .ui.medium.image {
+  width: 100%;
+  height: 100%;
   object-fit: cover;
-}
-
+}
\ No newline at end of file
diff --git a/client/src/components/RecordPage.js b/client/src/components/RecordPage.js
index 5d550cc..a894929 100644
--- a/client/src/components/RecordPage.js
+++ b/client/src/components/RecordPage.js
@@ -11,8 +11,8 @@ import React, {
 import { Link } from 'react-router-dom';
 import {
   Button,
-  Container,
-  Header,
+  Container, Grid,
+  Header as SemanticHeader,
   Loader,
   Menu,
   Ref,
@@ -142,10 +142,10 @@ const Section = (props: SectionProps) => (
     className={`section ${props.className ? props.className : ''}`}
   >
     { props.title && (
-      <Header
+      <SemanticHeader
         content={props.title}
         dividing
-        size='large'
+        size='small'
       />
     )}
     { props.children }
@@ -178,4 +178,32 @@ const Image = (props: ImageProps) => (
 
 RecordPage.Image = Image;
 
+type HeaderProps = {
+  children: Node,
+  image: Attachment
+};
+
+const Header = (props: HeaderProps) => (
+  <Grid
+    columns={2}
+  >
+    { props.image && (
+      <Grid.Column
+        width={4}
+      >
+        <RecordPage.Image
+          item={props.image}
+        />
+      </Grid.Column>
+    )}
+    <Grid.Column
+      width={12}
+    >
+      { props.children }
+    </Grid.Column>
+  </Grid>
+);
+
+RecordPage.Header = Header;
+
 export default RecordPage;
diff --git a/client/src/i18n/en.json b/client/src/i18n/en.json
index 4052d09..bef14f3 100644
--- a/client/src/i18n/en.json
+++ b/client/src/i18n/en.json
@@ -168,6 +168,7 @@
     },
     "labels": {
       "artwork": "Artwork",
+      "details": "Details",
       "document": "Document",
       "id": "ID",
       "physicalComponent": "Physical Component",
diff --git a/client/src/pages/Artwork.js b/client/src/pages/Artwork.js
index ac5ee64..47b61e9 100644
--- a/client/src/pages/Artwork.js
+++ b/client/src/pages/Artwork.js
@@ -2,7 +2,6 @@
 
 import React, { useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
-import { Segment } from 'semantic-ui-react';
 import _ from 'underscore';
 import ArtworkCreators from '../components/ArtworkCreators';
 import ArtworkTitles from '../components/ArtworkTitles';
@@ -34,86 +33,82 @@ const Artwork = () => {
       loading={loading}
       renderTitle={() => getPrimaryTitle(item)}
     >
-      { item && item.primary_attachment && (
-        <RecordPage.Section>
-          <RecordPage.Image
-            item={item.primary_attachment}
+      <RecordPage.Section>
+        <RecordPage.Header
+          image={item?.primary_attachment}
+        >
+          <AttributesGrid
+            attributes={[{
+              name: 'id',
+              label: t('Common.labels.id'),
+              renderValue: () => t('Artwork.labels.id', { id: item.id })
+            }, {
+              name: 'date_start',
+              label: t('Artwork.labels.startDate')
+            }, {
+              name: 'date_end',
+              label: t('Artwork.labels.endDate')
+            }, {
+              name: 'date_descriptor',
+              label: t('Artwork.labels.dateDescription')
+            }, {
+              name: 'object_work_type',
+              label: t('Artwork.labels.objectWorkType'),
+              qualification: {
+                object: 'Artwork',
+                group: 'Object/Work Type'
+              }
+            }, {
+              name: 'materials',
+              label: t('Artwork.labels.materials'),
+              qualification: {
+                object: 'Artwork',
+                group: 'Material'
+              }
+            }, {
+              name: 'techniques',
+              label: t('Artwork.labels.techniques'),
+              qualification: {
+                object: 'Artwork',
+                group: 'Technique'
+              }
+            }, {
+              name: 'height',
+              label: t('Artwork.labels.height')
+            }, {
+              name: 'width',
+              label: t('Artwork.labels.width')
+            }, {
+              name: 'depth',
+              label: t('Artwork.labels.depth')
+            }, {
+              name: 'commissioning_context',
+              label: t('Artwork.labels.commissioningContext'),
+              qualification: {
+                object: 'Artwork',
+                group: 'Commissioning Context'
+              }
+            }, {
+              name: 'number_documents_visible',
+              label: t('Artwork.labels.numberDocumentsVisible')
+            }, {
+              name: 'notes_external',
+              label: t('Artwork.labels.notes')
+            }]}
+            item={item}
+            title={t('Common.labels.details')}
           />
-        </RecordPage.Section>
-      )}
+        </RecordPage.Header>
+      </RecordPage.Section>
       { !_.isEmpty(item?.artwork_titles) && (
         <RecordPage.Section
           title={t('Artwork.sections.titles')}
         >
-          <Segment>
-            <ArtworkTitles
-              items={item.artwork_titles}
-            />
-          </Segment>
+          <ArtworkTitles
+            items={item.artwork_titles}
+          />
         </RecordPage.Section>
       )}
-      <RecordPage.Section>
-        <AttributesGrid
-          attributes={[{
-            name: 'id',
-            label: t('Common.labels.id'),
-            renderValue: () => t('Artwork.labels.id', { id: item.id })
-          }, {
-            name: 'date_start',
-            label: t('Artwork.labels.startDate')
-          }, {
-            name: 'date_end',
-            label: t('Artwork.labels.endDate')
-          }, {
-            name: 'date_descriptor',
-            label: t('Artwork.labels.dateDescription')
-          }, {
-            name: 'object_work_type',
-            label: t('Artwork.labels.objectWorkType'),
-            qualification: {
-              object: 'Artwork',
-              group: 'Object/Work Type'
-            }
-          }, {
-            name: 'materials',
-            label: t('Artwork.labels.materials'),
-            qualification: {
-              object: 'Artwork',
-              group: 'Material'
-            }
-          }, {
-            name: 'techniques',
-            label: t('Artwork.labels.techniques'),
-            qualification: {
-              object: 'Artwork',
-              group: 'Technique'
-            }
-          }, {
-            name: 'height',
-            label: t('Artwork.labels.height')
-          }, {
-            name: 'width',
-            label: t('Artwork.labels.width')
-          }, {
-            name: 'depth',
-            label: t('Artwork.labels.depth')
-          }, {
-            name: 'commissioning_context',
-            label: t('Artwork.labels.commissioningContext'),
-            qualification: {
-              object: 'Artwork',
-              group: 'Commissioning Context'
-            }
-          }, {
-            name: 'number_documents_visible',
-            label: t('Artwork.labels.numberDocumentsVisible')
-          }, {
-            name: 'notes_external',
-            label: t('Artwork.labels.notes')
-          }]}
-          item={item}
-        />
-      </RecordPage.Section>
       { !_.isEmpty(item?.participations) && (
         <RecordPage.Section
           title={t('Artwork.sections.creators')}
diff --git a/client/src/pages/Document.js b/client/src/pages/Document.js
index a3eb26b..4a101cf 100644
--- a/client/src/pages/Document.js
+++ b/client/src/pages/Document.js
@@ -3,7 +3,7 @@
 import React, { useCallback } from 'react';
 import { BooleanIcon } from '@performant-software/semantic-components';
 import { useTranslation } from 'react-i18next';
-import { Header, Segment } from 'semantic-ui-react';
+import { Grid } from 'semantic-ui-react';
 import _ from 'underscore';
 import AttributesGrid from '../components/AttributesGrid';
 import DocumentActions from '../components/DocumentActions';
@@ -33,362 +33,366 @@ const Document = () => {
       loading={loading}
       renderTitle={() => item.name}
     >
-      { item && item.primary_attachment && (
-        <RecordPage.Section>
-          <RecordPage.Image
-            item={item.primary_attachment}
-          />
-        </RecordPage.Section>
-      )}
       <RecordPage.Section>
-        <AttributesGrid
-          attributes={[{
-            name: 'id',
-            label: t('Common.labels.id')
-          }, {
-            name: 'name',
-            label: t('Document.labels.name')
-          }, {
-            name: 'document_type',
-            label: t('Document.labels.documentType'),
-            qualification: {
-              object: 'Document',
-              group: 'Document Type'
-            }
-          }, {
-            name: 'document_format',
-            label: t('Document.labels.documentFormat'),
-            qualification: {
-              object: 'Document',
-              group: 'Document Format'
-            }
-          }, {
-            name: 'orientation',
-            label: t('Document.labels.orientation'),
-            qualification: {
-              object: 'Document',
-              group: 'Orientation (spine)'
-            }
-          }, {
-            name: 'size',
-            label: t('Document.labels.size'),
-            qualification: {
-              object: 'Document',
-              group: 'Size'
-            }
-          }, {
-            name: 'aperture',
-            label: t('Document.labels.aperture'),
-            qualification: {
-              object: 'Document',
-              group: 'Aperture'
-            }
-          }, {
-            name: 'notes',
-            label: t('Document.labels.notes')
-          }]}
-          item={item}
-        />
+        <RecordPage.Header
+          image={item?.primary_attachment}
+        >
+          <AttributesGrid
+            attributes={[{
+              name: 'id',
+              label: t('Common.labels.id')
+            }, {
+              name: 'name',
+              label: t('Document.labels.name')
+            }, {
+              name: 'document_type',
+              label: t('Document.labels.documentType'),
+              qualification: {
+                object: 'Document',
+                group: 'Document Type'
+              }
+            }, {
+              name: 'document_format',
+              label: t('Document.labels.documentFormat'),
+              qualification: {
+                object: 'Document',
+                group: 'Document Format'
+              }
+            }, {
+              name: 'orientation',
+              label: t('Document.labels.orientation'),
+              qualification: {
+                object: 'Document',
+                group: 'Orientation (spine)'
+              }
+            }, {
+              name: 'size',
+              label: t('Document.labels.size'),
+              qualification: {
+                object: 'Document',
+                group: 'Size'
+              }
+            }, {
+              name: 'aperture',
+              label: t('Document.labels.aperture'),
+              qualification: {
+                object: 'Document',
+                group: 'Aperture'
+              }
+            }, {
+              name: 'notes',
+              label: t('Document.labels.notes')
+            }]}
+            item={item}
+            title={t('Common.labels.details')}
+          />
+        </RecordPage.Header>
       </RecordPage.Section>
       { !_.isEmpty(item?.actions) && (
         <RecordPage.Section
           title={t('Document.tabs.actions')}
         >
-          <Segment>
-            <DocumentActions
-              items={item.actions}
-            />
-          </Segment>
+          <DocumentActions
+            items={item.actions}
+          />
         </RecordPage.Section>
       )}
       <RecordPage.Section
         title={t('Document.tabs.external')}
       >
-        <Header
-          content={t('Document.sections.binding')}
-        />
-        <AttributesGrid
-          attributes={[{
-            name: 'binding_type',
-            label: t('Document.labels.bindingType'),
-            qualification: {
-              object: 'Document',
-              group: 'Binding Type'
-            }
-          }, {
-            name: 'binding_color',
-            label: t('Document.labels.bindingColor'),
-            qualification: {
-              object: 'Document',
-              group: 'Binding Color'
-            }
-          }, {
-            name: 'number_sewing_supports',
-            label: t('Document.labels.numberSewingSupports')
-          }, {
-            name: 'spine_features',
-            label: t('Document.labels.spineFeatures'),
-            qualification: {
-              object: 'Document',
-              group: 'Spine Features'
-            }
-          }, {
-            name: 'furniture',
-            label: t('Document.labels.furniture'),
-            qualification: {
-              object: 'Document',
-              group: 'Furniture'
-            }
-          }, {
-            name: 'fastenings',
-            label: t('Document.labels.fastenings'),
-            qualification: {
-              object: 'Document',
-              group: 'Fastenings'
-            }
-          }, {
-            name: 'number_fastenings',
-            label: t('Document.labels.numberFastenings')
-          }, {
-            name: 'location_fastenings',
-            label: t('Document.labels.locationsOfFastenings'),
-            qualification: {
-              object: 'Document',
-              group: 'Location of Fastenings'
-            }
-          }, {
-            name: 'inscriptions_on_binding',
-            label: t('Document.labels.inscriptionsOnBinding'),
-            renderValue: () => <BooleanIcon value={item.inscriptions_on_binding} />
-          }, {
-            name: 'inscription_text',
-            label: t('Document.labels.inscriptionText')
-          }, {
-            name: 'binding_ornamentation',
-            label: t('Document.labels.bindingOrnamentation'),
-            qualification: {
-              object: 'Document',
-              group: 'Binding Ornamentation'
-            }
-          }, {
-            name: 'binding_iconography',
-            label: t('Document.labels.bindingIconography'),
-            qualification: {
-              object: 'Document',
-              group: 'Iconography',
-              formField: 'binding_iconography'
-            }
-          }]}
-          item={item}
-        />
-        <Header
-          content={t('Document.sections.endband')}
-        />
-        <AttributesGrid
-          attributes={[{
-            name: 'endband_present',
-            label: t('Document.labels.endbandPresent'),
-            renderValue: () => <BooleanIcon value={item.endband_present} />
-          }, {
-            name: 'endband_colors',
-            label: t('Document.labels.endbandColors'),
-            qualification: {
-              object: 'Document',
-              group: 'Color',
-              formField: 'endband_colors'
-            }
-          }, {
-            name: 'endband_style',
-            label: t('Document.labels.endbandStyle'),
-            qualification: {
-              object: 'Document',
-              group: 'Endband Style'
-            }
-          }]}
-          item={item}
-        />
-        <Header
-          content={t('Document.sections.foreEdges')}
-        />
-        <AttributesGrid
-          attributes={[{
-            name: 'binding_relationship_text_block',
-            label: t('Document.labels.bindingRelationshipToTextBlock'),
-            qualification: {
-              object: 'Document',
-              group: 'Binding Relationship to Text Block'
-            }
-          }, {
-            name: 'decorated_fore_edges',
-            label: t('Document.labels.decoratedForeEdges'),
-            qualification: {
-              object: 'Document',
-              group: 'Decorated Fore-Edges'
-            }
-          }, {
-            name: 'uncut_fore_edges',
-            label: t('Document.labels.uncutForeEdges'),
-            renderValue: () => <BooleanIcon value={item.uncut_fore_edges} />
-          }, {
-            name: 'fore_edges_color',
-            label: t('Document.labels.colorOfForeEdges'),
-            qualification: {
-              object: 'Document',
-              group: 'Color',
-              formField: 'fore_edges_color'
-            }
-          }, {
-            name: 'fore_edge_text',
-            label: t('Document.labels.foreEdgeText')
-          }]}
-          item={item}
-        />
-        <Header
-          content={t('Document.sections.bookmarks')}
-        />
-        <AttributesGrid
-          attributes={[{
-            name: 'bookmarks_registers',
-            label: t('Document.labels.bookmarksRegisters')
-          }, {
-            name: 'bookmark_register_color',
-            label: t('Document.labels.bookmarksRegisterColor'),
-            qualification: {
-              object: 'Document',
-              group: 'Color',
-              formField: 'bookmark_register_color'
-            }
-          }, {
-            name: 'bookmark_register_style',
-            label: t('Document.labels.bookmarkRegisterStyle'),
-            qualification: {
-              object: 'Document',
-              group: 'Bookmark/Register Style'
-            }
-          }]}
-          item={item}
-        />
+        <Grid
+          columns={2}
+        >
+          <Grid.Column>
+            <AttributesGrid
+              attributes={[{
+                name: 'binding_type',
+                label: t('Document.labels.bindingType'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Binding Type'
+                }
+              }, {
+                name: 'binding_color',
+                label: t('Document.labels.bindingColor'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Binding Color'
+                }
+              }, {
+                name: 'number_sewing_supports',
+                label: t('Document.labels.numberSewingSupports')
+              }, {
+                name: 'spine_features',
+                label: t('Document.labels.spineFeatures'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Spine Features'
+                }
+              }, {
+                name: 'furniture',
+                label: t('Document.labels.furniture'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Furniture'
+                }
+              }, {
+                name: 'fastenings',
+                label: t('Document.labels.fastenings'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Fastenings'
+                }
+              }, {
+                name: 'number_fastenings',
+                label: t('Document.labels.numberFastenings')
+              }, {
+                name: 'location_fastenings',
+                label: t('Document.labels.locationsOfFastenings'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Location of Fastenings'
+                }
+              }, {
+                name: 'inscriptions_on_binding',
+                label: t('Document.labels.inscriptionsOnBinding'),
+                renderValue: () => <BooleanIcon value={item.inscriptions_on_binding} />
+              }, {
+                name: 'inscription_text',
+                label: t('Document.labels.inscriptionText')
+              }, {
+                name: 'binding_ornamentation',
+                label: t('Document.labels.bindingOrnamentation'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Binding Ornamentation'
+                }
+              }, {
+                name: 'binding_iconography',
+                label: t('Document.labels.bindingIconography'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Iconography',
+                  formField: 'binding_iconography'
+                }
+              }]}
+              item={item}
+              title={t('Document.sections.binding')}
+            />
+          </Grid.Column>
+          <Grid.Column>
+            <AttributesGrid
+              attributes={[{
+                name: 'endband_present',
+                label: t('Document.labels.endbandPresent'),
+                renderValue: () => <BooleanIcon value={item.endband_present} />
+              }, {
+                name: 'endband_colors',
+                label: t('Document.labels.endbandColors'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Color',
+                  formField: 'endband_colors'
+                }
+              }, {
+                name: 'endband_style',
+                label: t('Document.labels.endbandStyle'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Endband Style'
+                }
+              }]}
+              item={item}
+              title={t('Document.sections.endband')}
+            />
+          </Grid.Column>
+          <Grid.Column>
+            <AttributesGrid
+              attributes={[{
+                name: 'binding_relationship_text_block',
+                label: t('Document.labels.bindingRelationshipToTextBlock'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Binding Relationship to Text Block'
+                }
+              }, {
+                name: 'decorated_fore_edges',
+                label: t('Document.labels.decoratedForeEdges'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Decorated Fore-Edges'
+                }
+              }, {
+                name: 'uncut_fore_edges',
+                label: t('Document.labels.uncutForeEdges'),
+                renderValue: () => <BooleanIcon value={item.uncut_fore_edges} />
+              }, {
+                name: 'fore_edges_color',
+                label: t('Document.labels.colorOfForeEdges'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Color',
+                  formField: 'fore_edges_color'
+                }
+              }, {
+                name: 'fore_edge_text',
+                label: t('Document.labels.foreEdgeText')
+              }]}
+              item={item}
+              title={t('Document.sections.foreEdges')}
+            />
+          </Grid.Column>
+          <Grid.Column>
+            <AttributesGrid
+              attributes={[{
+                name: 'bookmarks_registers',
+                label: t('Document.labels.bookmarksRegisters')
+              }, {
+                name: 'bookmark_register_color',
+                label: t('Document.labels.bookmarksRegisterColor'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Color',
+                  formField: 'bookmark_register_color'
+                }
+              }, {
+                name: 'bookmark_register_style',
+                label: t('Document.labels.bookmarkRegisterStyle'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Bookmark/Register Style'
+                }
+              }]}
+              item={item}
+              title={t('Document.sections.bookmarks')}
+            />
+          </Grid.Column>
+        </Grid>
       </RecordPage.Section>
       <RecordPage.Section
         title={t('Document.tabs.internal')}
       >
-        <Header
-          content={t('Document.sections.layout')}
-        />
-        <AttributesGrid
-          attributes={[{
-            name: 'text_technology',
-            label: t('Document.labels.textTechnology'),
-            qualification: {
-              object: 'Document',
-              group: 'Text Technology'
-            }
-          }, {
-            name: 'text_columns',
-            label: t('Document.labels.textColumns')
-          }, {
-            name: 'page_contents',
-            label: t('Document.labels.pageContents'),
-            qualification: {
-              object: 'Document',
-              group: 'Page Contents'
-            }
-          }, {
-            name: 'ruling',
-            label: t('Document.labels.ruling'),
-            renderValue: () => <BooleanIcon value={item.ruling} />
-          }, {
-            name: 'ruling_color',
-            label: t('Document.labels.rulingColor'),
-            qualification: {
-              object: 'Document',
-              group: 'Color',
-              formField: 'ruling_color'
-            }
-          }, {
-            name: 'rubrication',
-            label: t('Document.labels.rubrication'),
-            renderValue: () => <BooleanIcon value={item.rubrication} />
-          }, {
-            name: 'rubrication_color',
-            label: t('Document.labels.rubricationColor'),
-            qualification: {
-              object: 'Document',
-              group: 'Color',
-              formField: 'rubrication_color'
-            }
-          }]}
-          item={item}
-        />
-        <Header
-          content={t('Document.sections.text')}
-        />
-        <AttributesGrid
-          attributes={[{
-            name: 'legibility',
-            label: t('Document.labels.legibility'),
-            qualification: {
-              object: 'Document',
-              group: 'Legibility'
-            }
-          }, {
-            name: 'script',
-            label: t('Document.labels.script'),
-            qualification: {
-              object: 'Document',
-              group: 'Script'
-            }
-          }, {
-            name: 'script_type',
-            label: t('Document.labels.scriptType'),
-            qualification: {
-              object: 'Document',
-              group: 'Script Type'
-            }
-          }, {
-            name: 'simulated_script',
-            label: t('Document.labels.simulatedScript'),
-            qualification: {
-              object: 'Document',
-              group: 'Simulated Script'
-            }
-          }, {
-            name: 'language',
-            label: t('Document.labels.language'),
-            qualification: {
-              object: 'Document',
-              group: 'Language'
-            }
-          }, {
-            name: 'identity',
-            label: t('Document.labels.identity')
-          }, {
-            name: 'transcription',
-            label: t('Document.labels.transcriptionDiplomatic')
-          }, {
-            name: 'transcription_expanded',
-            label: t('Document.labels.transcriptionExpanded')
-          }, {
-            name: 'transcription_translation',
-            label: t('Document.labels.transcriptionTranslation')
-          }, {
-            name: 'type_of_illumination',
-            label: t('Document.labels.typeOfIllumination'),
-            qualification: {
-              object: 'Document',
-              group: 'Type of Illumination'
-            }
-          }, {
-            name: 'illumination_iconography',
-            label: t('Document.labels.illuminationIconography'),
-            qualification: {
-              object: 'Document',
-              group: 'Iconography',
-              formField: 'illumination_iconography'
-            }
-          }]}
-          item={item}
-        />
+        <Grid
+          columns={2}
+        >
+          <Grid.Column>
+            <AttributesGrid
+              attributes={[{
+                name: 'text_technology',
+                label: t('Document.labels.textTechnology'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Text Technology'
+                }
+              }, {
+                name: 'text_columns',
+                label: t('Document.labels.textColumns')
+              }, {
+                name: 'page_contents',
+                label: t('Document.labels.pageContents'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Page Contents'
+                }
+              }, {
+                name: 'ruling',
+                label: t('Document.labels.ruling'),
+                renderValue: () => <BooleanIcon value={item.ruling} />
+              }, {
+                name: 'ruling_color',
+                label: t('Document.labels.rulingColor'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Color',
+                  formField: 'ruling_color'
+                }
+              }, {
+                name: 'rubrication',
+                label: t('Document.labels.rubrication'),
+                renderValue: () => <BooleanIcon value={item.rubrication} />
+              }, {
+                name: 'rubrication_color',
+                label: t('Document.labels.rubricationColor'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Color',
+                  formField: 'rubrication_color'
+                }
+              }]}
+              item={item}
+              title={t('Document.sections.layout')}
+            />
+          </Grid.Column>
+          <Grid.Column>
+            <AttributesGrid
+              attributes={[{
+                name: 'legibility',
+                label: t('Document.labels.legibility'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Legibility'
+                }
+              }, {
+                name: 'script',
+                label: t('Document.labels.script'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Script'
+                }
+              }, {
+                name: 'script_type',
+                label: t('Document.labels.scriptType'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Script Type'
+                }
+              }, {
+                name: 'simulated_script',
+                label: t('Document.labels.simulatedScript'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Simulated Script'
+                }
+              }, {
+                name: 'language',
+                label: t('Document.labels.language'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Language'
+                }
+              }, {
+                name: 'identity',
+                label: t('Document.labels.identity')
+              }, {
+                name: 'transcription',
+                label: t('Document.labels.transcriptionDiplomatic')
+              }, {
+                name: 'transcription_expanded',
+                label: t('Document.labels.transcriptionExpanded')
+              }, {
+                name: 'transcription_translation',
+                label: t('Document.labels.transcriptionTranslation')
+              }, {
+                name: 'type_of_illumination',
+                label: t('Document.labels.typeOfIllumination'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Type of Illumination'
+                }
+              }, {
+                name: 'illumination_iconography',
+                label: t('Document.labels.illuminationIconography'),
+                qualification: {
+                  object: 'Document',
+                  group: 'Iconography',
+                  formField: 'illumination_iconography'
+                }
+              }]}
+              item={item}
+              title={t('Document.sections.text')}
+            />
+          </Grid.Column>
+        </Grid>
       </RecordPage.Section>
     </RecordPage>
   );
diff --git a/client/src/pages/PhysicalComponent.js b/client/src/pages/PhysicalComponent.js
index 279d564..a5e4199 100644
--- a/client/src/pages/PhysicalComponent.js
+++ b/client/src/pages/PhysicalComponent.js
@@ -29,35 +29,31 @@ const PhysicalComponent = () => {
       renderTitle={() => item.name}
     >
       <RecordPage.Section>
-        { item && item.primary_attachment && (
-          <RecordPage.Section>
-            <RecordPage.Image
-              item={item.primary_attachment}
-            />
-          </RecordPage.Section>
-        )}
-      </RecordPage.Section>
-      <RecordPage.Section>
-        <AttributesGrid
-          attributes={[{
-            name: 'id',
-            label: t('Common.labels.id'),
-            renderValue: () => t('PhysicalComponent.labels.id', { id: item.id })
-          }, {
-            name: 'height',
-            label: t('PhysicalComponent.labels.height')
-          }, {
-            name: 'width',
-            label: t('PhysicalComponent.labels.width')
-          }, {
-            name: 'depth',
-            label: t('PhysicalComponent.labels.depth')
-          }, {
-            name: 'notes',
-            label: t('PhysicalComponent.labels.notes')
-          }]}
-          item={item}
-        />
+        <RecordPage.Header
+          image={item?.primary_attachment}
+        >
+          <AttributesGrid
+            attributes={[{
+              name: 'id',
+              label: t('Common.labels.id'),
+              renderValue: () => t('PhysicalComponent.labels.id', { id: item.id })
+            }, {
+              name: 'height',
+              label: t('PhysicalComponent.labels.height')
+            }, {
+              name: 'width',
+              label: t('PhysicalComponent.labels.width')
+            }, {
+              name: 'depth',
+              label: t('PhysicalComponent.labels.depth')
+            }, {
+              name: 'notes',
+              label: t('PhysicalComponent.labels.notes')
+            }]}
+            item={item}
+            title={t('Common.labels.details')}
+          />
+        </RecordPage.Header>
       </RecordPage.Section>
     </RecordPage>
   );
diff --git a/client/src/pages/VisualContext.js b/client/src/pages/VisualContext.js
index 407aaa0..a26aa23 100644
--- a/client/src/pages/VisualContext.js
+++ b/client/src/pages/VisualContext.js
@@ -30,60 +30,56 @@ const VisualContext = () => {
       renderTitle={() => item.name}
     >
       <RecordPage.Section>
-        { item && item.primary_attachment && (
-          <RecordPage.Section>
-            <RecordPage.Image
-              item={item.primary_attachment}
-            />
-          </RecordPage.Section>
-        )}
-      </RecordPage.Section>
-      <RecordPage.Section>
-        <AttributesGrid
-          attributes={[{
-            name: 'id',
-            label: t('Common.labels.id'),
-            renderValue: () => t('VisualContext.labels.id', { id: item.id })
-          }, {
-            name: 'height',
-            label: t('VisualContext.labels.height')
-          }, {
-            name: 'width',
-            label: t('VisualContext.labels.width')
-          }, {
-            name: 'depth',
-            label: t('VisualContext.labels.depth')
-          }, {
-            name: 'notes',
-            label: t('VisualContext.labels.notes')
-          }, {
-            name: 'general_subject_genre',
-            label: t('VisualContext.labels.generalSubjectGenre'),
-            qualification: {
-              object: 'Visual Context',
-              group: 'General Subject/Genre'
-            }
-          }, {
-            name: 'subject_cultural_context',
-            label: t('VisualContext.labels.subjectCulturalContext'),
-            qualification: {
-              object: 'Visual Context',
-              group: 'Subject Cultural Context'
-            }
-          }, {
-            name: 'subject_iconography',
-            label: t('VisualContext.labels.specificSubjectIconography'),
-            qualification: {
-              object: 'Visual Context',
-              group: 'Specific Subject/Iconography'
-            }
-          }, {
-            name: 'beta',
-            label: t('VisualContext.labels.beta'),
-            renderValue: () => <BooleanIcon value={item.beta} />
-          }]}
-          item={item}
-        />
+        <RecordPage.Header
+          image={item?.primary_attachment}
+        >
+          <AttributesGrid
+            attributes={[{
+              name: 'id',
+              label: t('Common.labels.id'),
+              renderValue: () => t('VisualContext.labels.id', { id: item.id })
+            }, {
+              name: 'height',
+              label: t('VisualContext.labels.height')
+            }, {
+              name: 'width',
+              label: t('VisualContext.labels.width')
+            }, {
+              name: 'depth',
+              label: t('VisualContext.labels.depth')
+            }, {
+              name: 'notes',
+              label: t('VisualContext.labels.notes')
+            }, {
+              name: 'general_subject_genre',
+              label: t('VisualContext.labels.generalSubjectGenre'),
+              qualification: {
+                object: 'Visual Context',
+                group: 'General Subject/Genre'
+              }
+            }, {
+              name: 'subject_cultural_context',
+              label: t('VisualContext.labels.subjectCulturalContext'),
+              qualification: {
+                object: 'Visual Context',
+                group: 'Subject Cultural Context'
+              }
+            }, {
+              name: 'subject_iconography',
+              label: t('VisualContext.labels.specificSubjectIconography'),
+              qualification: {
+                object: 'Visual Context',
+                group: 'Specific Subject/Iconography'
+              }
+            }, {
+              name: 'beta',
+              label: t('VisualContext.labels.beta'),
+              renderValue: () => <BooleanIcon value={item.beta} />
+            }]}
+            item={item}
+            title={t('Common.labels.details')}
+          />
+        </RecordPage.Header>
       </RecordPage.Section>
     </RecordPage>
   );