@onahhas/hello-dev 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +149 -11
  2. package/backend/Controllers/AccountController.cs +100 -0
  3. package/backend/Controllers/ActivityController.cs +44 -0
  4. package/backend/Controllers/AuthController.cs +127 -0
  5. package/backend/Controllers/LookupController.cs +46 -0
  6. package/backend/Controllers/TasksController.cs +652 -0
  7. package/backend/Controllers/UsersController.cs +181 -0
  8. package/backend/Data/AppDbContext.cs +93 -0
  9. package/backend/Data/DbSeeder.cs +122 -0
  10. package/backend/DevTasks.Api.csproj +13 -0
  11. package/backend/Dtos/ActivityDtos.cs +12 -0
  12. package/backend/Dtos/AuthDtos.cs +37 -0
  13. package/backend/Dtos/TaskDtos.cs +104 -0
  14. package/backend/Dtos/UserDtos.cs +29 -0
  15. package/backend/Enums/EditRequestStatus.cs +8 -0
  16. package/backend/Enums/TaskPriority.cs +8 -0
  17. package/backend/Enums/TaskState.cs +9 -0
  18. package/backend/Enums/TaskVisibility.cs +7 -0
  19. package/backend/Enums/UserRole.cs +7 -0
  20. package/backend/Extensions/ClaimsPrincipalExtensions.cs +23 -0
  21. package/backend/Models/ActivityLog.cs +12 -0
  22. package/backend/Models/AppUser.cs +17 -0
  23. package/backend/Models/TaskEditRequest.cs +31 -0
  24. package/backend/Models/TaskItem.cs +25 -0
  25. package/backend/Program.cs +138 -0
  26. package/backend/Properties/launchSettings.json +13 -0
  27. package/backend/Services/ActivityService.cs +28 -0
  28. package/backend/Services/PasswordHasher.cs +58 -0
  29. package/backend/Services/TokenService.cs +49 -0
  30. package/backend/appsettings.Development.json +10 -0
  31. package/backend/appsettings.json +24 -0
  32. package/frontend/index.html +12 -0
  33. package/frontend/package-lock.json +1769 -0
  34. package/frontend/package.json +23 -0
  35. package/frontend/src/App.tsx +40 -0
  36. package/frontend/src/api/http.ts +75 -0
  37. package/frontend/src/auth/AuthContext.tsx +101 -0
  38. package/frontend/src/components/EditRequestModal.tsx +139 -0
  39. package/frontend/src/components/EditRequestsPanel.tsx +94 -0
  40. package/frontend/src/components/Layout.tsx +76 -0
  41. package/frontend/src/components/PageHeader.tsx +21 -0
  42. package/frontend/src/components/ProtectedRoute.tsx +14 -0
  43. package/frontend/src/components/StatCard.tsx +15 -0
  44. package/frontend/src/components/TaskCard.tsx +83 -0
  45. package/frontend/src/components/TaskDetailsModal.tsx +45 -0
  46. package/frontend/src/components/TaskFilters.tsx +67 -0
  47. package/frontend/src/components/TaskModal.tsx +159 -0
  48. package/frontend/src/components/TaskTable.tsx +68 -0
  49. package/frontend/src/components/UserModal.tsx +124 -0
  50. package/frontend/src/main.tsx +19 -0
  51. package/frontend/src/pages/ActivityPage.tsx +37 -0
  52. package/frontend/src/pages/BoardPage.tsx +75 -0
  53. package/frontend/src/pages/CalendarPage.tsx +101 -0
  54. package/frontend/src/pages/DashboardPage.tsx +131 -0
  55. package/frontend/src/pages/LoginPage.tsx +69 -0
  56. package/frontend/src/pages/ProfilePage.tsx +111 -0
  57. package/frontend/src/pages/PublicTasksPage.tsx +99 -0
  58. package/frontend/src/pages/RegisterPage.tsx +80 -0
  59. package/frontend/src/pages/TasksPage.tsx +135 -0
  60. package/frontend/src/pages/UsersPage.tsx +86 -0
  61. package/frontend/src/styles.css +596 -0
  62. package/frontend/src/theme.tsx +49 -0
  63. package/frontend/src/types.ts +78 -0
  64. package/frontend/src/utils/date.ts +30 -0
  65. package/frontend/src/utils/labels.ts +3 -0
  66. package/frontend/src/vite-env.d.ts +1 -0
  67. package/frontend/tsconfig.json +21 -0
  68. package/frontend/vite.config.ts +15 -0
  69. package/package.json +22 -9
  70. package/index.js +0 -7
@@ -0,0 +1,135 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { api, toQuery } from '../api/http';
3
+ import { EditRequestsPanel } from '../components/EditRequestsPanel';
4
+ import { PageHeader } from '../components/PageHeader';
5
+ import { TaskCard } from '../components/TaskCard';
6
+ import { TaskDetailsModal } from '../components/TaskDetailsModal';
7
+ import { TaskFilters, type TaskFiltersValue } from '../components/TaskFilters';
8
+ import { TaskModal } from '../components/TaskModal';
9
+ import { TaskTable } from '../components/TaskTable';
10
+ import type { TaskItem, User } from '../types';
11
+
12
+ const defaultFilters: TaskFiltersValue = {
13
+ q: '',
14
+ status: '',
15
+ priority: '',
16
+ visibility: ''
17
+ };
18
+
19
+ export function TasksPage() {
20
+ const [tasks, setTasks] = useState<TaskItem[]>([]);
21
+ const [users, setUsers] = useState<User[]>([]);
22
+ const [filters, setFilters] = useState(defaultFilters);
23
+ const [editingTask, setEditingTask] = useState<TaskItem | null>(null);
24
+ const [viewingTask, setViewingTask] = useState<TaskItem | null>(null);
25
+ const [modalOpen, setModalOpen] = useState(false);
26
+ const [viewMode, setViewMode] = useState<'table' | 'cards'>('table');
27
+ const [refreshKey, setRefreshKey] = useState(0);
28
+ const [error, setError] = useState('');
29
+
30
+ const query = useMemo(() => toQuery(filters), [filters]);
31
+
32
+ useEffect(() => {
33
+ void loadTasks();
34
+ }, [query]);
35
+
36
+ useEffect(() => {
37
+ void loadUsers();
38
+ }, []);
39
+
40
+ async function loadTasks() {
41
+ try {
42
+ setTasks(await api<TaskItem[]>(`/api/tasks/my${query}`));
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err.message : 'Could not load tasks.');
45
+ }
46
+ }
47
+
48
+ async function loadUsers() {
49
+ try {
50
+ setUsers(await api<User[]>('/api/lookup/users'));
51
+ } catch {
52
+ setUsers([]);
53
+ }
54
+ }
55
+
56
+ async function deleteTask(task: TaskItem) {
57
+ if (!confirm(`Delete task #${task.id}: ${task.title}?`)) return;
58
+
59
+ try {
60
+ await api<void>(`/api/tasks/${task.id}`, { method: 'DELETE' });
61
+ await loadTasks();
62
+ } catch (err) {
63
+ alert(err instanceof Error ? err.message : 'Could not delete task.');
64
+ }
65
+ }
66
+
67
+ function openCreate() {
68
+ setEditingTask(null);
69
+ setModalOpen(true);
70
+ }
71
+
72
+ function openEdit(task: TaskItem) {
73
+ setEditingTask(task);
74
+ setModalOpen(true);
75
+ }
76
+
77
+ async function reloadEverything() {
78
+ await loadTasks();
79
+ setRefreshKey((value) => value + 1);
80
+ }
81
+
82
+ return (
83
+ <section>
84
+ <PageHeader
85
+ eyebrow="Tasks"
86
+ title="My Tasks"
87
+ description="Table-first task management with optional card view and edit request approval."
88
+ actions={(
89
+ <div className="buttonGroup">
90
+ <button className="ghostBtn" type="button" onClick={() => setViewMode(viewMode === 'table' ? 'cards' : 'table')}>
91
+ {viewMode === 'table' ? 'Card view' : 'Table view'}
92
+ </button>
93
+ <button className="primaryBtn" type="button" onClick={openCreate}>Add task</button>
94
+ </div>
95
+ )}
96
+ />
97
+
98
+ <EditRequestsPanel refreshKey={refreshKey} onChanged={reloadEverything} />
99
+
100
+ <TaskFilters value={filters} onChange={setFilters} />
101
+ {error && <div className="errorBox">{error}</div>}
102
+
103
+ {viewMode === 'table' ? (
104
+ <>
105
+ <TaskTable tasks={tasks} onView={setViewingTask} onEdit={openEdit} onDelete={deleteTask} />
106
+ {tasks.length === 0 && <div className="emptyState">No tasks match your filters.</div>}
107
+ </>
108
+ ) : (
109
+ <div className="taskList">
110
+ {tasks.map((task) => (
111
+ <TaskCard
112
+ key={task.id}
113
+ task={task}
114
+ onView={setViewingTask}
115
+ onEdit={openEdit}
116
+ onDelete={deleteTask}
117
+ />
118
+ ))}
119
+ {tasks.length === 0 && <div className="emptyState">No tasks match your filters.</div>}
120
+ </div>
121
+ )}
122
+
123
+ {modalOpen && (
124
+ <TaskModal
125
+ task={editingTask}
126
+ users={users}
127
+ onClose={() => setModalOpen(false)}
128
+ onSaved={reloadEverything}
129
+ />
130
+ )}
131
+
132
+ {viewingTask && <TaskDetailsModal task={viewingTask} onClose={() => setViewingTask(null)} />}
133
+ </section>
134
+ );
135
+ }
@@ -0,0 +1,86 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { api } from '../api/http';
3
+ import { PageHeader } from '../components/PageHeader';
4
+ import { UserModal } from '../components/UserModal';
5
+ import type { User } from '../types';
6
+ import { formatDate } from '../utils/date';
7
+
8
+ export function UsersPage() {
9
+ const [users, setUsers] = useState<User[]>([]);
10
+ const [editingUser, setEditingUser] = useState<User | null>(null);
11
+ const [modalOpen, setModalOpen] = useState(false);
12
+
13
+ useEffect(() => {
14
+ void load();
15
+ }, []);
16
+
17
+ async function load() {
18
+ setUsers(await api<User[]>('/api/users'));
19
+ }
20
+
21
+ async function deactivate(user: User) {
22
+ if (!confirm(`Deactivate ${user.fullName}?`)) return;
23
+
24
+ await api<void>(`/api/users/${user.id}`, { method: 'DELETE' });
25
+ await load();
26
+ }
27
+
28
+ function openCreate() {
29
+ setEditingUser(null);
30
+ setModalOpen(true);
31
+ }
32
+
33
+ function openEdit(user: User) {
34
+ setEditingUser(user);
35
+ setModalOpen(true);
36
+ }
37
+
38
+ return (
39
+ <section>
40
+ <PageHeader
41
+ eyebrow="Admin"
42
+ title="Users"
43
+ description="Add users, change roles, and deactivate accounts."
44
+ actions={<button className="primaryBtn" type="button" onClick={openCreate}>Add user</button>}
45
+ />
46
+
47
+ <section className="tableCard">
48
+ <table>
49
+ <thead>
50
+ <tr>
51
+ <th>Name</th>
52
+ <th>Email</th>
53
+ <th>Role</th>
54
+ <th>Status</th>
55
+ <th>Created</th>
56
+ <th></th>
57
+ </tr>
58
+ </thead>
59
+ <tbody>
60
+ {users.map((user) => (
61
+ <tr key={user.id}>
62
+ <td>{user.fullName}</td>
63
+ <td>{user.email}</td>
64
+ <td>{user.role}</td>
65
+ <td><span className={`statusDot ${user.isActive ? 'active' : 'inactive'}`}>{user.isActive ? 'Active' : 'Inactive'}</span></td>
66
+ <td>{formatDate(user.createdAt)}</td>
67
+ <td className="rowActions">
68
+ <button className="ghostBtn" onClick={() => openEdit(user)}>Edit</button>
69
+ {user.isActive && <button className="dangerBtn" onClick={() => void deactivate(user)}>Deactivate</button>}
70
+ </td>
71
+ </tr>
72
+ ))}
73
+ </tbody>
74
+ </table>
75
+ </section>
76
+
77
+ {modalOpen && (
78
+ <UserModal
79
+ user={editingUser}
80
+ onClose={() => setModalOpen(false)}
81
+ onSaved={load}
82
+ />
83
+ )}
84
+ </section>
85
+ );
86
+ }