Add feature requests page with upvotes and admin status workflow
Validate / validate (push) Successful in 22s

- New /feature-requests route accessible to all logged-in users via sidebar nav
- feature_requests + feature_request_votes tables (one vote per user per request)
- Submit form (modal), upvote toggle, filter by status, sort by votes/newest
- System admins can change status (open / in_review / waiting_for_checks / in_progress / resolved / closed) with optional note
- Submitter auto-votes their own request on creation
- Admin or original submitter can delete a request

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 16:42:50 +07:00
parent b6f07fe4df
commit 23b00b2cfc
5 changed files with 502 additions and 0 deletions
+45
View File
@@ -609,6 +609,51 @@ export const shippingAccounts = pgTable(
(table) => [uniqueIndex('shipping_accounts_company_carrier_idx').on(table.companyId, table.carrier)]
);
// ── Feature Requests ───────────────────────────────────
export const featureRequestStatusEnum = pgEnum('feature_request_status', [
'open',
'in_review',
'waiting_for_checks',
'in_progress',
'resolved',
'closed'
]);
export const featureRequests = pgTable(
'feature_requests',
{
id: uuid('id').primaryKey().defaultRandom(),
title: text('title').notNull(),
description: text('description'),
status: featureRequestStatusEnum('status').notNull().default('open'),
submittedBy: text('submitted_by')
.notNull()
.references(() => users.id, { onDelete: 'set null' }),
statusChangedBy: text('status_changed_by').references(() => users.id, { onDelete: 'set null' }),
statusChangedAt: timestamp('status_changed_at', { withTimezone: true }),
statusNote: text('status_note'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
},
(table) => [index('feature_requests_status_idx').on(table.status)]
);
export const featureRequestVotes = pgTable(
'feature_request_votes',
{
id: uuid('id').primaryKey().defaultRandom(),
requestId: uuid('request_id')
.notNull()
.references(() => featureRequests.id, { onDelete: 'cascade' }),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
},
(table) => [uniqueIndex('feature_request_votes_request_user_idx').on(table.requestId, table.userId)]
);
// ── Company Log (Audit Trail) ──────────────────────────
export const companyLogEventEnum = pgEnum('company_log_event', [