Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ dist/
cli/lib/
cli/node_modules/
cli/oclif.manifest.json
.data/
everywhere/.config.json
9 changes: 7 additions & 2 deletions cli/src/codegen/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,19 @@ export function generateModelHooks(schema: ModelSchema): string {
const lines = [HEADER];

lines.push("import { useQuery, useMutation } from '@workday/everywhere';");
lines.push(`import type { QueryResult } from '@workday/everywhere';`);
lines.push(`import type { ${name} } from './models.js';`);
lines.push('');
lines.push(`export function use${plural}(options?: Parameters<typeof useQuery>[1]) {`);
lines.push(` return useQuery<${name}>('${name}', options);`);
lines.push(
` return useQuery<${name}>('${name}', options) as QueryResult<${name}> & { data: ${name}[] | null };`
);
lines.push('}');
lines.push('');
lines.push(`export function use${name}(id: string) {`);
lines.push(` return useQuery<${name}>('${name}', { id });`);
lines.push(
` return useQuery<${name}>('${name}', { id }) as QueryResult<${name}> & { data: ${name} | null };`
);
lines.push('}');
lines.push('');
lines.push(`export function use${name}Mutation() {`);
Expand Down
1 change: 1 addition & 0 deletions examples/.justfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Install example plugin dependencies
setup:
cd hello && npm install
cd directory && npm install

# Format example source files
tidy:
Expand Down
16 changes: 16 additions & 0 deletions examples/directory/everywhere/data/Department.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// AUTO-GENERATED by `everywhere bind` -- do not edit manually.

import { useQuery, useMutation } from '@workday/everywhere';
import type { Department } from './models.js';

export function useDepartments(options?: Parameters<typeof useQuery>[1]) {
return useQuery<Department>('Department', options);
}

export function useDepartment(id: string) {
return useQuery<Department>('Department', { id });
}

export function useDepartmentMutation() {
return useMutation<Department>('Department');
}
16 changes: 16 additions & 0 deletions examples/directory/everywhere/data/Employee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// AUTO-GENERATED by `everywhere bind` -- do not edit manually.

import { useQuery, useMutation } from '@workday/everywhere';
import type { Employee } from './models.js';

export function useEmployees(options?: Parameters<typeof useQuery>[1]) {
return useQuery<Employee>('Employee', options);
}

export function useEmployee(id: string) {
return useQuery<Employee>('Employee', { id });
}

export function useEmployeeMutation() {
return useMutation<Employee>('Employee');
}
6 changes: 6 additions & 0 deletions examples/directory/everywhere/data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// AUTO-GENERATED by `everywhere bind` -- do not edit manually.

export * from './models.js';
export * from './schema.js';
export * from './Department.js';
export * from './Employee.js';
21 changes: 21 additions & 0 deletions examples/directory/everywhere/data/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// AUTO-GENERATED by `everywhere bind` -- do not edit manually.

export interface Department {
id: string;
name: string;
manager: string;
headcount: string;
}

export interface Employee {
id: string;
name: string;
email: string;
department: string;
title: string;
startDate: string;
photoUrl: string;
isActive: boolean;
isRemote: boolean;
bio: string;
}
32 changes: 32 additions & 0 deletions examples/directory/everywhere/data/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// AUTO-GENERATED by `everywhere bind` -- do not edit manually.

import type { ModelSchema } from '@workday/everywhere';

export const schemas: Record<string, ModelSchema> = {
Department: {
name: 'Department',
label: 'Department',
collection: 'departments',
fields: [
{ name: 'name', type: 'TEXT' },
{ name: 'manager', type: 'SINGLE_INSTANCE', target: 'Employee' },
{ name: 'headcount', type: 'TEXT' },
],
},
Employee: {
name: 'Employee',
label: 'Employee',
collection: 'employees',
fields: [
{ name: 'name', type: 'TEXT' },
{ name: 'email', type: 'TEXT' },
{ name: 'department', type: 'TEXT' },
{ name: 'title', type: 'TEXT' },
{ name: 'startDate', type: 'DATE', precision: 'DAY' },
{ name: 'photoUrl', type: 'TEXT' },
{ name: 'isActive', type: 'BOOLEAN' },
{ name: 'isRemote', type: 'BOOLEAN' },
{ name: 'bio', type: 'TEXT' },
],
},
};
14 changes: 14 additions & 0 deletions examples/directory/model/Department.businessobject
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": 2,
"name": "Department",
"label": "Department",
"defaultCollection": {
"name": "departments",
"label": "Departments"
},
"fields": [
{ "id": 1, "name": "name", "type": "TEXT", "useForDisplay": true },
{ "id": 2, "name": "manager", "type": "SINGLE_INSTANCE", "target": "Employee" },
{ "id": 3, "name": "headcount", "type": "TEXT" }
]
}
20 changes: 20 additions & 0 deletions examples/directory/model/Employee.businessobject
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": 1,
"name": "Employee",
"label": "Employee",
"defaultCollection": {
"name": "employees",
"label": "Employees"
},
"fields": [
{ "id": 1, "name": "name", "type": "TEXT", "useForDisplay": true },
{ "id": 2, "name": "email", "type": "TEXT" },
{ "id": 3, "name": "department", "type": "TEXT" },
{ "id": 4, "name": "title", "type": "TEXT" },
{ "id": 5, "name": "startDate", "type": "DATE", "precision": "DAY" },
{ "id": 6, "name": "photoUrl", "type": "TEXT" },
{ "id": 7, "name": "isActive", "type": "BOOLEAN" },
{ "id": 8, "name": "isRemote", "type": "BOOLEAN" },
{ "id": 9, "name": "bio", "type": "TEXT" }
]
}
16 changes: 16 additions & 0 deletions examples/directory/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "employee-directory",
"version": "1.0.0",
"description": "Search and manage employee records.",
"private": true,
"scripts": {
"preview": "everywhere view",
"clean": "rm -rf node_modules"
},
"dependencies": {
"@workday/canvas-kit-react": "^11",
"@workday/everywhere": "file:../..",
"react": "^19",
"react-dom": "^19"
}
}
118 changes: 118 additions & 0 deletions examples/directory/pages/EmployeeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useNavigate, useParams } from '@workday/everywhere';
import { Card, Flex, Heading, SecondaryButton, Text } from '@workday/canvas-kit-react';
import { useEmployees, useEmployee } from '../everywhere/data/Employee.js';
import type { Employee } from '../everywhere/data/models.js';

function EmployeeDetail() {
const { id } = useParams();
const navigate = useNavigate();
const { data: employee } = useEmployee(id ?? '');

return (
<Flex flexDirection="column" gap="s" padding="s">
<SecondaryButton size="small" onClick={() => navigate('employees')}>
Back to list
</SecondaryButton>
<Card>
<Card.Heading>{employee?.name ?? 'Employee Detail'}</Card.Heading>
<Card.Body>
{employee ? (
<Flex gap="m">
<img
src={employee.photoUrl}
alt={employee.name}
style={{ width: 80, height: 80, borderRadius: 8 }}
/>
<Flex flexDirection="column" gap="xxs">
<Text typeLevel="body.large" fontWeight="bold">
{employee.title}
</Text>
<Text typeLevel="body.small" color="licorice300">
{employee.department}
</Text>
<Text typeLevel="body.small">{employee.email}</Text>
<Text typeLevel="subtext.medium" color="licorice300">
Started {employee.startDate}
</Text>
{employee.isRemote && (
<Text typeLevel="subtext.medium" color="blueberry400">
Remote
</Text>
)}
{employee.bio && (
<Text typeLevel="body.small" style={{ marginTop: 8 }}>
{employee.bio}
</Text>
)}
</Flex>
</Flex>
) : (
<Text>Employee not found: {id}</Text>
)}
</Card.Body>
</Card>
</Flex>
);
}

function EmployeeRow({ employee, onClick }: { employee: Employee; onClick: () => void }) {
return (
<Flex
alignItems="center"
gap="s"
padding="xs"
style={{ cursor: 'pointer', borderRadius: 8 }}
onClick={onClick}
>
<img
src={employee.photoUrl}
alt={employee.name}
style={{ width: 40, height: 40, borderRadius: '50%' }}
/>
<Flex flexDirection="column" flex={1}>
<Text typeLevel="body.small" fontWeight="bold">
{employee.name}
</Text>
<Text typeLevel="subtext.medium" color="licorice300">
{employee.title}
</Text>
</Flex>
<Text typeLevel="subtext.medium" color="licorice300">
{employee.department}
</Text>
</Flex>
);
}

export default function EmployeeListPage() {
const navigate = useNavigate();
const { id } = useParams();
const { data: employees } = useEmployees();

if (id) {
return <EmployeeDetail />;
}

return (
<Flex flexDirection="column" gap="m" padding="s">
<Heading size="large">Employees</Heading>
<Text typeLevel="body.large" color="licorice300">
{Array.isArray(employees) ? `${employees.length} employees` : 'Loading...'}
</Text>
<Card>
<Card.Body>
<Flex flexDirection="column" gap="xxs">
{Array.isArray(employees) &&
employees.map((emp) => (
<EmployeeRow
key={emp.id}
employee={emp}
onClick={() => navigate('employees/detail', { id: emp.id })}
/>
))}
</Flex>
</Card.Body>
</Card>
</Flex>
);
}
90 changes: 90 additions & 0 deletions examples/directory/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useMemo } from 'react';
import { useNavigate } from '@workday/everywhere';
import { Card, Flex, Grid, Heading, SecondaryButton, Text } from '@workday/canvas-kit-react';
import { useEmployees } from '../everywhere/data/Employee.js';
import { useDepartments } from '../everywhere/data/Department.js';

function StatCard({ label, value, subtitle }: { label: string; value: string; subtitle: string }) {
return (
<Card>
<Card.Body>
<Flex flexDirection="column" alignItems="center" gap="xxs" padding="xs">
<Text typeLevel="subtext.large" color="licorice300">
{label}
</Text>
<Text typeLevel="title.large" color="blueberry400" fontWeight="bold">
{value}
</Text>
<Text typeLevel="subtext.medium" color="licorice300" fontStyle="italic">
{subtitle}
</Text>
</Flex>
</Card.Body>
</Card>
);
}

export default function HomePage() {
const navigate = useNavigate();
const { data: employees } = useEmployees();
const { data: departments } = useDepartments();

const totalEmployees = employees?.length ?? 0;

const deptStats = useMemo(() => {
if (!departments || !Array.isArray(departments)) return [];
return departments.map((dept) => {
const count = Array.isArray(employees)
? employees.filter((e) => e.department === dept.name).length
: Number(dept.headcount);
return { ...dept, count };
});
}, [employees, departments, totalEmployees]);

return (
<Flex flexDirection="column" gap="m" padding="s">
<Heading size="large">Employee Directory</Heading>

<Text typeLevel="body.large">
Welcome to the Employee Directory. Search for colleagues, view team information, and manage
employee records.
</Text>

<Card>
<Card.Body>
<Heading size="small" marginBottom="s">
Team Overview
</Heading>
<Grid gridTemplateColumns="repeat(4, 1fr)" gridGap="s">
<StatCard
label="Total Employees"
value={String(totalEmployees)}
subtitle="All departments"
/>
{deptStats.slice(0, 3).map((dept) => (
<StatCard
key={dept.id}
label={dept.name}
value={String(dept.count)}
subtitle="Employees"
/>
))}
</Grid>
</Card.Body>
</Card>

<Card>
<Card.Body>
<Heading size="small" marginBottom="s">
Quick Actions
</Heading>
<Flex gap="s">
<SecondaryButton onClick={() => navigate('employees')}>
View All Employees
</SecondaryButton>
</Flex>
</Card.Body>
</Card>
</Flex>
);
}
Loading
Loading