Updated via schema editor on 2025-06-19 01:50

This commit is contained in:
Flatlogic Bot 2025-06-19 01:52:05 +00:00
parent 6764c421bf
commit 06ef1c22e8
32 changed files with 1206 additions and 27 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,10 @@ module.exports = class OwnersDBApi {
{ {
id: data.id || undefined, id: data.id || undefined,
lives_on_site: data.lives_on_site || false,
emergency_contact: data.emergency_contact || null,
mailing_address: data.mailing_address || null,
importHash: data.importHash || null, importHash: data.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
@ -22,6 +26,14 @@ module.exports = class OwnersDBApi {
{ transaction }, { transaction },
); );
await owners.setUser_account(data.user_account || null, {
transaction,
});
await owners.setUnit(data.unit || [], {
transaction,
});
return owners; return owners;
} }
@ -33,6 +45,10 @@ module.exports = class OwnersDBApi {
const ownersData = data.map((item, index) => ({ const ownersData = data.map((item, index) => ({
id: item.id || undefined, id: item.id || undefined,
lives_on_site: item.lives_on_site || false,
emergency_contact: item.emergency_contact || null,
mailing_address: item.mailing_address || null,
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
@ -55,10 +71,31 @@ module.exports = class OwnersDBApi {
const updatePayload = {}; const updatePayload = {};
if (data.lives_on_site !== undefined)
updatePayload.lives_on_site = data.lives_on_site;
if (data.emergency_contact !== undefined)
updatePayload.emergency_contact = data.emergency_contact;
if (data.mailing_address !== undefined)
updatePayload.mailing_address = data.mailing_address;
updatePayload.updatedById = currentUser.id; updatePayload.updatedById = currentUser.id;
await owners.update(updatePayload, { transaction }); await owners.update(updatePayload, { transaction });
if (data.user_account !== undefined) {
await owners.setUser_account(
data.user_account,
{ transaction },
);
}
if (data.unit !== undefined) {
await owners.setUnit(data.unit, { transaction });
}
return owners; return owners;
} }
@ -120,6 +157,14 @@ module.exports = class OwnersDBApi {
const output = owners.get({ plain: true }); const output = owners.get({ plain: true });
output.user_account = await owners.getUser_account({
transaction,
});
output.unit = await owners.getUnit({
transaction,
});
return output; return output;
} }
@ -135,7 +180,39 @@ module.exports = class OwnersDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
let include = []; let include = [
{
model: db.users,
as: 'user_account',
where: filter.user_account
? {
[Op.or]: [
{
id: {
[Op.in]: filter.user_account
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
firstName: {
[Op.or]: filter.user_account
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.units,
as: 'unit',
required: false,
},
];
if (filter) { if (filter) {
if (filter.id) { if (filter.id) {
@ -145,6 +222,28 @@ module.exports = class OwnersDBApi {
}; };
} }
if (filter.emergency_contact) {
where = {
...where,
[Op.and]: Utils.ilike(
'owners',
'emergency_contact',
filter.emergency_contact,
),
};
}
if (filter.mailing_address) {
where = {
...where,
[Op.and]: Utils.ilike(
'owners',
'mailing_address',
filter.mailing_address,
),
};
}
if (filter.active !== undefined) { if (filter.active !== undefined) {
where = { where = {
...where, ...where,
@ -152,6 +251,45 @@ module.exports = class OwnersDBApi {
}; };
} }
if (filter.lives_on_site) {
where = {
...where,
lives_on_site: filter.lives_on_site,
};
}
if (filter.unit) {
const searchTerms = filter.unit.split('|');
include = [
{
model: db.units,
as: 'unit_filter',
required: searchTerms.length > 0,
where:
searchTerms.length > 0
? {
[Op.or]: [
{
id: {
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
},
},
{
unit_number: {
[Op.or]: searchTerms.map((term) => ({
[Op.iLike]: `%${term}%`,
})),
},
},
],
}
: undefined,
},
...include,
];
}
if (filter.createdAtRange) { if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange; const [start, end] = filter.createdAtRange;

View File

@ -31,6 +31,10 @@ module.exports = class UnitsDBApi {
transaction, transaction,
}); });
await units.setOwners(data.owners || [], {
transaction,
});
return units; return units;
} }
@ -94,6 +98,10 @@ module.exports = class UnitsDBApi {
); );
} }
if (data.owners !== undefined) {
await units.setOwners(data.owners, { transaction });
}
return units; return units;
} }
@ -169,6 +177,10 @@ module.exports = class UnitsDBApi {
transaction, transaction,
}); });
output.owners = await units.getOwners({
transaction,
});
return output; return output;
} }
@ -210,6 +222,12 @@ module.exports = class UnitsDBApi {
} }
: {}, : {},
}, },
{
model: db.owners,
as: 'owners',
required: false,
},
]; ];
if (filter) { if (filter) {
@ -330,6 +348,38 @@ module.exports = class UnitsDBApi {
}; };
} }
if (filter.owners) {
const searchTerms = filter.owners.split('|');
include = [
{
model: db.owners,
as: 'owners_filter',
required: searchTerms.length > 0,
where:
searchTerms.length > 0
? {
[Op.or]: [
{
id: {
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
},
},
{
id: {
[Op.or]: searchTerms.map((term) => ({
[Op.iLike]: `%${term}%`,
})),
},
},
],
}
: undefined,
},
...include,
];
}
if (filter.createdAtRange) { if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange; const [start, end] = filter.createdAtRange;

View File

@ -283,6 +283,10 @@ module.exports = class UsersDBApi {
transaction, transaction,
}); });
output.owners_user_account = await users.getOwners_user_account({
transaction,
});
output.avatar = await users.getAvatar({ output.avatar = await users.getAvatar({
transaction, transaction,
}); });

View File

@ -0,0 +1,96 @@
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.addColumn(
'owners',
'user_accountId',
{
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
{ transaction },
);
await queryInterface.addColumn(
'owners',
'lives_on_site',
{
type: Sequelize.DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
},
{ transaction },
);
await queryInterface.addColumn(
'owners',
'emergency_contact',
{
type: Sequelize.DataTypes.TEXT,
},
{ transaction },
);
await queryInterface.addColumn(
'owners',
'mailing_address',
{
type: Sequelize.DataTypes.TEXT,
},
{ 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.removeColumn('owners', 'mailing_address', {
transaction,
});
await queryInterface.removeColumn('owners', 'emergency_contact', {
transaction,
});
await queryInterface.removeColumn('owners', 'lives_on_site', {
transaction,
});
await queryInterface.removeColumn('owners', 'user_accountId', {
transaction,
});
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
};

View File

@ -14,6 +14,21 @@ module.exports = function (sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
lives_on_site: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
emergency_contact: {
type: DataTypes.TEXT,
},
mailing_address: {
type: DataTypes.TEXT,
},
importHash: { importHash: {
type: DataTypes.STRING(255), type: DataTypes.STRING(255),
allowNull: true, allowNull: true,
@ -28,10 +43,36 @@ module.exports = function (sequelize, DataTypes) {
); );
owners.associate = (db) => { owners.associate = (db) => {
db.owners.belongsToMany(db.units, {
as: 'unit',
foreignKey: {
name: 'owners_unitId',
},
constraints: false,
through: 'ownersUnitUnits',
});
db.owners.belongsToMany(db.units, {
as: 'unit_filter',
foreignKey: {
name: 'owners_unitId',
},
constraints: false,
through: 'ownersUnitUnits',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop //end loop
db.owners.belongsTo(db.users, {
as: 'user_account',
foreignKey: {
name: 'user_accountId',
},
constraints: false,
});
db.owners.belongsTo(db.users, { db.owners.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });

View File

@ -48,6 +48,24 @@ module.exports = function (sequelize, DataTypes) {
); );
units.associate = (db) => { units.associate = (db) => {
db.units.belongsToMany(db.owners, {
as: 'owners',
foreignKey: {
name: 'units_ownersId',
},
constraints: false,
through: 'unitsOwnersOwners',
});
db.units.belongsToMany(db.owners, {
as: 'owners_filter',
foreignKey: {
name: 'units_ownersId',
},
constraints: false,
through: 'unitsOwnersOwners',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.units.hasMany(db.users, { db.units.hasMany(db.users, {

View File

@ -110,6 +110,14 @@ module.exports = function (sequelize, DataTypes) {
constraints: false, constraints: false,
}); });
db.users.hasMany(db.owners, {
as: 'owners_user_account',
foreignKey: {
name: 'user_accountId',
},
constraints: false,
});
//end loop //end loop
db.users.belongsTo(db.roles, { db.users.belongsTo(db.roles, {

View File

@ -45,6 +45,14 @@ const BudgetsData = [
expenses: 23000, expenses: 23000,
}, },
{
year: 2019,
total_budget: 45000,
expenses: 24000,
},
]; ];
const DocumentsData = [ const DocumentsData = [
@ -79,6 +87,14 @@ const DocumentsData = [
// type code here for "relation_many" field // type code here for "relation_many" field
}, },
{
name: 'Maintenance Schedule',
// type code here for "files" field
// type code here for "relation_many" field
},
]; ];
const MaintenanceRequestsData = [ const MaintenanceRequestsData = [
@ -87,7 +103,7 @@ const MaintenanceRequestsData = [
description: 'Leaking faucet in kitchen', description: 'Leaking faucet in kitchen',
status: 'in_progress', status: 'pending',
request_date: new Date('2023-10-01T10:00:00Z'), request_date: new Date('2023-10-01T10:00:00Z'),
}, },
@ -97,7 +113,7 @@ const MaintenanceRequestsData = [
description: 'Broken window in living room', description: 'Broken window in living room',
status: 'pending', status: 'in_progress',
request_date: new Date('2023-09-25T14:30:00Z'), request_date: new Date('2023-09-25T14:30:00Z'),
}, },
@ -107,7 +123,7 @@ const MaintenanceRequestsData = [
description: 'Heating not working', description: 'Heating not working',
status: 'pending', status: 'in_progress',
request_date: new Date('2023-09-20T09:00:00Z'), request_date: new Date('2023-09-20T09:00:00Z'),
}, },
@ -117,10 +133,20 @@ const MaintenanceRequestsData = [
description: 'Elevator malfunction', description: 'Elevator malfunction',
status: 'pending', status: 'in_progress',
request_date: new Date('2023-10-02T11:15:00Z'), request_date: new Date('2023-10-02T11:15:00Z'),
}, },
{
// type code here for "relation_one" field
description: 'Paint peeling in hallway',
status: 'completed',
request_date: new Date('2023-09-28T13:45:00Z'),
},
]; ];
const NoticesData = [ const NoticesData = [
@ -163,6 +189,16 @@ const NoticesData = [
sent_date: new Date('2023-09-26T11:00:00Z'), sent_date: new Date('2023-09-26T11:00:00Z'),
}, },
{
// type code here for "relation_many" field
title: 'Holiday Party',
content: 'Join us for the holiday party on December 20th.',
sent_date: new Date('2023-09-25T12:00:00Z'),
},
]; ];
const UnitsData = [ const UnitsData = [
@ -175,9 +211,11 @@ const UnitsData = [
unit_factor: 1, unit_factor: 1,
cond_fee: 52.98, cond_fee: 40.89,
parking_stall: 7, parking_stall: 5,
// type code here for "relation_many" field
}, },
{ {
@ -187,11 +225,13 @@ const UnitsData = [
balance: 0, balance: 0,
unit_factor: 1, unit_factor: 4,
cond_fee: 98.25, cond_fee: 27.38,
parking_stall: 9, parking_stall: 1,
// type code here for "relation_many" field
}, },
{ {
@ -201,11 +241,13 @@ const UnitsData = [
balance: 150, balance: 150,
unit_factor: 5, unit_factor: 3,
cond_fee: 59.63, cond_fee: 47.22,
parking_stall: 8, parking_stall: 9,
// type code here for "relation_many" field
}, },
{ {
@ -215,15 +257,93 @@ const UnitsData = [
balance: 0, balance: 0,
unit_factor: 7, unit_factor: 8,
cond_fee: 32.97, cond_fee: 53.42,
parking_stall: 4, parking_stall: 1,
// type code here for "relation_many" field
},
{
unit_number: '105',
// type code here for "relation_one" field
balance: 300,
unit_factor: 6,
cond_fee: 18.67,
parking_stall: 2,
// type code here for "relation_many" field
}, },
]; ];
const OwnersData = [{}, {}, {}, {}]; const OwnersData = [
{
// type code here for "relation_one" field
lives_on_site: false,
emergency_contact: 'Leonard Euler',
mailing_address: 'Jonas Salk',
// type code here for "relation_many" field
},
{
// type code here for "relation_one" field
lives_on_site: true,
emergency_contact: 'Enrico Fermi',
mailing_address: 'Paul Dirac',
// type code here for "relation_many" field
},
{
// type code here for "relation_one" field
lives_on_site: false,
emergency_contact: 'Claude Bernard',
mailing_address: 'Frederick Sanger',
// type code here for "relation_many" field
},
{
// type code here for "relation_one" field
lives_on_site: true,
emergency_contact: 'Edward O. Wilson',
mailing_address: 'J. Robert Oppenheimer',
// type code here for "relation_many" field
},
{
// type code here for "relation_one" field
lives_on_site: true,
emergency_contact: 'Joseph J. Thomson',
mailing_address: 'Michael Faraday',
// type code here for "relation_many" field
},
];
// Similar logic for "relation_many" // Similar logic for "relation_many"
@ -271,6 +391,17 @@ async function associateUserWithUnit() {
if (User3?.setUnit) { if (User3?.setUnit) {
await User3.setUnit(relatedUnit3); await User3.setUnit(relatedUnit3);
} }
const relatedUnit4 = await Units.findOne({
offset: Math.floor(Math.random() * (await Units.count())),
});
const User4 = await Users.findOne({
order: [['id', 'ASC']],
offset: 4,
});
if (User4?.setUnit) {
await User4.setUnit(relatedUnit4);
}
} }
// Similar logic for "relation_many" // Similar logic for "relation_many"
@ -319,6 +450,17 @@ async function associateMaintenanceRequestWithUnit() {
if (MaintenanceRequest3?.setUnit) { if (MaintenanceRequest3?.setUnit) {
await MaintenanceRequest3.setUnit(relatedUnit3); await MaintenanceRequest3.setUnit(relatedUnit3);
} }
const relatedUnit4 = await Units.findOne({
offset: Math.floor(Math.random() * (await Units.count())),
});
const MaintenanceRequest4 = await MaintenanceRequests.findOne({
order: [['id', 'ASC']],
offset: 4,
});
if (MaintenanceRequest4?.setUnit) {
await MaintenanceRequest4.setUnit(relatedUnit4);
}
} }
// Similar logic for "relation_many" // Similar logic for "relation_many"
@ -367,7 +509,79 @@ async function associateUnitWithOwner() {
if (Unit3?.setOwner) { if (Unit3?.setOwner) {
await Unit3.setOwner(relatedOwner3); await Unit3.setOwner(relatedOwner3);
} }
const relatedOwner4 = await Users.findOne({
offset: Math.floor(Math.random() * (await Users.count())),
});
const Unit4 = await Units.findOne({
order: [['id', 'ASC']],
offset: 4,
});
if (Unit4?.setOwner) {
await Unit4.setOwner(relatedOwner4);
} }
}
// Similar logic for "relation_many"
async function associateOwnerWithUser_account() {
const relatedUser_account0 = await Users.findOne({
offset: Math.floor(Math.random() * (await Users.count())),
});
const Owner0 = await Owners.findOne({
order: [['id', 'ASC']],
offset: 0,
});
if (Owner0?.setUser_account) {
await Owner0.setUser_account(relatedUser_account0);
}
const relatedUser_account1 = await Users.findOne({
offset: Math.floor(Math.random() * (await Users.count())),
});
const Owner1 = await Owners.findOne({
order: [['id', 'ASC']],
offset: 1,
});
if (Owner1?.setUser_account) {
await Owner1.setUser_account(relatedUser_account1);
}
const relatedUser_account2 = await Users.findOne({
offset: Math.floor(Math.random() * (await Users.count())),
});
const Owner2 = await Owners.findOne({
order: [['id', 'ASC']],
offset: 2,
});
if (Owner2?.setUser_account) {
await Owner2.setUser_account(relatedUser_account2);
}
const relatedUser_account3 = await Users.findOne({
offset: Math.floor(Math.random() * (await Users.count())),
});
const Owner3 = await Owners.findOne({
order: [['id', 'ASC']],
offset: 3,
});
if (Owner3?.setUser_account) {
await Owner3.setUser_account(relatedUser_account3);
}
const relatedUser_account4 = await Users.findOne({
offset: Math.floor(Math.random() * (await Users.count())),
});
const Owner4 = await Owners.findOne({
order: [['id', 'ASC']],
offset: 4,
});
if (Owner4?.setUser_account) {
await Owner4.setUser_account(relatedUser_account4);
}
}
// Similar logic for "relation_many"
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
@ -395,6 +609,12 @@ module.exports = {
// Similar logic for "relation_many" // Similar logic for "relation_many"
await associateUnitWithOwner(), await associateUnitWithOwner(),
// Similar logic for "relation_many"
await associateOwnerWithUser_account(),
// Similar logic for "relation_many"
]); ]);
}, },

View File

@ -20,6 +20,13 @@ router.use(checkCrudPermissions('owners'));
* type: object * type: object
* properties: * properties:
* emergency_contact:
* type: string
* default: emergency_contact
* mailing_address:
* type: string
* default: mailing_address
*/ */
/** /**
@ -295,7 +302,7 @@ router.get(
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await OwnersDBApi.findAll(req.query, { currentUser }); const payload = await OwnersDBApi.findAll(req.query, { currentUser });
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id']; const fields = ['id', 'emergency_contact', 'mailing_address'];
const opts = { fields }; const opts = { fields };
try { try {
const csv = parse(payload.rows, opts); const csv = parse(payload.rows, opts);

View File

@ -50,6 +50,8 @@ module.exports = class SearchService {
notices: ['title', 'content'], notices: ['title', 'content'],
units: ['unit_number'], units: ['unit_number'],
owners: ['emergency_contact', 'mailing_address'],
}; };
const columnsInt = { const columnsInt = {
budgets: ['year', 'total_budget', 'expenses'], budgets: ['year', 'total_budget', 'expenses'],

View File

@ -75,7 +75,62 @@ const CardOwners = ({
/> />
</div> </div>
</div> </div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'></dl> <dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
User Account
</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{dataFormatter.usersOneListFormatter(item.user_account)}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
Lives On Site
</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{dataFormatter.booleanFormatter(item.lives_on_site)}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
Emergency Contact
</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{item.emergency_contact}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>
Mailing Address
</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{item.mailing_address}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Unit</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{dataFormatter
.unitsManyListFormatter(item.unit)
.join(', ')}
</div>
</dd>
</div>
</dl>
</li> </li>
))} ))}
{!loading && owners.length === 0 && ( {!loading && owners.length === 0 && (

View File

@ -50,7 +50,46 @@ const ListOwners = ({
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
></Link> >
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>User Account</p>
<p className={'line-clamp-2'}>
{dataFormatter.usersOneListFormatter(item.user_account)}
</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>
Lives On Site
</p>
<p className={'line-clamp-2'}>
{dataFormatter.booleanFormatter(item.lives_on_site)}
</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>
Emergency Contact
</p>
<p className={'line-clamp-2'}>{item.emergency_contact}</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>
Mailing Address
</p>
<p className={'line-clamp-2'}>{item.mailing_address}</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Unit</p>
<p className={'line-clamp-2'}>
{dataFormatter
.unitsManyListFormatter(item.unit)
.join(', ')}
</p>
</div>
</Link>
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}

View File

@ -20,6 +20,8 @@ import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'; import dataFormatter from '../../helpers/dataFormatter';
import { dataGridStyles } from '../../styles'; import { dataGridStyles } from '../../styles';
import CardOwners from './CardOwners';
const perPage = 10; const perPage = 10;
const TableSampleOwners = ({ const TableSampleOwners = ({
@ -461,7 +463,18 @@ const TableSampleOwners = ({
<p>Are you sure you want to delete this item?</p> <p>Are you sure you want to delete this item?</p>
</CardBoxModal> </CardBoxModal>
{dataGrid} {owners && Array.isArray(owners) && !showGrid && (
<CardOwners
owners={owners}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 && {selectedRows.length > 0 &&
createPortal( createPortal(

View File

@ -38,6 +38,83 @@ export const loadColumns = async (
const hasUpdatePermission = hasPermission(user, 'UPDATE_OWNERS'); const hasUpdatePermission = hasPermission(user, 'UPDATE_OWNERS');
return [ return [
{
field: 'user_account',
headerName: 'User Account',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
sortable: false,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('users'),
valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value,
},
{
field: 'lives_on_site',
headerName: 'Lives On Site',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'boolean',
},
{
field: 'emergency_contact',
headerName: 'Emergency Contact',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'mailing_address',
headerName: 'Mailing Address',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'unit',
headerName: 'Unit',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: false,
sortable: false,
type: 'singleSelect',
valueFormatter: ({ value }) =>
dataFormatter.unitsManyListFormatter(value).join(', '),
renderEditCell: (params) => (
<DataGridMultiSelect {...params} entityName={'units'} />
),
},
{ {
field: 'actions', field: 'actions',
type: 'actions', type: 'actions',

View File

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

View File

@ -84,6 +84,15 @@ const ListUnits = ({
</p> </p>
<p className={'line-clamp-2'}>{item.parking_stall}</p> <p className={'line-clamp-2'}>{item.parking_stall}</p>
</div> </div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Owners</p>
<p className={'line-clamp-2'}>
{dataFormatter
.ownersManyListFormatter(item.owners)
.join(', ')}
</p>
</div>
</Link> </Link>
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}

View File

@ -126,6 +126,25 @@ export const loadColumns = async (
type: 'number', type: 'number',
}, },
{
field: 'owners',
headerName: 'Owners',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: false,
sortable: false,
type: 'singleSelect',
valueFormatter: ({ value }) =>
dataFormatter.ownersManyListFormatter(value).join(', '),
renderEditCell: (params) => (
<DataGridMultiSelect {...params} entityName={'owners'} />
),
},
{ {
field: 'actions', field: 'actions',
type: 'actions', type: 'actions',

View File

@ -114,4 +114,23 @@ export default {
if (!val) return ''; if (!val) return '';
return { label: val.name, id: val.id }; return { label: val.name, id: val.id };
}, },
ownersManyListFormatter(val) {
if (!val || !val.length) return [];
return val.map((item) => item.id);
},
ownersOneListFormatter(val) {
if (!val) return '';
return val.id;
},
ownersManyListFormatterEdit(val) {
if (!val || !val.length) return [];
return val.map((item) => {
return { id: item.id, label: item.id };
});
},
ownersOneListFormatterEdit(val) {
if (!val) return '';
return { label: val.id, id: val.id };
},
}; };

View File

@ -35,7 +35,15 @@ import ImageField from '../../components/ImageField';
const EditOwners = () => { const EditOwners = () => {
const router = useRouter(); const router = useRouter();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const initVals = {}; const initVals = {
lives_on_site: false,
emergency_contact: '',
mailing_address: '',
unit: [],
};
const [initialValues, setInitialValues] = useState(initVals); const [initialValues, setInitialValues] = useState(initVals);
const { owners } = useAppSelector((state) => state.owners); const { owners } = useAppSelector((state) => state.owners);
@ -87,6 +95,40 @@ const EditOwners = () => {
onSubmit={(values) => handleSubmit(values)} onSubmit={(values) => handleSubmit(values)}
> >
<Form> <Form>
<FormField label='Lives On Site' labelFor='lives_on_site'>
<Field
name='lives_on_site'
id='lives_on_site'
component={SwitchField}
></Field>
</FormField>
<FormField label='Emergency Contact'>
<Field
name='emergency_contact'
placeholder='Emergency Contact'
/>
</FormField>
<FormField label='Mailing Address' hasTextareaHeight>
<Field
name='mailing_address'
id='mailing_address'
component={RichTextField}
></Field>
</FormField>
<FormField label='Unit' labelFor='unit'>
<Field
name='unit'
id='unit'
component={SelectFieldMany}
options={initialValues.unit}
itemRef={'units'}
showField={'unit_number'}
></Field>
</FormField>
<BaseDivider /> <BaseDivider />
<BaseButtons> <BaseButtons>
<BaseButton type='submit' color='info' label='Submit' /> <BaseButton type='submit' color='info' label='Submit' />

View File

@ -35,7 +35,15 @@ import ImageField from '../../components/ImageField';
const EditOwnersPage = () => { const EditOwnersPage = () => {
const router = useRouter(); const router = useRouter();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const initVals = {}; const initVals = {
lives_on_site: false,
emergency_contact: '',
mailing_address: '',
unit: [],
};
const [initialValues, setInitialValues] = useState(initVals); const [initialValues, setInitialValues] = useState(initVals);
const { owners } = useAppSelector((state) => state.owners); const { owners } = useAppSelector((state) => state.owners);
@ -85,6 +93,40 @@ const EditOwnersPage = () => {
onSubmit={(values) => handleSubmit(values)} onSubmit={(values) => handleSubmit(values)}
> >
<Form> <Form>
<FormField label='Lives On Site' labelFor='lives_on_site'>
<Field
name='lives_on_site'
id='lives_on_site'
component={SwitchField}
></Field>
</FormField>
<FormField label='Emergency Contact'>
<Field
name='emergency_contact'
placeholder='Emergency Contact'
/>
</FormField>
<FormField label='Mailing Address' hasTextareaHeight>
<Field
name='mailing_address'
id='mailing_address'
component={RichTextField}
></Field>
</FormField>
<FormField label='Unit' labelFor='unit'>
<Field
name='unit'
id='unit'
component={SelectFieldMany}
options={initialValues.unit}
itemRef={'units'}
showField={'unit_number'}
></Field>
</FormField>
<BaseDivider /> <BaseDivider />
<BaseButtons> <BaseButtons>
<BaseButton type='submit' color='info' label='Submit' /> <BaseButton type='submit' color='info' label='Submit' />

View File

@ -28,7 +28,14 @@ const OwnersTablesPage = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [filters] = useState([]); const [filters] = useState([
{ label: 'Emergency Contact', title: 'emergency_contact' },
{ label: 'Mailing Address', title: 'mailing_address' },
{ label: 'User Account', title: 'user_account' },
{ label: 'Unit', title: 'unit' },
]);
const hasCreatePermission = const hasCreatePermission =
currentUser && hasPermission(currentUser, 'CREATE_OWNERS'); currentUser && hasPermission(currentUser, 'CREATE_OWNERS');
@ -121,6 +128,10 @@ const OwnersTablesPage = () => {
<div className='md:inline-flex items-center ms-auto'> <div className='md:inline-flex items-center ms-auto'>
<div id='delete-rows-button'></div> <div id='delete-rows-button'></div>
</div> </div>
<div className='md:inline-flex items-center ms-auto'>
<Link href={'/owners/owners-table'}>Switch to Table</Link>
</div>
</CardBox> </CardBox>
<CardBox className='mb-6' hasTable> <CardBox className='mb-6' hasTable>

View File

@ -32,7 +32,15 @@ import { useAppDispatch } from '../../stores/hooks';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import moment from 'moment'; import moment from 'moment';
const initialValues = {}; const initialValues = {
lives_on_site: false,
emergency_contact: '',
mailing_address: '',
unit: [],
};
const OwnersNew = () => { const OwnersNew = () => {
const router = useRouter(); const router = useRouter();
@ -61,6 +69,39 @@ const OwnersNew = () => {
onSubmit={(values) => handleSubmit(values)} onSubmit={(values) => handleSubmit(values)}
> >
<Form> <Form>
<FormField label='Lives On Site' labelFor='lives_on_site'>
<Field
name='lives_on_site'
id='lives_on_site'
component={SwitchField}
></Field>
</FormField>
<FormField label='Emergency Contact'>
<Field
name='emergency_contact'
placeholder='Emergency Contact'
/>
</FormField>
<FormField label='Mailing Address' hasTextareaHeight>
<Field
name='mailing_address'
id='mailing_address'
component={RichTextField}
></Field>
</FormField>
<FormField label='Unit' labelFor='unit'>
<Field
name='unit'
id='unit'
itemRef={'units'}
options={[]}
component={SelectFieldMany}
></Field>
</FormField>
<BaseDivider /> <BaseDivider />
<BaseButtons> <BaseButtons>
<BaseButton type='submit' color='info' label='Submit' /> <BaseButton type='submit' color='info' label='Submit' />

View File

@ -28,7 +28,14 @@ const OwnersTablesPage = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [filters] = useState([]); const [filters] = useState([
{ label: 'Emergency Contact', title: 'emergency_contact' },
{ label: 'Mailing Address', title: 'mailing_address' },
{ label: 'User Account', title: 'user_account' },
{ label: 'Unit', title: 'unit' },
]);
const hasCreatePermission = const hasCreatePermission =
currentUser && hasPermission(currentUser, 'CREATE_OWNERS'); currentUser && hasPermission(currentUser, 'CREATE_OWNERS');
@ -120,6 +127,10 @@ const OwnersTablesPage = () => {
<div className='md:inline-flex items-center ms-auto'> <div className='md:inline-flex items-center ms-auto'>
<div id='delete-rows-button'></div> <div id='delete-rows-button'></div>
<Link href={'/owners/owners-list'}>
Back to <span className='capitalize'>card</span>
</Link>
</div> </div>
</CardBox> </CardBox>
<CardBox className='mb-6' hasTable> <CardBox className='mb-6' hasTable>

View File

@ -54,6 +54,81 @@ const OwnersView = () => {
/> />
</SectionTitleLineWithButton> </SectionTitleLineWithButton>
<CardBox> <CardBox>
<FormField label='Lives On Site'>
<SwitchField
field={{ name: 'lives_on_site', value: owners?.lives_on_site }}
form={{ setFieldValue: () => null }}
disabled
/>
</FormField>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>Emergency Contact</p>
<p>{owners?.emergency_contact}</p>
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>Mailing Address</p>
{owners.mailing_address ? (
<p dangerouslySetInnerHTML={{ __html: owners.mailing_address }} />
) : (
<p>No data</p>
)}
</div>
<>
<p className={'block font-bold mb-2'}>Unit</p>
<CardBox
className='mb-6 border border-gray-300 rounded overflow-hidden'
hasTable
>
<div className='overflow-x-auto'>
<table>
<thead>
<tr>
<th>UnitNumber</th>
<th>Balance</th>
<th>Unit Factor</th>
<th>Cond Fee</th>
<th>Parking Stall</th>
</tr>
</thead>
<tbody>
{owners.unit &&
Array.isArray(owners.unit) &&
owners.unit.map((item: any) => (
<tr
key={item.id}
onClick={() =>
router.push(`/units/units-view/?id=${item.id}`)
}
>
<td data-label='unit_number'>{item.unit_number}</td>
<td data-label='balance'>{item.balance}</td>
<td data-label='unit_factor'>{item.unit_factor}</td>
<td data-label='cond_fee'>{item.cond_fee}</td>
<td data-label='parking_stall'>
{item.parking_stall}
</td>
</tr>
))}
</tbody>
</table>
</div>
{!owners?.unit?.length && (
<div className={'text-center py-4'}>No data</div>
)}
</CardBox>
</>
<BaseDivider /> <BaseDivider />
<BaseButton <BaseButton

View File

@ -47,6 +47,8 @@ const EditUnits = () => {
cond_fee: '', cond_fee: '',
parking_stall: '', parking_stall: '',
owners: [],
}; };
const [initialValues, setInitialValues] = useState(initVals); const [initialValues, setInitialValues] = useState(initVals);
@ -138,6 +140,17 @@ const EditUnits = () => {
/> />
</FormField> </FormField>
<FormField label='Owners' labelFor='owners'>
<Field
name='owners'
id='owners'
component={SelectFieldMany}
options={initialValues.owners}
itemRef={'owners'}
showField={'id'}
></Field>
</FormField>
<BaseDivider /> <BaseDivider />
<BaseButtons> <BaseButtons>
<BaseButton type='submit' color='info' label='Submit' /> <BaseButton type='submit' color='info' label='Submit' />

View File

@ -47,6 +47,8 @@ const EditUnitsPage = () => {
cond_fee: '', cond_fee: '',
parking_stall: '', parking_stall: '',
owners: [],
}; };
const [initialValues, setInitialValues] = useState(initVals); const [initialValues, setInitialValues] = useState(initVals);
@ -136,6 +138,17 @@ const EditUnitsPage = () => {
/> />
</FormField> </FormField>
<FormField label='Owners' labelFor='owners'>
<Field
name='owners'
id='owners'
component={SelectFieldMany}
options={initialValues.owners}
itemRef={'owners'}
showField={'id'}
></Field>
</FormField>
<BaseDivider /> <BaseDivider />
<BaseButtons> <BaseButtons>
<BaseButton type='submit' color='info' label='Submit' /> <BaseButton type='submit' color='info' label='Submit' />

View File

@ -36,6 +36,8 @@ const UnitsTablesPage = () => {
{ label: 'Cond Fee', title: 'cond_fee', number: 'true' }, { label: 'Cond Fee', title: 'cond_fee', number: 'true' },
{ label: 'Owner', title: 'owner' }, { label: 'Owner', title: 'owner' },
{ label: 'Owners', title: 'owners' },
]); ]);
const hasCreatePermission = const hasCreatePermission =

View File

@ -44,6 +44,8 @@ const initialValues = {
cond_fee: '', cond_fee: '',
parking_stall: '', parking_stall: '',
owners: [],
}; };
const UnitsNew = () => { const UnitsNew = () => {
@ -111,6 +113,16 @@ const UnitsNew = () => {
/> />
</FormField> </FormField>
<FormField label='Owners' labelFor='owners'>
<Field
name='owners'
id='owners'
itemRef={'owners'}
options={[]}
component={SelectFieldMany}
></Field>
</FormField>
<BaseDivider /> <BaseDivider />
<BaseButtons> <BaseButtons>
<BaseButton type='submit' color='info' label='Submit' /> <BaseButton type='submit' color='info' label='Submit' />

View File

@ -36,6 +36,8 @@ const UnitsTablesPage = () => {
{ label: 'Cond Fee', title: 'cond_fee', number: 'true' }, { label: 'Cond Fee', title: 'cond_fee', number: 'true' },
{ label: 'Owner', title: 'owner' }, { label: 'Owner', title: 'owner' },
{ label: 'Owners', title: 'owners' },
]); ]);
const hasCreatePermission = const hasCreatePermission =

View File

@ -85,6 +85,49 @@ const UnitsView = () => {
<p>{units?.parking_stall || 'No data'}</p> <p>{units?.parking_stall || 'No data'}</p>
</div> </div>
<>
<p className={'block font-bold mb-2'}>Owners</p>
<CardBox
className='mb-6 border border-gray-300 rounded overflow-hidden'
hasTable
>
<div className='overflow-x-auto'>
<table>
<thead>
<tr>
<th>Lives On Site</th>
<th>Emergency Contact</th>
</tr>
</thead>
<tbody>
{units.owners &&
Array.isArray(units.owners) &&
units.owners.map((item: any) => (
<tr
key={item.id}
onClick={() =>
router.push(`/owners/owners-view/?id=${item.id}`)
}
>
<td data-label='lives_on_site'>
{dataFormatter.booleanFormatter(item.lives_on_site)}
</td>
<td data-label='emergency_contact'>
{item.emergency_contact}
</td>
</tr>
))}
</tbody>
</table>
</div>
{!units?.owners?.length && (
<div className={'text-center py-4'}>No data</div>
)}
</CardBox>
</>
<> <>
<p className={'block font-bold mb-2'}>Users Unit</p> <p className={'block font-bold mb-2'}>Users Unit</p>
<CardBox <CardBox

View File

@ -197,6 +197,49 @@ const UsersView = () => {
</CardBox> </CardBox>
</> </>
<>
<p className={'block font-bold mb-2'}>Owners User Account</p>
<CardBox
className='mb-6 border border-gray-300 rounded overflow-hidden'
hasTable
>
<div className='overflow-x-auto'>
<table>
<thead>
<tr>
<th>Lives On Site</th>
<th>Emergency Contact</th>
</tr>
</thead>
<tbody>
{users.owners_user_account &&
Array.isArray(users.owners_user_account) &&
users.owners_user_account.map((item: any) => (
<tr
key={item.id}
onClick={() =>
router.push(`/owners/owners-view/?id=${item.id}`)
}
>
<td data-label='lives_on_site'>
{dataFormatter.booleanFormatter(item.lives_on_site)}
</td>
<td data-label='emergency_contact'>
{item.emergency_contact}
</td>
</tr>
))}
</tbody>
</table>
</div>
{!users?.owners_user_account?.length && (
<div className={'text-center py-4'}>No data</div>
)}
</CardBox>
</>
<BaseDivider /> <BaseDivider />
<BaseButton <BaseButton