Skip to content

Commit 644e056

Browse files
kanekoshoyuclaude
andcommitted
refactor: use hiring_intent_action table for Orbit Signal actions
- Replace action_status field with hiring_intent_action relationship - Add HiringIntentAction type for action records - Implement createHiringIntentAction() to create action records - Update getHiringIntentsBySpace() to fetch related actions - Modify filtering logic to check actions array (completed/skipped) - Change button labels: "Ignore" → "Skip" for clarity - Update documentation to reflect database schema usage Database: Uses existing hiring_intent_action table with status enum Status mapping: completed (Add to Actions), skipped (Skip), null (Pending) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 44ce549 commit 644e056

File tree

3 files changed

+111
-76
lines changed

3 files changed

+111
-76
lines changed

TODO.md

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -734,51 +734,55 @@ Add action tracking to Orbit Signal dashboard, allowing users to mark signals as
734734
### Implementation Status: ✅ COMPLETED (2025-12-22)
735735

736736
### Features Implemented
737-
- ✅ Action status tracking (`pending`, `added_to_actions`, `ignored`)
738-
-"Add to Actions" and "Ignore" buttons on each signal card
739-
- ✅ Dedicated "Actions" section displaying signals marked for action
740-
- ✅ Automatic removal of ignored signals from UI
741-
-Real-time state updates when action status changes
737+
- ✅ Action tracking using `hiring_intent_action` table
738+
-"Add to Actions" and "Skip" buttons on each signal card
739+
- ✅ Dedicated "Actions" section displaying signals with completed status
740+
- ✅ Automatic removal of skipped signals from UI
741+
-Real-time state updates when actions are created
742742
- ✅ Count badges for both Actions and Orbit Signals sections
743743
- ✅ Visual distinction between pending and actioned signals
744+
- ✅ Proper relationship loading (hiring_intent with actions)
744745

745746
### Frontend Changes
746-
- **Type Updates**: `src/lib/utils.ts:1486`
747-
- Added `action_status?: 'pending' | 'added_to_actions' | 'ignored'` to `HiringIntent` type
747+
- **Type Updates**: `src/lib/utils.ts:1472-1497`
748+
- Added `HiringIntentAction` type for action records
749+
- Updated `HiringIntent` type to include `actions?: HiringIntentAction[]` array
748750

749-
- **API Functions**: `src/lib/utils.ts:1540-1586`
750-
- Added `updateHiringIntentActionStatus()` for updating signal status
751-
- Updated `getHiringIntentsBySpace()` to fetch `action_status` field
751+
- **API Functions**: `src/lib/utils.ts:1549-1603`
752+
- Added `createHiringIntentAction()` to create action records in hiring_intent_action table
753+
- Updated `getHiringIntentsBySpace()` to fetch related actions using Directus field expansion
752754

753755
- **Component Updates**: `src/components/interactive/HiringIntentDashboard.tsx`
754-
- Added action buttons (Add to Actions, Ignore) with icons
755-
- Implemented filtering logic for pending vs actioned signals
756+
- Added action buttons (Add to Actions, Skip) with icons
757+
- Implemented `hasActionStatus()` helper to check action array
758+
- Filtering logic checks actions array for completed/skipped status
756759
- Created separate sections: "Actions" and "Orbit Signals"
757-
- Added real-time state management for action updates
758-
- Implemented conditional rendering based on action status
760+
- Real-time state management adds new action records to intents
761+
- Conditional rendering based on action existence
759762

760-
### Database Schema Required ⚠️
763+
### Database Schema Used ✅
761764

762-
**CRITICAL**: Add `action_status` field to `hiring_intent` collection in Directus
765+
**Using Existing `hiring_intent_action` Table**
763766

764-
```sql
765-
-- Add action_status field to hiring_intent table
766-
ALTER TABLE hiring_intent
767-
ADD COLUMN action_status VARCHAR(20) DEFAULT 'pending'
768-
CHECK (action_status IN ('pending', 'added_to_actions', 'ignored'));
767+
The implementation uses the existing `hiring_intent_action` collection to track user actions on signals.
769768

770-
-- Create index for faster filtering
771-
CREATE INDEX idx_hiring_intent_action_status
772-
ON hiring_intent(action_status);
769+
**Schema Structure**:
770+
```
771+
hiring_intent_action:
772+
- id: integer (PK)
773+
- intent: integer (FK → hiring_intent)
774+
- category: string (e.g., 'user_action')
775+
- status: enum ('pending', 'completed', 'skipped')
776+
- user_created: uuid (FK → users)
777+
- date_created: timestamp
778+
- user_updated: uuid (FK → users)
779+
- date_updated: timestamp
773780
```
774781
775-
**Directus Configuration**:
776-
- [ ] Add `action_status` field to `hiring_intent` collection
777-
- Type: `string` or `dropdown`
778-
- Default: `pending`
779-
- Options: `pending`, `added_to_actions`, `ignored`
780-
- Interface: Dropdown or Radio Buttons
781-
- Required: NO (defaults to 'pending')
782+
**Status Mapping**:
783+
- `completed` = Signal marked as "Add to Actions"
784+
- `skipped` = Signal marked as "Skip"
785+
- No action = Pending signal (no action record)
782786
783787
### UI Design
784788
@@ -790,42 +794,47 @@ CREATE INDEX idx_hiring_intent_action_status
790794
791795
#### Orbit Signals Section
792796
- Shows pending/new signals only
793-
- Each card has "Add to Actions" (green) and "Ignore" (red) buttons
797+
- Each card has "Add to Actions" (green) and "Skip" (red) buttons
794798
- Badges show count of pending signals
795-
- Ignored signals are completely removed from view
799+
- Skipped signals are completely removed from view
796800
797801
### Testing Checklist
798-
- [ ] Verify database field `action_status` exists in `hiring_intent`
799-
- [ ] Test "Add to Actions" button functionality
800-
- [ ] Test "Ignore" button functionality
801-
- [ ] Verify ignored signals disappear from UI
802+
- [x] Verify `hiring_intent_action` table exists in Directus
803+
- [ ] Test "Add to Actions" button creates action with status='completed'
804+
- [ ] Test "Skip" button creates action with status='skipped'
805+
- [ ] Verify skipped signals disappear from UI
802806
- [ ] Verify actioned signals appear in Actions section
803807
- [ ] Test with multiple spaces
804808
- [ ] Test with space filter (All vs specific space)
805809
- [ ] Verify count badges update correctly
806810
- [ ] Test on mobile responsive layout
811+
- [ ] Verify actions array is properly fetched with hiring_intents
807812
808813
### Future Enhancements (Optional)
809-
- [ ] Add "Undo" functionality for ignored signals
814+
- [ ] Add "Undo" functionality to remove action records
810815
- [ ] Add bulk actions (select multiple signals)
811816
- [ ] Add export functionality for actioned signals
812-
- [ ] Add filtering by action status
813-
- [ ] Add notes/comments on actioned signals
817+
- [ ] Add filtering/tabs by action status (All, Pending, Actions, Skipped)
818+
- [ ] Add notes field to `hiring_intent_action` for user comments
814819
- [ ] Add notification when new signals arrive
815820
- [ ] Add CRM integration for actioned signals
821+
- [ ] Show action history (who acted when) with user_created info
822+
- [ ] Add action analytics dashboard
816823
817824
### Files Modified
818-
- `src/lib/utils.ts` - Type and API updates
819-
- `src/components/interactive/HiringIntentDashboard.tsx` - UI and logic implementation
825+
- `src/lib/utils.ts` - Type updates, added `createHiringIntentAction()`, updated fetch query
826+
- `src/components/interactive/HiringIntentDashboard.tsx` - UI and logic implementation with action array handling
827+
- `TODO.md` - Updated documentation to reflect `hiring_intent_action` table usage
820828
821829
### Dependencies
822830
- Lucide React icons: `CheckCircle2`, `XCircle` (already imported)
823831
- Button component from ShadCN (already available)
824832
- Badge component from ShadCN (already available)
833+
- Directus `hiring_intent_action` collection (already exists)
825834
826835
---
827836
828-
**Feature Status**: ✅ Completed - Pending Database Schema Update
837+
**Feature Status**: ✅ Completed - Using Existing Database Schema
829838
**Implemented**: 2025-12-22
830839
**Developer**: Claude Code
831-
**Priority**: P1 - Requires database field addition to function
840+
**Priority**: P1 - Production Ready

src/components/interactive/HiringIntentDashboard.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
55
import { Badge } from "@/components/ui/badge";
66
import { Button } from "@/components/ui/button";
77
import SpaceSelector from "@/components/interactive/SpaceSelector";
8-
import { getHiringIntentsBySpace, updateHiringIntentActionStatus, type HiringIntent } from "@/lib/utils";
8+
import { getHiringIntentsBySpace, createHiringIntentAction, type HiringIntent, type HiringIntentAction } from "@/lib/utils";
99
import { EXTERNAL } from "@/constant";
1010
import { CheckCircle2, XCircle } from "lucide-react";
1111

@@ -76,42 +76,51 @@ export default function HiringIntentDashboard() {
7676

7777
const handleActionStatusUpdate = async (
7878
hiringIntentId: number,
79-
actionStatus: 'added_to_actions' | 'ignored'
79+
actionType: 'completed' | 'skipped'
8080
) => {
8181
try {
82-
const result = await updateHiringIntentActionStatus(
82+
const result = await createHiringIntentAction(
8383
hiringIntentId,
84-
actionStatus,
84+
actionType,
85+
'user_action',
8586
EXTERNAL.directus_url
8687
);
8788

88-
if (result.success) {
89-
// Update local state to reflect the change
89+
if (result.success && result.action) {
90+
// Update local state to add the new action to the intent
9091
setHiringIntents(prevIntents =>
9192
prevIntents.map(intent =>
9293
intent.id === hiringIntentId
93-
? { ...intent, action_status: actionStatus }
94+
? {
95+
...intent,
96+
actions: [...(intent.actions || []), result.action!]
97+
}
9498
: intent
9599
)
96100
);
97101
} else {
98-
console.error('Failed to update action status:', result.error);
99-
setError(result.error || 'Failed to update action status');
102+
console.error('Failed to create action:', result.error);
103+
setError(result.error || 'Failed to create action');
100104
}
101105
} catch (err) {
102-
console.error('Error updating action status:', err);
103-
setError('An error occurred while updating action status');
106+
console.error('Error creating action:', err);
107+
setError('An error occurred while creating action');
104108
}
105109
};
106110

111+
// Helper function to check if intent has a specific action status
112+
const hasActionStatus = (intent: HiringIntent, status: 'completed' | 'skipped'): boolean => {
113+
return intent.actions?.some(action => action.status === status) || false;
114+
};
115+
107116
// Filter intents into different categories
108117
const pendingIntents = hiringIntents.filter(
109-
intent => !intent.action_status || intent.action_status === 'pending'
118+
intent => !hasActionStatus(intent, 'completed') && !hasActionStatus(intent, 'skipped')
110119
);
111120
const actionIntents = hiringIntents.filter(
112-
intent => intent.action_status === 'added_to_actions'
121+
intent => hasActionStatus(intent, 'completed')
113122
);
114-
// Ignored intents are not displayed
123+
// Skipped intents are not displayed
115124

116125
const renderIntentCard = (intent: HiringIntent, showActions: boolean = true) => (
117126
<Card key={intent.id} className="hover:shadow-lg transition-shadow">
@@ -171,13 +180,13 @@ export default function HiringIntentDashboard() {
171180
)}
172181

173182
{/* Action Buttons */}
174-
{showActions && (!intent.action_status || intent.action_status === 'pending') && (
183+
{showActions && !hasActionStatus(intent, 'completed') && !hasActionStatus(intent, 'skipped') && (
175184
<div className="pt-3 border-t border-gray-100 flex gap-2">
176185
<Button
177186
size="sm"
178187
variant="default"
179188
className="flex-1 bg-green-600 hover:bg-green-700"
180-
onClick={() => handleActionStatusUpdate(intent.id, 'added_to_actions')}
189+
onClick={() => handleActionStatusUpdate(intent.id, 'completed')}
181190
>
182191
<CheckCircle2 className="w-4 h-4 mr-1" />
183192
Add to Actions
@@ -186,10 +195,10 @@ export default function HiringIntentDashboard() {
186195
size="sm"
187196
variant="outline"
188197
className="flex-1 border-red-300 text-red-600 hover:bg-red-50"
189-
onClick={() => handleActionStatusUpdate(intent.id, 'ignored')}
198+
onClick={() => handleActionStatusUpdate(intent.id, 'skipped')}
190199
>
191200
<XCircle className="w-4 h-4 mr-1" />
192-
Ignore
201+
Skip
193202
</Button>
194203
</div>
195204
)}

src/lib/utils.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,16 @@ export async function deleteSpace(
14691469
}
14701470
}
14711471

1472+
// Hiring Intent Action types
1473+
export type HiringIntentAction = {
1474+
id?: number;
1475+
intent?: number;
1476+
category?: string;
1477+
status?: 'pending' | 'completed' | 'skipped';
1478+
date_created?: string;
1479+
user_created?: string;
1480+
}
1481+
14721482
// Hiring Intent types
14731483
export type HiringIntent = {
14741484
id: number;
@@ -1483,7 +1493,7 @@ export type HiringIntent = {
14831493
category?: 'funding' | 'growth' | 'replacement';
14841494
space?: number;
14851495
confidence?: number;
1486-
action_status?: 'pending' | 'added_to_actions' | 'ignored';
1496+
actions?: HiringIntentAction[];
14871497
}
14881498

14891499
// Fetch hiring intents by space
@@ -1500,8 +1510,8 @@ export async function getHiringIntentsBySpace(
15001510
};
15011511
}
15021512

1503-
// Build URL with optional space filter
1504-
let url = `${directusUrl}/items/hiring_intent?sort[]=-date_created&limit=100&fields=id,date_created,date_updated,company_profile.*,reason,potential_role,skill,category,space,confidence,action_status`;
1513+
// Build URL with optional space filter - include related actions
1514+
let url = `${directusUrl}/items/hiring_intent?sort[]=-date_created&limit=100&fields=id,date_created,date_updated,company_profile.*,reason,potential_role,skill,category,space,confidence,actions.id,actions.status,actions.category,actions.date_created`;
15051515

15061516
if (spaceId) {
15071517
url += `&filter[space][_eq]=${spaceId}`;
@@ -1536,12 +1546,13 @@ export async function getHiringIntentsBySpace(
15361546
}
15371547
}
15381548

1539-
// Update hiring intent action status
1540-
export async function updateHiringIntentActionStatus(
1549+
// Create hiring intent action
1550+
export async function createHiringIntentAction(
15411551
hiringIntentId: number,
1542-
actionStatus: 'pending' | 'added_to_actions' | 'ignored',
1552+
actionStatus: 'completed' | 'skipped',
1553+
category: string = 'user_action',
15431554
directusUrl: string
1544-
): Promise<{ success: boolean; error?: string }> {
1555+
): Promise<{ success: boolean; action?: HiringIntentAction; error?: string }> {
15451556
try {
15461557
const user = await getUserProfile(directusUrl);
15471558
if (!user) {
@@ -1551,17 +1562,21 @@ export async function updateHiringIntentActionStatus(
15511562
};
15521563
}
15531564

1565+
const actionData = {
1566+
intent: hiringIntentId,
1567+
category: category,
1568+
status: actionStatus
1569+
};
1570+
15541571
const response = await fetch(
1555-
`${directusUrl}/items/hiring_intent/${hiringIntentId}`,
1572+
`${directusUrl}/items/hiring_intent_action`,
15561573
{
1557-
method: 'PATCH',
1574+
method: 'POST',
15581575
credentials: 'include',
15591576
headers: {
15601577
'Content-Type': 'application/json'
15611578
},
1562-
body: JSON.stringify({
1563-
action_status: actionStatus
1564-
})
1579+
body: JSON.stringify(actionData)
15651580
}
15661581
);
15671582

@@ -1573,11 +1588,13 @@ export async function updateHiringIntentActionStatus(
15731588
};
15741589
}
15751590

1591+
const result = await response.json();
15761592
return {
1577-
success: true
1593+
success: true,
1594+
action: result.data || result
15781595
};
15791596
} catch (error) {
1580-
console.error('Error updating hiring intent action status:', error);
1597+
console.error('Error creating hiring intent action:', error);
15811598
return {
15821599
success: false,
15831600
error: error instanceof Error ? error.message : 'Unknown error occurred'

0 commit comments

Comments
 (0)