Skip to content

People Tool

The People tool provides a directory of all active employees and an org chart visualization. Data is synced from Productive.io using their People API.

  • People directory — Searchable, sortable table of all active employees with avatar, name, title, email, and manager
  • Person profile page — Full-page profile with six tabs: Details (Contact, Role, Personal, System), Employment (Tenure, Pay, Leave, Financial), Documents, Activity, Notes, and LinkedIn. Navigates via ?id=personId
  • Confidential notes — Timestamped log of confidential notes on person profiles with rich text (Tiptap editor), per-note visibility control (multi-select access levels, executive always included), and tiered access: executives view/add/edit/delete all, heads view/add for subtree, managers view/add for direct reports
  • Org chart — Tree visualization showing the reporting hierarchy based on manager relationships
  • Org chart peek sheet — Simplified quick-peek drawer when clicking a node in the org chart, showing Contact/System info, Activity tab, and a “View Profile” button
  • Xero sync on profiles — Executives can link a person to their Xero employee record (with smart name/email matching) and trigger payroll data sync directly from the profile System card
  • Productive sync — Manual sync button (executive only) pulls active people from Productive and matches them to app users by email
  • User enrichment — Sync updates matched users’ job title and profile picture from Productive
  • TTL-based refresh — Data auto-refreshes in the background after 6 hours
  • LinkedIn Activities — View a person’s recent LinkedIn posts, comments, and reactions (fetched from Scrapin.io, 3 credits per fetch). Managers/executives add a LinkedIn URL to the Contact card, then manually trigger fetches. Cached in DB for free viewing
  • LinkedIn Change Alerts — Subscribe to Scrapin webhooks that detect LinkedIn profile changes (title, company, headline, location, skills). Shows a timeline of detected changes. Requires webhook URL configuration on the Connections page

Toggle between two views using tabs:

  1. Directory (default) — Table with sortable columns (name, title, email) and manager lookup. Clicking a row navigates to the full profile page
  2. Org Chart — Hierarchical tree with collapsible nodes, showing first 2 levels expanded by default. Clicking a node opens a peek sheet with quick info and a link to the full profile
  3. Profile Page — Shown when ?id=personId is present. Tabs are permission-scoped: Employment, Documents, and Activity require manager/head/executive access

When an executive clicks “Sync”:

  1. Fetches all active people from Productive (paginated, max 200 per page)
  2. Upserts into the people table, matched by productive_id
  3. Matches people to app users by email (case-insensitive)
  4. Resolves manager relationships (self-referencing FK)
  5. Updates matched users’ job_title and picture from Productive data
  6. Upserts user_external_ids for the productive provider
  7. Removes people no longer active in Productive
Access LevelViewUpdateManage (Sync)
ExecutiveYesYesYes
HeadYesNoNo
ManagerYesNoNo
LeadYesNoNo
EmployeeYesNoNo
ParameterTypeDescription
viewstringdirectory (default) or org-chart
idstringPerson ID — when present, renders the full profile page
tabstringProfile tab: details, employment, documents, activity, notes, linkedin

The people table is the source of truth for all employee/HR data. Employee fields (employment, contact, demographics, financial) are stored here rather than on users, which allows Xero sync for people who don’t have an app account. The users table is auth-only (id, email, name, picture, access_level, template IDs).

ColumnTypeDescription
idTEXT PKUUID
productive_idTEXT UNIQUEProductive person ID
user_idTEXT FKMatched app user (nullable)
first_nameTEXTFirst name
last_nameTEXTLast name
emailTEXTEmail address
titleTEXTJob title from Productive
avatar_urlTEXTProfile picture URL
squadTEXTSquad/team name
manager_idTEXT FKSelf-referencing FK to manager’s people row
xero_employee_idTEXTLinked Xero employee GUID
linkedin_urlTEXTLinkedIn profile URL
linkedin_activities_fetched_atTEXTLast activities fetch timestamp
alert_subscription_idTEXTScrapin webhook subscription ID
alert_subscribed_atTEXTWhen alert subscription was created
start_dateTEXTEmployment start date
end_dateTEXTEmployment end date (nullable)
employment_statusTEXTactive (default) or terminated
employment_basisTEXTFULLTIME, PARTTIME, CASUAL, etc.
locationTEXTWork location
genderTEXTGender code from Xero
date_of_birthTEXTDate of birth
phoneTEXTWork phone
mobileTEXTMobile number
personal_emailTEXTPersonal email address
home_addressTEXTHome address (JSON from Xero)
super_fund_nameTEXTSuperannuation fund name
super_fund_typeTEXTREGULATED or SELFMANAGED
super_member_numberTEXTSuper fund member number
bank_account_nameTEXTBank account holder name
bank_bsbTEXTBank BSB
bank_account_numberTEXTBank account number
synced_atTEXTLast sync timestamp

Confidential timestamped notes on person profiles with per-note visibility control.

ColumnTypeDescription
idTEXT PKUUID
person_idTEXT FKReferences people(id)
author_idTEXT FKReferences users(id) — who wrote the note
contentTEXTRich text (HTML) content
visibilityTEXTComma-separated access levels (e.g. executive,head,manager)
created_atTEXTCreation timestamp
updated_atTEXTLast edit timestamp

Cached LinkedIn activities (posts, comments, reactions) fetched from Scrapin.

ColumnTypeDescription
idTEXT PKUUID
person_idTEXT FKReferences people(id)
typeTEXTpost, reaction, or comment
activity_idTEXTScrapin activity ID (dedup key)
contentTEXTPost/comment text
reaction_typeTEXTLIKE, LOVE, etc. (reactions only)
related_post_textTEXTOriginal post text (reactions/comments)
related_post_urlTEXTOriginal post URL
reactions_countINTEGERNumber of reactions
comments_countINTEGERNumber of comments
activity_dateTEXTWhen the activity occurred
activity_urlTEXTDirect link to the LinkedIn activity
fetched_atTEXTWhen this was cached

Change alerts received via Scrapin webhooks when LinkedIn profile changes are detected.

ColumnTypeDescription
idTEXT PKUUID
person_idTEXT FKReferences people(id)
subscription_idTEXTScrapin subscription ID
linkedin_urlTEXTLinkedIn URL at time of alert
previous_snapshotTEXTJSON of previous profile
current_snapshotTEXTJSON of current profile
changes_detectedTEXTJSON array of {field, old, new} changes
received_atTEXTWhen the webhook was received
FilePurpose
src/routes/people.tsxRoute with ?view and ?id search params
src/components/people/people-page.tsxMain page with view toggle, search, sync button; renders profile page when id param present
src/components/people/people-directory.tsxSearchable/sortable table view
src/components/people/person-profile.tsxFull-page person profile with tabbed layout
src/components/people/person-peek-sheet.tsxSimplified peek drawer for org chart
src/components/people/org-tree.tsxRecursive tree visualization
src/components/people/org-card.tsxIndividual person card in org chart
src/components/people/linkedin-activities.tsxLinkedIn activities section (posts, comments, reactions)
src/components/people/change-alerts.tsxChange alerts timeline with subscribe/unsubscribe
src/hooks/use-people.tsTanStack Query hooks
worker/routes/people.tsAPI endpoints
worker/routes/webhooks.tsPublic webhook receiver for Scrapin change alerts