mobile-asset-manager
v1.0.0
Published
An Ionic project
Readme
Mobile Asset Manager: Ionic 4 Capacitor app
Doc: https://capacitor.ionicframework.com/docs/
Project setup:
- look into pre-requisites section in this document.
npm i --force
To Run App
ionic build
// For following commands, replace your platform-value (ex: ios) in place of android
2. npx cap add android
3. For icon and splash screen refer https://capacitorjs.com/docs/guides/splash-screens-and-icons
4. npx cap copy android
5. npx cap sync android
6. npx cap open android
- For any build issues or errors, please refer to this document for handling them.
- In case if you faced any new-issue, please do update the document.
- Note: once APK build is successful you can use
ionic cap run android -l --externalfor live-reload
To Change App Version
- iOS : Info.plist(ios/App/App) -> CFBundleShortVersionString
- Android : build.gradle(android/app) -> versionName
Naming Conventions:
private variables are prefixed with
_.// Example: private _privateVariable: string;observables are suffixed with
$.// Example: woDefinitions$: Observable<WorkDefintionInterface>;if styles need to be applied, use class-names, don't attempt to style html-elements, or override styles using predefined-classes
while creating a new component, wrap the element with its 'selector' as a class and apply the styles, so that they are scoped to that page only
<!-- html -->
<div class="dashboard">
<ion-card class="title">
// content
</ion-card>
</div>
<!-- css -->
.dashboard {
.title {
// styles
}
}- ALWAYS unsubscribe the
SubscriptionsinngOnDestroy
TODOs:
- [ ] On
SignOut: clear local-storage, ionic-storage and tables(if any) - [ ] if login-api fails, dismiss the loader try signing in while offline with
BUSINESS_CONFIG.useFrontEgg = false - [ ] should make all interface fields for GET APIs as must, without it being optional interface, otherwise if fields are missing it will not insert in Table
- [ ] make a common component for attachments, that has inputs as attachments, and contains toggle-able options to preview, delete, add attachment
index-9b0d46f4.js:11 [Ionic Warning]: The value provided to ion-datetime is out of bounds.
Min: {"month":6,"day":4,"year":2024,"hour":16,"minute":0}
Max: {"month":1,"day":1,"year":2030,"hour":23,"minute":59}
Value: {"year":2024,"month":5,"day":8,"hour":12,"minute":41,"ampm":"pm"}TODO: styles:
- all components to be tested & displayed for responsiveness, states(hover, focus, active, disabled), and variants(small, medium, large)
- in Figma, we don't have colors for different status of buttons(like: hover, focus, active, disabled)
- need any ripple-effect for buttons? if so, refer 'ion-ripple-effect' in ionic docs
- 'not-available' status for buttons, should be disabled?, and should have a ripple-effect? what is the purpose of it?
- use enums for all component classes, inputs
- use a shared/default margin/padding for all components (like:
margin: 2px 4px;) [checkdefault-margin-for-components] - add validations for 'eam-counter'? with-in the component or in the parent-component?
- there should be a min-width of 3-4 digit space in 'eam-counter' component, so that it doesn't shrink/expand when the value changes
- it should be scaled as well, (transform: scale(1.5), relatively to the parent-component based on
emorrem)
eam-tagto be used for priority as well(using icons)eam-input-field:- create a common style-sheet and import for all type of inputs
- apply state based styles (like: focus, hover, active, disabled) like for borders, background-colors, etc
- the variants: small, medium, large, should be relative, like 1x, 1.5x, 2x, etc (check how to do in css/globally)
- read-only, disabled, active states should be applied
- handle different error-messages, like if age: 'should be less than 18', 'required', etc
- consider accessibility?
- if considering apple-devices, then apple specific icons? and how to test?
Maintaing Style Sheets:
- applying styles:
<!-- Always have a container tag prefixed with `eam-` -->
<div class="eam-container">
<div class="eam-title">
<!-- content -->
</div>
<div class="eam-input">
<!-- input content -->
</div>
</div>/* imports (if any) */
@import 'flexbox.scss';
/* Define any local variables (if any) */
:host {
--eam-title-color: #000;
--eam-title-font-size: 1.5rem;
--input-font-family: var(--font-family-primary);
--input-font-size: var(--input-field-label-font-size);
--input-font-weight: var(--font-weight-regular);
--input-font-color: #000;
}
/* Then apply styles */
.eam-container {
/* Order */
/* 1. local/host variable styles to be overridden/declared */
--eam-title-color: #fff;
/* 2. use includes, extends and other scss methods (if any) */
@include flexbox();
/* 3. apply styles */
.eam-title {
color: var(--eam-title-color);
&:hover {
--eam-title-color: #f00;
}
}
.eam-input {
font-family: var(--input-font-family);
font-size: var(--input-font-size);
font-weight: var(--input-font-weight);
/* pseudo-classes */
&:hover {
--input-font-color: #f00;
}
/* variants */
&.small {
--input-font-size: 1rem;
}
&.large {
--input-font-size: 2rem;
}
/* states */
&.disabled {
--input-font-color: #ccc;
cursor: not-allowed;
}
&.active {
--input-font-color: #000;
}
}
}Info:
[ ] firstValueFrom:-
This function resolves the Promise with the first value emitted by the Observable.
Use Case: Use this when you are only interested in the first emission and want to ignore subsequent values.
[ ]lastValueFrom:-
This function resolves the Promise with the last value emitted by the Observable before it completes.
Use Case: Use this when you want to wait for the Observable to complete and are interested in the final value it emits.
ToastService
-[ ] The ToastService is used to display toast messages in our Ionic application. It provides a method to show a toast with customizable messages, colors, and positions.
[ ] showToast(message: string, color: string = TOAST_COLOR.DANGER, position: ToastPositionOptions = TOAST_POSITION.BOTTOM): Promise
[ ] message (string): The text to be displayed in the toast. This text will be translated using the TranslateConfigService. we should pass the value as
Stringonly[ ] color (string, optional): The color of the toast.
Default is TOAST_COLOR.DANGER. Colors are defined in TOAST_COLOR constants(enums).[ ] position (ToastPositionOptions, optional): The position of the toast on the screen.
Can be 'bottom', 'top', or 'middle'. Default is TOAST_POSITION.BOTTOM.
Usage Example:
this.toastService.showToast('Your message here', 'success', 'top');
In this example, the toast will display a success message at the top of the screen.TODOs for others in dev:
src/app/constants/local-storage.constant.ts- use
TableNames4SQLiteDBfor values
- use
src\app\services\utils\application-cache-handling.service.ts- add
|| []to avoid errors while accidentally spreadingnullorundefined - change the order of api-insertion or identify why values are not loading while doing
ionic-serve
- add
- add:
src/app/services/utils/field-mapping.service.ts - add:
src/constants/field-mapping.constants.ts- automatic mapping of fields from one object to another
handleNewData(handles flag-D, inert-or-replace-into for observables, still relevant?)
https://testnode.propelapps.com/CLD/24B/getAllConditionEvents/" https://testnode.propelapps.com/CLD/24B/getAllWOFailures/300000158802121/300000160936508/" https://testnode.propelapps.com/CLD/24B/getAllFailureAssociations/" https://testnode.propelapps.com/CLD/24B/getAllFailureChains/" https://testnode.propelapps.com/CLD/24B/getAllWOFailuresBatches/1
Changes to be done as part of new DB support:
- check the guides at setup-sqlite.md for official documentation of
@capacitor-community/sqliteand its limitations and other info. - Take a look into
./electron/src/preload.tsadn check thecontextBridge.exposeInMainWorld('CapacitorSQLite' ...)code which is used to expose theCapacitorSQLiteobject to thewindowobject in the Electron app. and need to add any missing methods or properties to theCapacitorSQLiteobject, incase if you'er facing any errors. Few things I've observed:
- can't insert huge value for INTEGER:
- Search for
getWorkOrderIdinwork-order.component.ts
- date object can't be inserted, need to be converted as string
- Search for
addToTransactionHistoryinwork-order.component.tsand see that the date is converted to string - Check if there is a datatype for date, if not, then convert it to string and insert (check docs)
- If the datatype is a number, the default value should be
nullor0, would recommend the usage ofnullover0to avoid any edge-case.
- This would be required when you're saving a new record, and the value is not provided, so it should be
nullinstead of0. You can check thework-order.component.tsfor reference, where insetWorkOrder()method, We're passingorganizationIdasnull.
- you cannot insert
undefinedornullvalues, so you need to check fornullvalues and replace them with''or0ornullbased on the datatype. - check
work-order.page.tson how to implement search-functionality, infinite-scroll, refresh on data-reload/delta-sync, handle route-params Things I couldn't fix or to be looked into: - supposed to use
.run, but using.query(there is other thing as.execute) when doing DML operations(like insert, update, delete). But due to consistent errors, for now, I've used.queryfor all operations. - insertion should be done in one single transaction, but due to errors, I've used multiple transactions for now. (inserting one record at a time)
- test reset-db while using CapSQL DB
- delete db and db-files: DROP TABLE, delete/reset the db file, and then create a new db file
- Need to confirm, but: in activity-screen, user should be able to navigate forward, upon the complete insertion to DB or API success?
- Right now, there seems to be a delay in the insertion to DB or the process, so the user is not able to navigate forward.
- In the provided
insertToTableCapacitormethod, if an error occurs during the insertion of a record within theforloop, the method immediately catches the error, logs it, and returnsfalse. This means that as soon as any single insertion fails, the method will stop executing further insertions and exit. The insertions that occurred before the error will not be rolled back automatically; they will remain in the database. [I believe I've fixed, but confirm again] - I've commented following code, as when org switches: it won't go throught this for MASTER and CONFIG APIs.
// Determine if delta sync or initial sync needs to be handled
if ((isDeltaSync && !JSON.parse(this.globalvariablesProvider.getAllAPISyncedStatus())) ||
(!isDeltaSync && JSON.parse(this.globalvariablesProvider.getAllAPISyncedStatus()) && isMasterOrConfigApi)) {
return { status: true, responsibility: responsibility, msg: 'No Content' };
}TODOs:
- complete pending TODOs(search for:
TODO) - retry for master or config apis
- pull-down refresh in dashboard? (there is already a sync button, necessary?)
- I've removed allFailureCodeWithBatches responsibility as it is not required, when we have FAILURE_CODE responsibility (why??)
- issue: calling/inserting
AllWoFailurestwice workOrderComponent.updateWorkOrder,workOrderComponent.createWorkOrdershould be an event emitters or triggered upon a subject/other sort? why are we using viewChildren ?- make header a component
- In WO List: search-bar : should it be on top always?(like overlay if user scrolls)
- make search-bar a component(helps with debounce, distinctUntilChanged), header(for common changes) [refer figma]
- search input minLength validation? (to reduce triggers)
- csv api data insertion: should happen without the need of converting csv-to-json and then do insertion
- eslint
- in case of success, transaction result: delete that local record in respective table(not sure how it is working now)
- in work-orders filter option should be a modal, so we can dismiss and pass the props for filtering wo-list
- log errors and success(toggleable) details transaction in a file [same as inventory]
- Log certain data based on a flag
- on logout + login => org-page(with default org selected) but no activity screen?
- when application gets killed in activity-screen, and user re-open the application: should direct to activity-screen with only pending APs called(but what about batch?)
Coding Conventions:
Naming Convention:
- Subscription: Use the suffix
Subscription. [searchSubscription] - Observable: Use the suffix
$. [workOrdersUpdate$] - Subject: Use the suffix
Subject$. [searchSubject$] - BehaviorSubject: Use the suffix
Subject$(same as Subject). - if private: prefix with
_. [_searchSubscription] - don't use
forkJoin, butPromise.allorPromise.allSettledwith and for proper error-handling - always use interfaces
- translation
- jsDoc
- guard-clauses
| Field | isChainFlag | isNotChainFlag | | --- | --- | --- | | Failure Code | getAllFailureChains | | | Cause Code | getAllFailureChains | 0 | | Resoultion Code | 1 | 0 | SPRINT 2:
Currently Finished Tiles: WO, ASSET(view-only), METER READING
@Subramanian provided the SQL tables, need to be created in GCP (priority?? and estimations??) needs to finalise on the approval process by functional-team
WORK REQUEST WORK PERMIT
ASSET: [estimations: ??] Need to add POST APIs(create and update API), and show as categorize) a to the existing Asset Tile UI, Flow, TDD
GET_ALL: `SELECT * FROM ${TableNames4SQLiteDB.WORK_ORDER_HEADERS} ${BUSINESS_CONFIG.hideUnassignedWorkOrders ? `WHERE ${conditions.getAssignedToCondition()}` : ''}`,
GET_WITH_LIMIT: `SELECT * FROM ${TableNames4SQLiteDB.WORK_ORDER_HEADERS} ${BUSINESS_CONFIG.hideUnassignedWorkOrders ? `WHERE ${conditions.getAssignedToCondition()}` : ''} LIMIT ${BUSINESS_CONFIG.queryLimitSize} OFFSET ?`,
// implement fuzzy search: means what ever we're displaying in work-order-card, should be searchable
GET_WITH_FILTER_AND_LIMIT: `SELECT * FROM ${TableNames4SQLiteDB.WORK_ORDER_HEADERS} ${BUSINESS_CONFIG.hideUnassignedWorkOrders ? `WHERE ${conditions.getAssignedToCondition()} AND` : 'WHERE'} (WorkOrderNumber LIKE ? OR assetDescription LIKE ? OR assetNumber LIKE ? ORDER BY StartDate Desc LIMIT ${BUSINESS_CONFIG.queryLimitSize} OFFSET ?`,
GET_COUNT: `SELECT COUNT(*) AS count FROM ${TableNames4SQLiteDB.WORK_ORDER_HEADERS} ${BUSINESS_CONFIG.hideUnassignedWorkOrders ? `WHERE ${conditions.getAssignedToCondition()}` : ''}`,
GET_BY_WORK_ORDER_NUMBER: `SELECT * FROM ${TableNames4SQLiteDB.WORK_ORDER_HEADERS} WHERE WorkOrderNumber = ? ${BUSINESS_CONFIG.hideUnassignedWorkOrders ? `AND ${conditions.getAssignedToCondition()}` : ''}`,
GET_BY_WORK_ORDER_ID: `SELECT * FROM ${TableNames4SQLiteDB.WORK_ORDER_HEADERS} WHERE workOrderId = ? ${BUSINESS_CONFIG.hideUnassignedWorkOrders ? `AND ${conditions.getAssignedToCondition()}` : ''}`,MAM-128:
- add 'Attachment' tab
- From
/assetDetailsAPI when "IsAttachmentAvailable" is "Y", then call geAttachements api - Ex: https://testnode.propelapps.com/CLD/23D/getAttachments/ASSET/INJ-MOLD-001/''/''/''/''
Steps:
- create
attachments-cardcomponent:
command: ionic generate component components/attachments-card
should contain
can accept
assetNumber,woNumberas inputdisplay all attachments (file-name, uploaded-time)
should be able to upload new attachments(doc, docx, pdf, png)
preview of the file should be shown
on click of any attachment, open in new tab
should have a
download-iconon click of download-icon, download the file
sample ui: header: | Attachments | add-button |
each-card: | [ Thumbnail ] file-name | preview-icon | download-icon | delete-icon |
add-button: prompts to choose from gallery, camera or file-manager
Date - 6thNov -2024
When the backend team introduces a new field or column, please ensure it is added in three specific locations within the codebase to maintain consistency and functionality:
Why This Approach? Originally, table creation was based entirely on metadata. However, we’ve updated the process to align table creation with both the model interface and metadata fields. This enhancement ensures tables are structured based on the fields present in the interface, maintaining the metadata order for a consistent schema.
Model Interface:
Add the new field in the model interface. This can be done in any order, as the interface primarily defines available fields.
API Mapping Object (Work Order - Transactional API):
Update the mapping object for the specific API. Ensure the field appears in the same order as specified in the metadata, as this preserves a standardized structure across API requests and responses.
Eg :
private async getWorkOrderHeaders(isDeltaSync: boolean): Promise<API_RESPONSE_TYPE> {
const tableName = TableNames4SQLiteDB.WORK_ORDER_HEADERS;
const metadataUrl = getApiUrl('workOrderHeadersMetadata');
const lastSyncDate = await this.apiUtilService.getLastSyncDate(isDeltaSync, tableName);
const listUrl = getApiUrl('workOrderHeaders') + /"${lastSyncDate}";
const workOrderInterfaceObject = workOrderMappingObject; // we need to map the object & should add respective field
return this.fetchApisDataService.getDataFromApi(listUrl, metadataUrl, tableName, isDeltaSync, RESPONSIBILITY.WORK_ORDER_HEADERS, false, false, false, null, workOrderInterfaceObject);
}
Mapping Business Configuration for Table Creation and Data Insertion
In this approach, we use a business configuration value to manage object mapping dynamically. The following steps illustrate how this configuration influences data mapping and table creation:
Using Business Configuration for Data Insertion
When the insertion business configuration value is set to true, we utilize the mapping object to ensure data is returned and stored in a specific sequence. If insertion is false, we revert to the old data mapping approach, directly returning the data without the additional mapping. Mapping Metadata List from Backend API
The metadata list from the backend API is mapped with our custom mapping object. This allows us to create tables using interface keys in Pascal case format.
Here’s an example mapping object for WorkRequestsDepartmentModel:
typescript Copy code export const workRequestDepartObject: { [key in keyof WorkRequestsDepartmentModel]: string } = { department: 'Department', departmentId: 'DepartmentId', departmentDesc: 'DepartmentDesc', }; With this mapping object, table columns will be created using Pascal case(keys).
Data Insertion Process
During data insertion, we map values according to the specified sequence before inserting them into the table. This ensures the data is stored in an ordered and structured format. eg: In work request department service async storeData(db: SQLiteObject, data: any, tableName: string): Promise {
const itemsList: WorkRequestsDepartmentModel[] = this.getDataWithMapping(data, true);
try {
await this.sqliteService.insertToTable(tableName, itemsList, db);
const updatedData: WorkRequestsDepartmentModel[] = await this.getWorkRequestData(); // Updated to expect an array
this.addToWorkRequestsDepartment(updatedData);
// Update local storage
return await this.storage.set(TableNames4SQLiteDB.WORK_REQUESTS_DEPARTMENT, updatedData);
} catch (error) {
console.error(error);
return [];
}}
Data Fetching will be decide based on business config value
private getDataWithMapping(data, isInsertion = false): WorkRequestsDepartmentModel[] { if (!BUSINESS_CONFIG.useSequenceMappingMethod) { return data.map((item: any): WorkRequestsDepartmentModel => ({ department: item.Department, departmentId: item.DepartmentId, departmentDesc: item.DepartmentDesc, })); }
if (BUSINESS_CONFIG.useSequenceMappingMethod && isInsertion) {
// While inserting, we need mapped data
return this.sharedService.getDataMapWithSequence(data, workRequestDepartObject);
}
if (BUSINESS_CONFIG.useSequenceMappingMethod) {
// While fetching, return the data as is
return data;
}}
Tranlsation message by passing dynamic values.
/**
- Retrieves a translated text for the given key and substitutes parameters into the translation string.
- @param {string} key - The translation key to look up in the translation files.
- @param {object} params - An object containing the parameters to substitute into the translation string.
- @returns {Promise<string | unknown>} A promise that resolves to the translated text with parameters substituted,
- or the key itself as a fallback if an error occurs.
- @throws Will log an error to the console if the translation process fails.
*/
async getTranslatedTextWithParams(key: string, params: object): Promise<string | unknown> {
try {
const translatedText = await firstValueFrom(this.translate.instant(key, params));
return translatedText;
} catch (error) {
console.error(
Error translating key "${key}":, error); return key; // Fallback to key } }
EX : { "welcome_message": "Hello, {{name}}! Welcome to {{appName}}.", "error_message": "An error occurred. Please try again." }
Use case : const key = 'welcome_message'; const params = { name: 'John', appName: 'MyApp' };
this.getTranslatedTextWithParams(key, params).then((result) => { console.log(result); // Output: "Hello, John! Welcome to MyApp." });
How It Works: Translation Lookup:
The function calls this.translate.instant to fetch the translated text for the specified key from the localization file. Parameter Substitution:
The placeholders in the translation string (e.g., {{name}}) are replaced by the corresponding values in the params object.
Keystore configuration for Android Release builds
The keystore configuration for Android release builds is as follows:
keyAlias:mam-maintenancestoreFile:mam-maintenance.jksstorePassword:Propel@1234keyPassword:Propel@1234
