Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
1980cb2
ag grid example
perryr16 Mar 11, 2025
c7d4242
dev
perryr16 Mar 12, 2025
f61a69b
cycles, profiles, pagination functional
perryr16 Mar 13, 2025
4c2c9df
dev
perryr16 Mar 13, 2025
d218294
grid controls component
perryr16 Mar 14, 2025
42dbdcb
grid componenet functional
perryr16 Mar 18, 2025
1dba0be
actions dropdown semi functional
perryr16 Mar 18, 2025
c07457a
more actions modal
perryr16 Mar 19, 2025
7d40de1
select all fxn
perryr16 Mar 19, 2025
215e764
basic filtering
perryr16 Mar 19, 2025
23d0e3c
cleanup
perryr16 Mar 20, 2025
c4af1ef
dev
perryr16 Mar 20, 2025
c2d6dff
actions component
perryr16 Mar 20, 2025
a30ad9b
tiger stripe
perryr16 Mar 20, 2025
bb7fb52
cell menu dev
perryr16 Mar 21, 2025
31954d5
sorts and filters to request data
perryr16 Mar 26, 2025
4845696
merge main
perryr16 Mar 26, 2025
848b175
filter and sort chips
perryr16 Mar 28, 2025
f2a601a
build filter display text
perryr16 Mar 28, 2025
34cc037
user settings applied on reload
perryr16 Apr 1, 2025
f42a06f
lint
perryr16 Apr 1, 2025
8209140
cleanup
perryr16 Apr 1, 2025
ed9ebb8
remove outdated endpoint
perryr16 Apr 1, 2025
673ba10
Merge branch 'main' into feat/inventory-list-part1
perryr16 Apr 1, 2025
a6f9c33
lint
perryr16 Apr 1, 2025
a54b4ae
lint
perryr16 Apr 1, 2025
d0c3626
material icons package
perryr16 Apr 1, 2025
21f1a7f
handle tax vs prop filter user settings
perryr16 Apr 1, 2025
a9f9e5c
detail routes
perryr16 Apr 2, 2025
54b825a
dev
perryr16 Apr 4, 2025
0a66b78
header
perryr16 Apr 7, 2025
fb98cfe
ali as ag grid
perryr16 Apr 9, 2025
280a910
merge main
perryr16 Apr 10, 2025
0d7d2f7
detail components
perryr16 Apr 10, 2025
c105903
reorg files
perryr16 Apr 11, 2025
30b1be6
dc icon and form dev
perryr16 Apr 11, 2025
78a864e
lint
perryr16 Apr 14, 2025
2a2ef6b
Merge branch 'main' into feat/inventory-detail-part1
perryr16 Apr 14, 2025
704855a
update p/tl
perryr16 Apr 14, 2025
fd349ba
merge redirect and sort guard
perryr16 Apr 15, 2025
fcde3e2
map dev
perryr16 Apr 15, 2025
0bb790e
merge
perryr16 Apr 15, 2025
d44ab6d
merge
perryr16 Apr 15, 2025
77f9908
map functional
perryr16 Apr 15, 2025
ef76d1b
typing
perryr16 Apr 17, 2025
b8bb8bc
dev
perryr16 Apr 17, 2025
e9a07e9
Merge branch 'main' into feat/inventory-detail-part1
perryr16 Apr 17, 2025
3eaf708
detail using profiles
perryr16 Apr 17, 2025
7e7027a
dropdown height
perryr16 Apr 17, 2025
215cdcf
extra data edits to detail
perryr16 Apr 17, 2025
ee2db84
show populated cols updates profile
perryr16 Apr 18, 2025
55225ba
create profile on populate with none
perryr16 Apr 18, 2025
cb1b89b
detail document and pairing table functionality
perryr16 Apr 22, 2025
2ceccc3
profile select
perryr16 Apr 22, 2025
c296c7a
scenarios not measures functional
perryr16 Apr 22, 2025
6990e6a
scenario delete semi functional with vite error
perryr16 Apr 22, 2025
3b41e76
styles
perryr16 Apr 23, 2025
4bfb538
prettier
perryr16 Apr 23, 2025
c7cefb6
grid consistency
perryr16 Apr 23, 2025
1f83746
clean up
perryr16 Apr 23, 2025
5bd3563
lint
perryr16 Apr 23, 2025
e1df394
toggle nav visibility
perryr16 Apr 23, 2025
8f1ea1b
side nav toggle for columns
perryr16 Apr 24, 2025
decc0fb
prettier
perryr16 Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"lodash-es": "^4.17.21",
"luxon": "^3.5.0",
"material-icons": "^1.13.14",
"ol": "^10.5.0",
"perfect-scrollbar": "^1.5.6",
"pnpm": "^10.6.2",
"rxjs": "~7.8.1",
Expand Down
160 changes: 136 additions & 24 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/@seed/api/column/column.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ export type ColumnsResponse = {
status: string;
columns: Column[];
}

export type GenericColumn = {
[key: string]: unknown;
display_name: string;
column_name: string;
}
4 changes: 3 additions & 1 deletion src/@seed/api/derived-column/derived-column.types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { InventoryDisplayType } from 'app/modules/inventory/inventory.types'

export type DerivedColumn = {
expression: string;
id: number;
inventory_type: 'Property' | 'Tax Lot';
inventory_type: InventoryDisplayType;
name: string;
organization: number;
parameters: Parameter[];
Expand Down
145 changes: 143 additions & 2 deletions src/@seed/api/inventory/inventory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { BehaviorSubject, catchError, map, Subject, takeUntil, tap } from 'rxjs'
import { BehaviorSubject, catchError, map, Subject, takeUntil, tap, throwError } from 'rxjs'
import { OrganizationService } from '@seed/api/organization'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type { DeleteParams, FilterResponse, Profile, ProfileResponse, ProfilesResponse } from 'app/modules/inventory/inventory.types'
import type { DeleteParams, FilterResponse, GenericView, GenericViewsResponse, InventoryDisplayType, InventoryType, NewProfileData, Profile, ProfileResponse, ProfilesResponse, PropertyDocumentExtension, UpdateInventoryResponse, ViewResponse } from 'app/modules/inventory/inventory.types'

@Injectable({ providedIn: 'root' })
export class InventoryService {
Expand Down Expand Up @@ -76,6 +76,27 @@ export class InventoryService {
)
}

updateProfileToShowPopulatedColumns(orgId: number, id: number, cycle_id: number, inventory_type: InventoryDisplayType): Observable<Profile> {
const url = `/api/v3/column_list_profiles/${id}/show_populated/?organization_id=${orgId}`
const data = { cycle_id, inventory_type }
return this._httpClient.put<ProfileResponse>(url, data).pipe(
map(({ data }) => data),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error updating column list profile')
}),
)
}

createColumnListProfile(orgId: number, data: NewProfileData): Observable<Profile> {
const url = `/api/v3/column_list_profiles/?organization_id=${orgId}`
return this._httpClient.post<ProfileResponse>(url, data).pipe(
map(({ data }) => data),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error creating column list profile')
}),
)
}

deletePropertyStates({ orgId, viewIds }: DeleteParams): Observable<object> {
const url = '/api/v3/properties/batch_delete/'
const data = { property_view_ids: viewIds }
Expand All @@ -89,4 +110,124 @@ export class InventoryService {
}),
)
}

/*
* Get PropertyView or TaxLotView
*/
getView(orgId: number, viewId: number, inventoryType: InventoryType): Observable<ViewResponse> {
return inventoryType === 'taxlots' ? this.getTaxLotView(orgId, viewId) : this.getPropertyView(orgId, viewId)
}

getPropertyView(orgId: number, viewId: number): Observable<ViewResponse> {
const url = `/api/v3/properties/${viewId}/`
const params = { organization_id: orgId }
return this._httpClient.get<ViewResponse>(url, { params }).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching property')
}),
)
}

getTaxLotView(orgId: number, viewId: number): Observable<ViewResponse> {
const url = `/api/v3/taxlots/${viewId}/`
const params = { organization_id: orgId }
return this._httpClient.get<ViewResponse>(url, { params }).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching property')
}),
)
}

/*
* Get PropertyViews or TaxLotViews given a Property or Taxlot id
*/
getViews(orgId: number, id: number, inventoryType: InventoryType): Observable<GenericView[]> {
return inventoryType === 'taxlots' ? this.getTaxLotViews(orgId, id) : this.getPropertyViews(orgId, id)
}

getPropertyViews(orgId: number, propertyId: number): Observable<GenericView[]> {
const url = '/api/v3/property_views/'
const params = { organization_id: orgId, property: propertyId }
return this._httpClient.get<GenericViewsResponse>(url, { params }).pipe(
map(({ property_views }) => property_views),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching property')
}),
)
}

getTaxLotViews(orgId: number, taxLotId: number): Observable<GenericView[]> {
const url = '/api/v3/taxlot_views/'
const params = { organization_id: orgId, taxlot: taxLotId }
return this._httpClient.get<GenericViewsResponse>(url, { params }).pipe(
map(({ taxlot_views }) => taxlot_views),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching taxlot')
}),
)
}

/*
* Update a property/taxlot view's state
*/
updateInventory(orgId: number, viewId: number, inventoryType: InventoryType, updatedStateFields: Record<string, unknown>): Observable<UpdateInventoryResponse> {
return inventoryType === 'taxlots'
? this.updateTaxLot(orgId, viewId, updatedStateFields)
: this.updateProperty(orgId, viewId, updatedStateFields)
}

updateProperty(orgId: number, viewId: number, state: Record<string, unknown>): Observable<UpdateInventoryResponse> {
const url = `/api/v3/properties/${viewId}/?organization_id=${orgId}`
return this._httpClient.put<UpdateInventoryResponse>(url, { state }).pipe(
tap((response) => {
this._snackBar.success(`Success! - ${response.match_link_count} Linked. ${response.match_merged_count} Merged`)
}),
catchError((error: HttpErrorResponse) => {
// errors tend to be non human readable
this._snackBar.alert('Error updating property. Check data types and try again')
return throwError(() => error)
}),
)
}

updateTaxLot(orgId: number, viewId: number, state: Record<string, unknown>): Observable<UpdateInventoryResponse> {
const url = `/api/v3/taxlots/${viewId}/?organization_id=${orgId}`
return this._httpClient.put<UpdateInventoryResponse>(url, { state }).pipe(
tap((response) => {
this._snackBar.success(`Success! - ${response.match_link_count} Linked. ${response.match_merged_count} Merged`)
}),
catchError((error: HttpErrorResponse) => {
// errors tend to be non human readable
this._snackBar.alert('Error updating taxlot. Check data types and try again')
return throwError(() => error)
}),
)
}

uploadPropertyDocument(orgId: number, viewId: number, file: File, fileExt: PropertyDocumentExtension): Observable<unknown> {
const url = `/api/v3/properties/${viewId}/upload_inventory_document/?organization_id=${orgId}`
const formData = new FormData()
formData.append('file', file, file.name)
formData.append('file_type', fileExt)
return this._httpClient.put<unknown>(url, formData).pipe(
tap(() => {
this._snackBar.success('Document uploaded successfully')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error Uploading Document')
}),
)
}

deletePropertyDocument(orgId: number, viewId: number, file_id: string): Observable<object> {
const url = `/api/v3/properties/${viewId}/delete_inventory_document/?organization_id=${orgId}&file_id=${file_id}`
return this._httpClient.delete(url).pipe(
tap(() => {
this._snackBar.success('Document deleted successfully')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error deleting Document')
}),
)
}
}
41 changes: 41 additions & 0 deletions src/@seed/api/label/label.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { catchError, map, ReplaySubject, Subject, takeUntil } from 'rxjs'
import { ErrorService } from '@seed/services'
import { naturalSort } from '@seed/utils'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type { InventoryType } from 'app/modules/inventory/inventory.types'
import { UserService } from '../user'
import type { Label } from './label.types'

Expand Down Expand Up @@ -42,6 +43,46 @@ export class LabelService {
)
}

/*
* Get inventory labels for a list of views
*/
getInventoryLabels(orgId: number, viewIds: number[], cycleId: number, inventoryType: InventoryType): Observable<Label[]> {
return inventoryType === 'taxlots'
? this.getTaxLotLabels(orgId, viewIds, cycleId)
: this.getPropertyLabels(orgId, viewIds, cycleId)
}

getPropertyLabels(orgId: number, viewIds: number[], cycleId: number): Observable<Label[]> {
const url = '/api/v3/properties/labels/'
const data = { selected: viewIds }
const params = { organization_id: orgId, cycle_id: cycleId }
return this._httpClient.post<Label[]>(url, data, { params }).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, `Error fetching labels: ${error.message}`)
}),
)
}

getTaxLotLabels(orgId: number, viewIds: number[], cycleId: number): Observable<Label[]> {
const url = '/api/v3/properties/labels/'
const data = { selected: viewIds }
const params = { organization_id: orgId, cycle_id: cycleId }
return this._httpClient.post<Label[]>(url, data, { params }).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, `Error fetching labels: ${error.message}`)
}),
)
}

get(orgId: number, id: number): Observable<Label> {
const url = `/api/v3/labels/${id}/?organization_id=${orgId}`
return this._httpClient.get<Label>(url).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, `Error fetching label: ${error.message}`)
}),
)
}

create(label: Label): Observable<Label> {
const url = `/api/v3/labels/?organization_id=${label.organization_id}`
return this._httpClient.post<Label>(url, { ...label }).pipe(
Expand Down
1 change: 1 addition & 0 deletions src/@seed/api/label/label.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export type Label = {
color: string;
organization_id: number;
show_in_list: boolean;
is_applied?: number[];
}
16 changes: 16 additions & 0 deletions src/@seed/api/organization/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Observable } from 'rxjs'
import { catchError, combineLatest, map, of, ReplaySubject, Subject, switchMap, takeUntil, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type { InventoryType } from 'app/modules/inventory/inventory.types'
import { naturalSort } from '../../utils'
import type { ProgressResponse } from '../progress'
import { UserService } from '../user'
Expand All @@ -18,6 +19,7 @@ import type {
CreateAccessLevelInstanceRequest,
EditAccessLevelInstanceRequest,
EditAccessLevelInstanceResponse,
MatchingCriteriaColumnsResponse,
Organization,
OrganizationResponse,
OrganizationSettings,
Expand Down Expand Up @@ -270,6 +272,20 @@ export class OrganizationService {
)
}

getMatchingCriteriaColumns(orgId: number, inventoryType: InventoryType | null = null): Observable<MatchingCriteriaColumnsResponse | string[]> {
const url = `/api/v3/organizations/${orgId}/matching_criteria_columns/`
return this._httpClient.get<MatchingCriteriaColumnsResponse>(url).pipe(
map((response) => {
if (inventoryType === 'properties') return response.PropertyState
if (inventoryType === 'taxlots') return response.TaxLotState
return response
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching matching columns')
}),
)
}

private _sortAccessLevelInstances(tree: AccessLevelInstance[]): AccessLevelInstance[] {
return tree
.map((instance) => ({
Expand Down
13 changes: 12 additions & 1 deletion src/@seed/api/organization/organization.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export type OrganizationUser = {
number_of_orgs: number;
role: UserRole;
user_id: number;
settings: Record<string, unknown>;
settings: OrganizationUserSettings;
}

export type OrganizationUserSettings = {
Expand All @@ -106,6 +106,7 @@ export type OrganizationUserSettings = {
profile_id?: number;
sorts?: UserSettingsSorts;
filters?: UserSettingsFilters;
profile?: UserSettingsProfiles;
}

type UserSettingsFilters = {
Expand All @@ -118,6 +119,11 @@ type UserSettingsSorts = {
taxlots?: string[];
}

type UserSettingsProfiles = {
detail?: { properties?: number; taxlots?: number };
list?: { properties?: number; taxlots?: number };
}

export type OrganizationUsersResponse = {
users: OrganizationUser[];
status: string;
Expand Down Expand Up @@ -185,3 +191,8 @@ export type UploadAccessLevelInstancesResponse = {
export type StartSavingAccessLevelInstancesRequest = {
filename: string;
}

export type MatchingCriteriaColumnsResponse = {
PropertyState: string[];
TaxLotState: string[];
}
1 change: 1 addition & 0 deletions src/@seed/api/pairing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './pairing.service'
30 changes: 30 additions & 0 deletions src/@seed/api/pairing/pairing.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { catchError, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type { InventoryType } from 'app/modules/inventory/inventory.types'

@Injectable({ providedIn: 'root' })
export class PairingService {
private _httpClient = inject(HttpClient)
private _errorService = inject(ErrorService)
private _snackBar = inject(SnackBarService)

unpairInventory(orgId: number, viewId: number, otherViewId: number, inventoryType: InventoryType): Observable<unknown> {

Check warning on line 16 in src/@seed/api/pairing/pairing.service.ts

View workflow job for this annotation

GitHub Actions / lint_and_build

Unknown word: "unpair"
const url = inventoryType === 'taxlots'
? `/api/v3/taxlots/${viewId}/unpair/?organization_id=${orgId}&property_id=${otherViewId}`

Check warning on line 18 in src/@seed/api/pairing/pairing.service.ts

View workflow job for this annotation

GitHub Actions / lint_and_build

Unknown word: "unpair"
: `/api/v3/properties/${viewId}/unpair/?organization_id=${orgId}&taxlot_id=${otherViewId}`

Check warning on line 19 in src/@seed/api/pairing/pairing.service.ts

View workflow job for this annotation

GitHub Actions / lint_and_build

Unknown word: "unpair"

return this._httpClient.put<unknown>(url, {}).pipe(
tap(() => {
this._snackBar.success('Unpaired successfully')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error unpairing inventory')

Check warning on line 26 in src/@seed/api/pairing/pairing.service.ts

View workflow job for this annotation

GitHub Actions / lint_and_build

Unknown word: "unpairing"
}),
)
}
}
2 changes: 2 additions & 0 deletions src/@seed/api/scenario/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './scenario.types'
export * from './scenario.service'
26 changes: 26 additions & 0 deletions src/@seed/api/scenario/scenario.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { catchError, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'

@Injectable({ providedIn: 'root' })
export class ScenarioService {
private _httpClient = inject(HttpClient)
private _errorService = inject(ErrorService)
private _snackBar = inject(SnackBarService)

deleteScenario(orgId: number, viewId: number, scenarioId: number): Observable<unknown> {
const url = `api/v3/properties/${viewId}/scenarios/${scenarioId}/?organization_id=${orgId}`
return this._httpClient.delete<unknown>(url, {}).pipe(
tap(() => {
this._snackBar.success('Successfully deleted scenario')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error deleting scenario')
}),
)
}
}
Loading