Updated via schema editor on 2025-08-03 21:05

This commit is contained in:
Flatlogic Bot 2025-08-03 21:06:02 +00:00
parent 6712d9401a
commit ab6c05f494
15 changed files with 264 additions and 100 deletions

File diff suppressed because one or more lines are too long

View File

@ -26,6 +26,10 @@ module.exports = class PhotosDBApi {
transaction,
});
await photos.setPhoto_annotation(data.photo_annotation || [], {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.photos.getTableName(),
@ -93,6 +97,10 @@ module.exports = class PhotosDBApi {
);
}
if (data.photo_annotation !== undefined) {
await photos.setPhoto_annotation(data.photo_annotation, { transaction });
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.photos.getTableName(),
@ -176,6 +184,10 @@ module.exports = class PhotosDBApi {
transaction,
});
output.photo_annotation = await photos.getPhoto_annotation({
transaction,
});
return output;
}
@ -218,6 +230,12 @@ module.exports = class PhotosDBApi {
: {},
},
{
model: db.annotations,
as: 'photo_annotation',
required: false,
},
{
model: db.file,
as: 'image',
@ -239,6 +257,38 @@ module.exports = class PhotosDBApi {
};
}
if (filter.photo_annotation) {
const searchTerms = filter.photo_annotation.split('|');
include = [
{
model: db.annotations,
as: 'photo_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,36 @@
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 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 transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
};

View File

@ -28,6 +28,24 @@ module.exports = function (sequelize, DataTypes) {
);
photos.associate = (db) => {
db.photos.belongsToMany(db.annotations, {
as: 'photo_annotation',
foreignKey: {
name: 'photos_photo_annotationId',
},
constraints: false,
through: 'photosPhoto_annotationAnnotations',
});
db.photos.belongsToMany(db.annotations, {
as: 'photo_annotation_filter',
foreignKey: {
name: 'photos_photo_annotationId',
},
constraints: false,
through: 'photosPhoto_annotationAnnotations',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.photos.hasMany(db.annotations, {

View File

@ -25,7 +25,7 @@ const AnnotationsData = [
{
text: 'Point of Interest 2',
color: 'white',
color: 'pink',
// type code here for "relation_one" field
},
@ -33,15 +33,7 @@ const AnnotationsData = [
{
text: 'Point of Interest 3',
color: 'orange',
// type code here for "relation_one" field
},
{
text: 'Point of Interest 4',
color: 'purple',
color: 'red',
// type code here for "relation_one" field
},
@ -71,35 +63,25 @@ 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 = [
{
// type code here for "images" field
// type code here for "relation_one" field
// type code here for "relation_many" field
},
{
// type code here for "images" field
// type code here for "relation_one" field
// type code here for "relation_many" field
},
{
// 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
// type code here for "relation_many" field
},
];
@ -113,9 +95,9 @@ const ReportsData = [
views: 150,
address: 'Hermann von Helmholtz',
address: 'Marcello Malpighi',
phone: 'Francis Galton',
phone: 'Charles Lyell',
// type code here for "images" field
@ -131,9 +113,9 @@ const ReportsData = [
views: 200,
address: 'Albrecht von Haller',
address: 'John Dalton',
phone: 'B. F. Skinner',
phone: 'Johannes Kepler',
// type code here for "images" field
@ -149,27 +131,9 @@ const ReportsData = [
views: 175,
address: 'Paul Dirac',
address: 'George Gaylord Simpson',
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',
phone: 'Edward Teller',
// type code here for "images" field
@ -179,31 +143,25 @@ const ReportsData = [
const ImprovementsData = [
{
improvement_name: 'Anton van Leeuwenhoek',
improvement_name: 'Frederick Gowland Hopkins',
improvement_status: 'Jean Piaget',
},
{
improvement_name: 'Emil Fischer',
improvement_status: 'Charles Darwin',
},
{
improvement_name: 'Max Delbruck',
improvement_name: 'Carl Gauss (Karl Friedrich Gauss)',
improvement_status: 'Louis Victor de Broglie',
},
{
improvement_name: 'Edward O. Wilson',
improvement_status: 'J. Robert Oppenheimer',
},
{
improvement_name: 'Max Planck',
improvement_status: 'John Bardeen',
improvement_status: 'B. F. Skinner',
},
];
const CategoriesData = [{}, {}, {}, {}];
const CategoriesData = [{}, {}, {}];
// Similar logic for "relation_many"
@ -240,17 +198,6 @@ 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"
@ -288,19 +235,10 @@ 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);
}
}
// Similar logic for "relation_many"
async function associateReportWithClient() {
const relatedClient0 = await Clients.findOne({
offset: Math.floor(Math.random() * (await Clients.count())),
@ -334,17 +272,6 @@ 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"
@ -372,6 +299,8 @@ module.exports = {
await associatePhotoWithClient(),
// Similar logic for "relation_many"
await associateReportWithClient(),
// Similar logic for "relation_many"

View File

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

View File

@ -75,6 +75,17 @@ const ListPhotos = ({
{dataFormatter.clientsOneListFormatter(item.client)}
</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>
Photo Annotation
</p>
<p className={'line-clamp-2'}>
{dataFormatter
.annotationsManyListFormatter(item.photo_annotation)
.join(', ')}
</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}

View File

@ -78,6 +78,25 @@ export const loadColumns = async (
params?.value?.id ?? params?.value,
},
{
field: 'photo_annotation',
headerName: 'Photo 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.DESIGN_DIVERSITY;
const design = FooterDesigns.DEFAULT_DESIGN;
return (
<div

View File

@ -39,6 +39,8 @@ const EditPhotos = () => {
image: [],
client: null,
photo_annotation: [],
};
const [initialValues, setInitialValues] = useState(initVals);
@ -118,6 +120,17 @@ const EditPhotos = () => {
></Field>
</FormField>
<FormField label='Photo Annotation' labelFor='photo_annotation'>
<Field
name='photo_annotation'
id='photo_annotation'
component={SelectFieldMany}
options={initialValues.photo_annotation}
itemRef={'annotations'}
showField={'text'}
></Field>
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type='submit' color='info' label='Submit' />

View File

@ -39,6 +39,8 @@ const EditPhotosPage = () => {
image: [],
client: null,
photo_annotation: [],
};
const [initialValues, setInitialValues] = useState(initVals);
@ -116,6 +118,17 @@ const EditPhotosPage = () => {
></Field>
</FormField>
<FormField label='Photo Annotation' labelFor='photo_annotation'>
<Field
name='photo_annotation'
id='photo_annotation'
component={SelectFieldMany}
options={initialValues.photo_annotation}
itemRef={'annotations'}
showField={'text'}
></Field>
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type='submit' color='info' label='Submit' />

View File

@ -28,7 +28,11 @@ const PhotosTablesPage = () => {
const dispatch = useAppDispatch();
const [filters] = useState([{ label: 'Client', title: 'client' }]);
const [filters] = useState([
{ label: 'Client', title: 'client' },
{ label: 'Photo Annotation', title: 'photo_annotation' },
]);
const hasCreatePermission =
currentUser && hasPermission(currentUser, 'CREATE_PHOTOS');

View File

@ -36,6 +36,8 @@ const initialValues = {
image: [],
client: '',
photo_annotation: [],
};
const PhotosNew = () => {
@ -91,6 +93,16 @@ const PhotosNew = () => {
></Field>
</FormField>
<FormField label='Photo Annotation' labelFor='photo_annotation'>
<Field
name='photo_annotation'
id='photo_annotation'
itemRef={'annotations'}
options={[]}
component={SelectFieldMany}
></Field>
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type='submit' color='info' label='Submit' />

View File

@ -28,7 +28,11 @@ const PhotosTablesPage = () => {
const dispatch = useAppDispatch();
const [filters] = useState([{ label: 'Client', title: 'client' }]);
const [filters] = useState([
{ label: 'Client', title: 'client' },
{ label: 'Photo Annotation', title: 'photo_annotation' },
]);
const hasCreatePermission =
currentUser && hasPermission(currentUser, 'CREATE_PHOTOS');

View File

@ -73,6 +73,47 @@ const PhotosView = () => {
<p>{photos?.client?.name ?? 'No data'}</p>
</div>
<>
<p className={'block font-bold mb-2'}>Photo 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>
{photos.photo_annotation &&
Array.isArray(photos.photo_annotation) &&
photos.photo_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>
{!photos?.photo_annotation?.length && (
<div className={'text-center py-4'}>No data</div>
)}
</CardBox>
</>
<>
<p className={'block font-bold mb-2'}>Annotations Photo</p>
<CardBox