Updated via schema editor on 2025-07-31 15:15

This commit is contained in:
Flatlogic Bot 2025-07-31 15:16:26 +00:00
parent e7dbda30bd
commit 6712d9401a
21 changed files with 374 additions and 109 deletions

5
.gitignore vendored
View File

@ -1,3 +1,8 @@
node_modules/
*/node_modules/
*/build/
**/node_modules/
**/build/
.DS_Store
.env

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,6 @@ module.exports = class ReportsDBApi {
title: data.title || null,
created_date: data.created_date || null,
views: data.views || null,
annotations: data.annotations || null,
address: data.address || null,
phone: data.phone || null,
importHash: data.importHash || null,
@ -32,6 +31,10 @@ module.exports = class ReportsDBApi {
transaction,
});
await reports.setAnnotation(data.Annotation || [], {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.reports.getTableName(),
@ -56,7 +59,6 @@ module.exports = class ReportsDBApi {
title: item.title || null,
created_date: item.created_date || null,
views: item.views || null,
annotations: item.annotations || null,
address: item.address || null,
phone: item.phone || null,
importHash: item.importHash || null,
@ -100,9 +102,6 @@ module.exports = class ReportsDBApi {
if (data.views !== undefined) updatePayload.views = data.views;
if (data.annotations !== undefined)
updatePayload.annotations = data.annotations;
if (data.address !== undefined) updatePayload.address = data.address;
if (data.phone !== undefined) updatePayload.phone = data.phone;
@ -119,6 +118,10 @@ module.exports = class ReportsDBApi {
);
}
if (data.Annotation !== undefined) {
await reports.setAnnotation(data.Annotation, { transaction });
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.reports.getTableName(),
@ -198,6 +201,10 @@ module.exports = class ReportsDBApi {
transaction,
});
output.Annotation = await reports.getAnnotation({
transaction,
});
return output;
}
@ -240,6 +247,12 @@ module.exports = class ReportsDBApi {
: {},
},
{
model: db.annotations,
as: 'Annotation',
required: false,
},
{
model: db.file,
as: 'image',
@ -261,13 +274,6 @@ module.exports = class ReportsDBApi {
};
}
if (filter.annotations) {
where = {
...where,
[Op.and]: Utils.ilike('reports', 'annotations', filter.annotations),
};
}
if (filter.address) {
where = {
...where,
@ -337,6 +343,38 @@ module.exports = class ReportsDBApi {
};
}
if (filter.Annotation) {
const searchTerms = filter.Annotation.split('|');
include = [
{
model: db.annotations,
as: 'Annotation_filter',
required: searchTerms.length > 0,
where:
searchTerms.length > 0
? {
[Op.or]: [
{
id: {
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
},
},
{
text: {
[Op.or]: searchTerms.map((term) => ({
[Op.iLike]: `%${term}%`,
})),
},
},
],
}
: undefined,
},
...include,
];
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;

View File

@ -0,0 +1,49 @@
module.exports = {
/**
* @param {QueryInterface} queryInterface
* @param {Sequelize} Sequelize
* @returns {Promise<void>}
*/
async up(queryInterface, Sequelize) {
/**
* @type {Transaction}
*/
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.removeColumn('reports', 'annotations', {
transaction,
});
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
/**
* @param {QueryInterface} queryInterface
* @param {Sequelize} Sequelize
* @returns {Promise<void>}
*/
async down(queryInterface, Sequelize) {
/**
* @type {Transaction}
*/
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.addColumn(
'reports',
'annotations',
{
type: Sequelize.DataTypes.TEXT,
},
{ transaction },
);
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
};

View File

@ -26,10 +26,6 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.INTEGER,
},
annotations: {
type: DataTypes.TEXT,
},
address: {
type: DataTypes.TEXT,
},
@ -52,6 +48,24 @@ module.exports = function (sequelize, DataTypes) {
);
reports.associate = (db) => {
db.reports.belongsToMany(db.annotations, {
as: 'Annotation',
foreignKey: {
name: 'reports_AnnotationId',
},
constraints: false,
through: 'reportsAnnotationAnnotations',
});
db.reports.belongsToMany(db.annotations, {
as: 'Annotation_filter',
foreignKey: {
name: 'reports_AnnotationId',
},
constraints: false,
through: 'reportsAnnotationAnnotations',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop

View File

@ -17,7 +17,7 @@ const AnnotationsData = [
{
text: 'Point of Interest 1',
color: 'pink',
color: 'red',
// type code here for "relation_one" field
},
@ -25,7 +25,7 @@ const AnnotationsData = [
{
text: 'Point of Interest 2',
color: 'yellow',
color: 'white',
// type code here for "relation_one" field
},
@ -37,6 +37,14 @@ const AnnotationsData = [
// type code here for "relation_one" field
},
{
text: 'Point of Interest 4',
color: 'purple',
// type code here for "relation_one" field
},
];
const ClientsData = [
@ -63,6 +71,14 @@ const ClientsData = [
// type code here for "relation_many" field
},
{
name: 'Umbrella Corp.',
// type code here for "images" field
// type code here for "relation_many" field
},
];
const PhotosData = [
@ -80,6 +96,11 @@ const PhotosData = [
// type code here for "images" field
// type code here for "relation_one" field
},
{
// type code here for "images" field
// type code here for "relation_one" field
},
];
const ReportsData = [
@ -92,13 +113,13 @@ const ReportsData = [
views: 150,
annotations: 'Lynn Margulis',
address: 'Hermann von Helmholtz',
address: 'Hans Bethe',
phone: 'John Dalton',
phone: 'Francis Galton',
// type code here for "images" field
// type code here for "relation_many" field
},
{
@ -110,13 +131,13 @@ const ReportsData = [
views: 200,
annotations: 'John Dalton',
address: 'Albrecht von Haller',
address: 'Max von Laue',
phone: 'Paul Ehrlich',
phone: 'B. F. Skinner',
// type code here for "images" field
// type code here for "relation_many" field
},
{
@ -128,37 +149,61 @@ const ReportsData = [
views: 175,
annotations: 'Rudolf Virchow',
address: 'Paul Dirac',
address: 'Claude Levi-Strauss',
phone: 'Konrad Lorenz',
phone: 'Ernst Mayr',
// type code here for "images" field
// type code here for "relation_many" field
},
{
title: 'Product Launch Overview',
// type code here for "relation_one" field
created_date: new Date('2023-04-05T14:00:00Z'),
views: 220,
address: 'Frederick Gowland Hopkins',
phone: 'Alexander Fleming',
// type code here for "images" field
// type code here for "relation_many" field
},
];
const ImprovementsData = [
{
improvement_name: 'Rudolf Virchow',
improvement_name: 'Anton van Leeuwenhoek',
improvement_status: 'Hermann von Helmholtz',
improvement_status: 'Charles Darwin',
},
{
improvement_name: 'Lucretius',
improvement_name: 'Max Delbruck',
improvement_status: 'Heike Kamerlingh Onnes',
improvement_status: 'Louis Victor de Broglie',
},
{
improvement_name: 'Gertrude Belle Elion',
improvement_name: 'Edward O. Wilson',
improvement_status: 'Claude Levi-Strauss',
improvement_status: 'J. Robert Oppenheimer',
},
{
improvement_name: 'Max Planck',
improvement_status: 'John Bardeen',
},
];
const CategoriesData = [{}, {}, {}];
const CategoriesData = [{}, {}, {}, {}];
// Similar logic for "relation_many"
@ -195,6 +240,17 @@ async function associateAnnotationWithPhoto() {
if (Annotation2?.setPhoto) {
await Annotation2.setPhoto(relatedPhoto2);
}
const relatedPhoto3 = await Photos.findOne({
offset: Math.floor(Math.random() * (await Photos.count())),
});
const Annotation3 = await Annotations.findOne({
order: [['id', 'ASC']],
offset: 3,
});
if (Annotation3?.setPhoto) {
await Annotation3.setPhoto(relatedPhoto3);
}
}
// Similar logic for "relation_many"
@ -232,6 +288,17 @@ async function associatePhotoWithClient() {
if (Photo2?.setClient) {
await Photo2.setClient(relatedClient2);
}
const relatedClient3 = await Clients.findOne({
offset: Math.floor(Math.random() * (await Clients.count())),
});
const Photo3 = await Photos.findOne({
order: [['id', 'ASC']],
offset: 3,
});
if (Photo3?.setClient) {
await Photo3.setClient(relatedClient3);
}
}
async function associateReportWithClient() {
@ -267,8 +334,21 @@ async function associateReportWithClient() {
if (Report2?.setClient) {
await Report2.setClient(relatedClient2);
}
const relatedClient3 = await Clients.findOne({
offset: Math.floor(Math.random() * (await Clients.count())),
});
const Report3 = await Reports.findOne({
order: [['id', 'ASC']],
offset: 3,
});
if (Report3?.setClient) {
await Report3.setClient(relatedClient3);
}
}
// Similar logic for "relation_many"
module.exports = {
up: async (queryInterface, Sequelize) => {
await Annotations.bulkCreate(AnnotationsData);
@ -293,6 +373,8 @@ module.exports = {
await associatePhotoWithClient(),
await associateReportWithClient(),
// Similar logic for "relation_many"
]);
},

View File

@ -23,9 +23,6 @@ router.use(checkCrudPermissions('reports'));
* title:
* type: string
* default: title
* annotations:
* type: string
* default: annotations
* address:
* type: string
* default: address
@ -320,7 +317,6 @@ router.get(
const fields = [
'id',
'title',
'annotations',
'address',
'phone',
'views',

View File

@ -47,7 +47,7 @@ module.exports = class SearchService {
clients: ['name'],
reports: ['title', 'annotations', 'address', 'phone'],
reports: ['title', 'address', 'phone'],
improvements: ['improvement_name', 'improvement_status'],
};

View File

@ -118,17 +118,6 @@ const CardReports = ({
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
Annotations
</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{item.annotations}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
Address
@ -159,6 +148,19 @@ const CardReports = ({
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
Annotation
</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{dataFormatter
.annotationsManyListFormatter(item.Annotation)
.join(', ')}
</div>
</dd>
</div>
</dl>
</li>
))}

View File

@ -84,11 +84,6 @@ const ListReports = ({
<p className={'line-clamp-2'}>{item.views}</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Annotations</p>
<p className={'line-clamp-2'}>{item.annotations}</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Address</p>
<p className={'line-clamp-2'}>{item.address}</p>
@ -107,6 +102,15 @@ const ListReports = ({
className='mx-auto w-8 h-8'
/>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Annotation</p>
<p className={'line-clamp-2'}>
{dataFormatter
.annotationsManyListFormatter(item.Annotation)
.join(', ')}
</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}

View File

@ -100,18 +100,6 @@ export const loadColumns = async (
type: 'number',
},
{
field: 'annotations',
headerName: 'Annotations',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'address',
headerName: 'Address',
@ -156,6 +144,25 @@ export const loadColumns = async (
),
},
{
field: 'Annotation',
headerName: 'Annotation',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: false,
sortable: false,
type: 'singleSelect',
valueFormatter: ({ value }) =>
dataFormatter.annotationsManyListFormatter(value).join(', '),
renderEditCell: (params) => (
<DataGridMultiSelect {...params} entityName={'annotations'} />
),
},
{
field: 'actions',
type: 'actions',

View File

@ -19,7 +19,7 @@ export default function WebSiteFooter({ projectName }: WebSiteFooterProps) {
const style = FooterStyle.WITH_PROJECT_NAME;
const design = FooterDesigns.DEFAULT_DESIGN;
const design = FooterDesigns.DESIGN_DIVERSITY;
return (
<div

View File

@ -19,7 +19,7 @@ export default function WebSiteHeader({ projectName }: WebSiteHeaderProps) {
const style = HeaderStyle.PAGES_LEFT;
const design = HeaderDesigns.DESIGN_DIVERSITY;
const design = HeaderDesigns.DEFAULT_DESIGN;
return (
<header id='websiteHeader' className='overflow-hidden'>
<div

View File

@ -39,6 +39,25 @@ export default {
});
},
annotationsManyListFormatter(val) {
if (!val || !val.length) return [];
return val.map((item) => item.text);
},
annotationsOneListFormatter(val) {
if (!val) return '';
return val.text;
},
annotationsManyListFormatterEdit(val) {
if (!val || !val.length) return [];
return val.map((item) => {
return { id: item.id, label: item.text };
});
},
annotationsOneListFormatterEdit(val) {
if (!val) return '';
return { label: val.text, id: val.id };
},
clientsManyListFormatter(val) {
if (!val || !val.length) return [];
return val.map((item) => item.name);

View File

@ -88,8 +88,6 @@ const ClientsView = () => {
<th>Views</th>
<th>Annotations</th>
<th>Address</th>
<th>Phone</th>
@ -113,8 +111,6 @@ const ClientsView = () => {
<td data-label='views'>{item.views}</td>
<td data-label='annotations'>{item.annotations}</td>
<td data-label='address'>{item.address}</td>
<td data-label='phone'>{item.phone}</td>
@ -176,8 +172,6 @@ const ClientsView = () => {
<th>Views</th>
<th>Annotations</th>
<th>Address</th>
<th>Phone</th>
@ -201,8 +195,6 @@ const ClientsView = () => {
<td data-label='views'>{item.views}</td>
<td data-label='annotations'>{item.annotations}</td>
<td data-label='address'>{item.address}</td>
<td data-label='phone'>{item.phone}</td>

View File

@ -44,13 +44,13 @@ const EditReports = () => {
views: '',
annotations: '',
address: '',
phone: '',
image: [],
Annotation: [],
};
const [initialValues, setInitialValues] = useState(initVals);
@ -141,10 +141,6 @@ const EditReports = () => {
<Field type='number' name='views' placeholder='Views' />
</FormField>
<FormField label='Annotations'>
<Field name='annotations' placeholder='Annotations' />
</FormField>
<FormField label='Address'>
<Field name='address' placeholder='Address' />
</FormField>
@ -169,6 +165,17 @@ const EditReports = () => {
></Field>
</FormField>
<FormField label='Annotation' labelFor='Annotation'>
<Field
name='Annotation'
id='Annotation'
component={SelectFieldMany}
options={initialValues.Annotation}
itemRef={'annotations'}
showField={'text'}
></Field>
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type='submit' color='info' label='Submit' />

View File

@ -44,13 +44,13 @@ const EditReportsPage = () => {
views: '',
annotations: '',
address: '',
phone: '',
image: [],
Annotation: [],
};
const [initialValues, setInitialValues] = useState(initVals);
@ -139,10 +139,6 @@ const EditReportsPage = () => {
<Field type='number' name='views' placeholder='Views' />
</FormField>
<FormField label='Annotations'>
<Field name='annotations' placeholder='Annotations' />
</FormField>
<FormField label='Address'>
<Field name='address' placeholder='Address' />
</FormField>
@ -167,6 +163,17 @@ const EditReportsPage = () => {
></Field>
</FormField>
<FormField label='Annotation' labelFor='Annotation'>
<Field
name='Annotation'
id='Annotation'
component={SelectFieldMany}
options={initialValues.Annotation}
itemRef={'annotations'}
showField={'text'}
></Field>
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type='submit' color='info' label='Submit' />

View File

@ -30,7 +30,6 @@ const ReportsTablesPage = () => {
const [filters] = useState([
{ label: 'Title', title: 'title' },
{ label: 'Annotations', title: 'annotations' },
{ label: 'Address', title: 'address' },
{ label: 'Phone', title: 'phone' },
{ label: 'Views', title: 'views', number: 'true' },
@ -38,6 +37,8 @@ const ReportsTablesPage = () => {
{ label: 'CreatedDate', title: 'created_date', date: 'true' },
{ label: 'Client', title: 'client' },
{ label: 'Annotation', title: 'Annotation' },
]);
const hasCreatePermission =

View File

@ -41,13 +41,13 @@ const initialValues = {
views: '',
annotations: '',
address: '',
phone: '',
image: [],
Annotation: [],
};
const ReportsNew = () => {
@ -103,10 +103,6 @@ const ReportsNew = () => {
<Field type='number' name='views' placeholder='Views' />
</FormField>
<FormField label='Annotations'>
<Field name='annotations' placeholder='Annotations' />
</FormField>
<FormField label='Address'>
<Field name='address' placeholder='Address' />
</FormField>
@ -131,6 +127,16 @@ const ReportsNew = () => {
></Field>
</FormField>
<FormField label='Annotation' labelFor='Annotation'>
<Field
name='Annotation'
id='Annotation'
itemRef={'annotations'}
options={[]}
component={SelectFieldMany}
></Field>
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type='submit' color='info' label='Submit' />

View File

@ -30,7 +30,6 @@ const ReportsTablesPage = () => {
const [filters] = useState([
{ label: 'Title', title: 'title' },
{ label: 'Annotations', title: 'annotations' },
{ label: 'Address', title: 'address' },
{ label: 'Phone', title: 'phone' },
{ label: 'Views', title: 'views', number: 'true' },
@ -38,6 +37,8 @@ const ReportsTablesPage = () => {
{ label: 'CreatedDate', title: 'created_date', date: 'true' },
{ label: 'Client', title: 'client' },
{ label: 'Annotation', title: 'Annotation' },
]);
const hasCreatePermission =

View File

@ -89,11 +89,6 @@ const ReportsView = () => {
<p>{reports?.views || 'No data'}</p>
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>Annotations</p>
<p>{reports?.annotations}</p>
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>Address</p>
<p>{reports?.address}</p>
@ -117,6 +112,47 @@ const ReportsView = () => {
)}
</div>
<>
<p className={'block font-bold mb-2'}>Annotation</p>
<CardBox
className='mb-6 border border-gray-300 rounded overflow-hidden'
hasTable
>
<div className='overflow-x-auto'>
<table>
<thead>
<tr>
<th>Text</th>
<th>Color</th>
</tr>
</thead>
<tbody>
{reports.Annotation &&
Array.isArray(reports.Annotation) &&
reports.Annotation.map((item: any) => (
<tr
key={item.id}
onClick={() =>
router.push(
`/annotations/annotations-view/?id=${item.id}`,
)
}
>
<td data-label='text'>{item.text}</td>
<td data-label='color'>{item.color}</td>
</tr>
))}
</tbody>
</table>
</div>
{!reports?.Annotation?.length && (
<div className={'text-center py-4'}>No data</div>
)}
</CardBox>
</>
<BaseDivider />
<BaseButton