enh
This commit is contained in:
parent
32feef5621
commit
8d702ed2ff
9 changed files with 4285 additions and 5 deletions
File diff suppressed because one or more lines are too long
|
|
@ -14,6 +14,194 @@ import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import CancelOrderDialog from "@/components/CancelOrderDialog";
|
import CancelOrderDialog from "@/components/CancelOrderDialog";
|
||||||
|
|
||||||
|
function UserIncidentDialog({ userId, orderId, open, onClose, onSuccess }: { userId: number; orderId: number; open: boolean; onClose: () => void; onSuccess?: () => void }) {
|
||||||
|
const [adminComment, setAdminComment] = useState('');
|
||||||
|
const [negativityScore, setNegativityScore] = useState('');
|
||||||
|
|
||||||
|
const addIncidentMutation = trpc.admin.user.addUserIncident.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
Alert.alert('Success', 'Incident added successfully');
|
||||||
|
setAdminComment('');
|
||||||
|
setNegativityScore('');
|
||||||
|
onClose();
|
||||||
|
onSuccess?.();
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
Alert.alert('Error', error.message || 'Failed to add incident');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddIncident = () => {
|
||||||
|
const score = negativityScore ? parseInt(negativityScore) : undefined;
|
||||||
|
|
||||||
|
if (!adminComment.trim() && !negativityScore) {
|
||||||
|
Alert.alert('Error', 'Please enter a comment or negativity score');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addIncidentMutation.mutate({
|
||||||
|
userId,
|
||||||
|
orderId,
|
||||||
|
adminComment: adminComment || undefined,
|
||||||
|
negativityScore: score,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomDialog open={open} onClose={onClose}>
|
||||||
|
<View style={tw`p-6`}>
|
||||||
|
<View style={tw`items-center mb-6`}>
|
||||||
|
<View style={tw`w-12 h-12 bg-amber-100 rounded-full items-center justify-center mb-3`}>
|
||||||
|
<MaterialIcons name="warning" size={24} color="#D97706" />
|
||||||
|
</View>
|
||||||
|
<MyText style={tw`text-xl font-bold text-gray-900 text-center`}>
|
||||||
|
Add User Incident
|
||||||
|
</MyText>
|
||||||
|
<MyText style={tw`text-gray-500 text-center mt-2 text-sm leading-5`}>
|
||||||
|
Record an incident for this user. This will be visible in their profile.
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<MyTextInput
|
||||||
|
topLabel="Admin Comment"
|
||||||
|
value={adminComment}
|
||||||
|
onChangeText={setAdminComment}
|
||||||
|
placeholder="Enter details about the incident..."
|
||||||
|
multiline
|
||||||
|
style={tw`h-24`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MyTextInput
|
||||||
|
topLabel="Negativity Score (Optional)"
|
||||||
|
value={negativityScore}
|
||||||
|
onChangeText={setNegativityScore}
|
||||||
|
placeholder="0"
|
||||||
|
keyboardType="numeric"
|
||||||
|
style={tw`mt-4`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={tw`bg-amber-50 p-4 rounded-xl border border-amber-100 mb-6 mt-4 flex-row items-start`}>
|
||||||
|
<MaterialIcons name="info-outline" size={20} color="#D97706" style={tw`mt-0.5`} />
|
||||||
|
<MyText style={tw`text-sm text-amber-800 ml-2 flex-1 leading-5`}>
|
||||||
|
Higher negativity scores indicate more serious incidents (e.g., repeated cancellations, abusive behavior).
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={tw`flex-row gap-3`}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={tw`flex-1 bg-gray-100 py-3.5 rounded-xl items-center`}
|
||||||
|
onPress={onClose}
|
||||||
|
>
|
||||||
|
<MyText style={tw`text-gray-700 font-bold`}>Cancel</MyText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={tw`flex-1 bg-amber-500 py-3.5 rounded-xl items-center shadow-sm ${addIncidentMutation.isPending ? 'opacity-50' : ''}`}
|
||||||
|
onPress={handleAddIncident}
|
||||||
|
disabled={addIncidentMutation.isPending}
|
||||||
|
>
|
||||||
|
<MyText style={tw`text-white font-bold`}>
|
||||||
|
{addIncidentMutation.isPending ? 'Adding...' : 'Add Incident'}
|
||||||
|
</MyText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</BottomDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserIncidentsView({ userId, orderId }: { userId: number; orderId: number }) {
|
||||||
|
const [incidentDialogOpen, setIncidentDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const { data: incidentsData, refetch: refetchIncidents } = trpc.admin.user.getUserIncidents.useQuery(
|
||||||
|
{ userId },
|
||||||
|
{ enabled: !!userId }
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View style={tw`bg-amber-50 p-5 rounded-2xl shadow-sm mb-4 border border-amber-100`}>
|
||||||
|
<View style={tw`flex-row items-center justify-between mb-4`}>
|
||||||
|
<MyText style={tw`text-base font-bold text-amber-900`}>
|
||||||
|
User Incidents
|
||||||
|
</MyText>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setIncidentDialogOpen(true)}
|
||||||
|
style={tw`flex-row items-center bg-amber-200 px-3 py-1.5 rounded-lg`}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="add" size={16} color="#D97706" />
|
||||||
|
<MyText style={tw`text-xs font-bold text-amber-800 ml-1`}>Add Incident</MyText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{incidentsData?.incidents && incidentsData.incidents.length > 0 ? (
|
||||||
|
<View style={tw`space-y-3`}>
|
||||||
|
{incidentsData.incidents.map((incident: any, index: number) => (
|
||||||
|
<View
|
||||||
|
key={incident.id}
|
||||||
|
style={tw`bg-white p-4 rounded-xl border border-amber-200 ${index === incidentsData.incidents.length - 1 ? 'mb-0' : 'mb-3'}`}
|
||||||
|
>
|
||||||
|
<View style={tw`flex-row justify-between items-start mb-2`}>
|
||||||
|
<View style={tw`flex-row items-center`}>
|
||||||
|
<MaterialIcons name="event" size={14} color="#6B7280" />
|
||||||
|
<MyText style={tw`text-xs text-gray-600 ml-1`}>
|
||||||
|
{dayjs(incident.dateAdded).format('MMM DD, YYYY • h:mm A')}
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
{incident.negativityScore && (
|
||||||
|
<View style={tw`px-2 py-1 bg-red-100 rounded-md`}>
|
||||||
|
<MyText style={tw`text-xs font-bold text-red-700`}>
|
||||||
|
Score: {incident.negativityScore}
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{incident.adminComment && (
|
||||||
|
<View style={tw`mt-2`}>
|
||||||
|
<MyText style={tw`text-sm text-gray-900 leading-5`}>
|
||||||
|
{incident.adminComment}
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={tw`flex-row items-center mt-2 pt-2 border-t border-gray-100`}>
|
||||||
|
<MaterialIcons name="person" size={12} color="#9CA3AF" />
|
||||||
|
<MyText style={tw`text-xs text-gray-500 ml-1`}>
|
||||||
|
Added by {incident.addedBy}
|
||||||
|
</MyText>
|
||||||
|
{incident.orderId && (
|
||||||
|
<>
|
||||||
|
<MaterialIcons name="shopping-cart" size={12} color="#9CA3AF" style={tw`ml-3`} />
|
||||||
|
<MyText style={tw`text-xs text-gray-500 ml-1`}>
|
||||||
|
Order #{incident.orderId}
|
||||||
|
</MyText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={tw`items-center py-6`}>
|
||||||
|
<MaterialIcons name="check-circle-outline" size={32} color="#D97706" />
|
||||||
|
<MyText style={tw`text-sm text-amber-700 mt-2`}>
|
||||||
|
No incidents recorded for this user
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<UserIncidentDialog
|
||||||
|
orderId={orderId}
|
||||||
|
userId={userId}
|
||||||
|
open={incidentDialogOpen}
|
||||||
|
onClose={() => setIncidentDialogOpen(false)}
|
||||||
|
onSuccess={refetchIncidents}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function OrderDetails() {
|
export default function OrderDetails() {
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -610,6 +798,14 @@ export default function OrderDetails() {
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* User Incidents Section */}
|
||||||
|
{order.userId && (
|
||||||
|
<UserIncidentsView
|
||||||
|
userId={order.userId}
|
||||||
|
orderId={order.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Coupon Applied Section */}
|
{/* Coupon Applied Section */}
|
||||||
{order.couponCode && (
|
{order.couponCode && (
|
||||||
<View
|
<View
|
||||||
|
|
|
||||||
109
apps/admin-ui/components/UserIncidentDialog.tsx
Normal file
109
apps/admin-ui/components/UserIncidentDialog.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, TouchableOpacity } from 'react-native';
|
||||||
|
import { MyText, tw, BottomDialog, MyTextInput } from 'common-ui';
|
||||||
|
import { trpc } from '@/src/trpc-client';
|
||||||
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
|
import { Alert } from 'react-native';
|
||||||
|
|
||||||
|
interface UserIncidentDialogProps {
|
||||||
|
orderId: number;
|
||||||
|
userId: number;
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserIncidentDialog({ orderId, userId, open, onClose, onSuccess }: UserIncidentDialogProps) {
|
||||||
|
const [adminComment, setAdminComment] = useState('');
|
||||||
|
const [negativityScore, setNegativityScore] = useState('');
|
||||||
|
|
||||||
|
const addIncidentMutation = trpc.admin.user.addUserIncident.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
Alert.alert('Success', 'Incident added successfully');
|
||||||
|
setAdminComment('');
|
||||||
|
setNegativityScore('');
|
||||||
|
onClose();
|
||||||
|
onSuccess?.();
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
Alert.alert('Error', error.message || 'Failed to add incident');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddIncident = () => {
|
||||||
|
const score = negativityScore ? parseInt(negativityScore) : undefined;
|
||||||
|
|
||||||
|
if (!adminComment.trim() && !negativityScore) {
|
||||||
|
Alert.alert('Error', 'Please enter a comment or negativity score');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addIncidentMutation.mutate({
|
||||||
|
userId,
|
||||||
|
orderId,
|
||||||
|
adminComment: adminComment || undefined,
|
||||||
|
negativityScore: score,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomDialog open={open} onClose={onClose}>
|
||||||
|
<View style={tw`p-6`}>
|
||||||
|
<View style={tw`items-center mb-6`}>
|
||||||
|
<View style={tw`w-12 h-12 bg-amber-100 rounded-full items-center justify-center mb-3`}>
|
||||||
|
<MaterialIcons name="warning" size={24} color="#D97706" />
|
||||||
|
</View>
|
||||||
|
<MyText style={tw`text-xl font-bold text-gray-900 text-center`}>
|
||||||
|
Add User Incident
|
||||||
|
</MyText>
|
||||||
|
<MyText style={tw`text-gray-500 text-center mt-2 text-sm leading-5`}>
|
||||||
|
Record an incident for this user. This will be visible in their profile.
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<MyTextInput
|
||||||
|
topLabel="Admin Comment"
|
||||||
|
value={adminComment}
|
||||||
|
onChangeText={setAdminComment}
|
||||||
|
placeholder="Enter details about the incident..."
|
||||||
|
multiline
|
||||||
|
style={tw`h-24`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MyTextInput
|
||||||
|
topLabel="Negativity Score (Optional)"
|
||||||
|
value={negativityScore}
|
||||||
|
onChangeText={setNegativityScore}
|
||||||
|
placeholder="0"
|
||||||
|
keyboardType="numeric"
|
||||||
|
style={tw`mt-4`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={tw`bg-amber-50 p-4 rounded-xl border border-amber-100 mb-6 mt-4 flex-row items-start`}>
|
||||||
|
<MaterialIcons name="info-outline" size={20} color="#D97706" style={tw`mt-0.5`} />
|
||||||
|
<MyText style={tw`text-sm text-amber-800 ml-2 flex-1 leading-5`}>
|
||||||
|
Higher negativity scores indicate more serious incidents (e.g., repeated cancellations, abusive behavior).
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={tw`flex-row gap-3`}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={tw`flex-1 bg-gray-100 py-3.5 rounded-xl items-center`}
|
||||||
|
onPress={onClose}
|
||||||
|
>
|
||||||
|
<MyText style={tw`text-gray-700 font-bold`}>Cancel</MyText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={tw`flex-1 bg-amber-500 py-3.5 rounded-xl items-center shadow-sm ${addIncidentMutation.isPending ? 'opacity-50' : ''}`}
|
||||||
|
onPress={handleAddIncident}
|
||||||
|
disabled={addIncidentMutation.isPending}
|
||||||
|
>
|
||||||
|
<MyText style={tw`text-white font-bold`}>
|
||||||
|
{addIncidentMutation.isPending ? 'Adding...' : 'Add Incident'}
|
||||||
|
</MyText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</BottomDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ENV_MODE=PROD
|
ENV_MODE=PROD
|
||||||
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
# DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
||||||
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||||
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
||||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||||
PHONE_PE_CLIENT_VERSION=1
|
PHONE_PE_CLIENT_VERSION=1
|
||||||
|
|
|
||||||
13
apps/backend/drizzle/0076_sturdy_wolverine.sql
Normal file
13
apps/backend/drizzle/0076_sturdy_wolverine.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
CREATE TABLE "mf"."user_incidents" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "mf"."user_incidents_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"order_id" integer,
|
||||||
|
"date_added" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"admin_comment" text,
|
||||||
|
"added_by" integer,
|
||||||
|
"negativity_score" integer
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "mf"."user_incidents" ADD CONSTRAINT "user_incidents_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "mf"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mf"."user_incidents" ADD CONSTRAINT "user_incidents_order_id_orders_id_fk" FOREIGN KEY ("order_id") REFERENCES "mf"."orders"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mf"."user_incidents" ADD CONSTRAINT "user_incidents_added_by_staff_users_id_fk" FOREIGN KEY ("added_by") REFERENCES "mf"."staff_users"("id") ON DELETE no action ON UPDATE no action;
|
||||||
3865
apps/backend/drizzle/meta/0076_snapshot.json
Normal file
3865
apps/backend/drizzle/meta/0076_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -533,6 +533,13 @@
|
||||||
"when": 1772196660983,
|
"when": 1772196660983,
|
||||||
"tag": "0075_cuddly_rocket_racer",
|
"tag": "0075_cuddly_rocket_racer",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 76,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1772637259874,
|
||||||
|
"tag": "0076_sturdy_wolverine",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +391,16 @@ export const couponApplicableProducts = mf.table('coupon_applicable_products', {
|
||||||
unq_coupon_product: unique('unique_coupon_product').on(t.couponId, t.productId),
|
unq_coupon_product: unique('unique_coupon_product').on(t.couponId, t.productId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const userIncidents = mf.table('user_incidents', {
|
||||||
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
|
userId: integer('user_id').notNull().references(() => users.id),
|
||||||
|
orderId: integer('order_id').references(() => orders.id),
|
||||||
|
dateAdded: timestamp('date_added').notNull().defaultNow(),
|
||||||
|
adminComment: text('admin_comment'),
|
||||||
|
addedBy: integer('added_by').references(() => staffUsers.id),
|
||||||
|
negativityScore: integer('negativity_score'),
|
||||||
|
});
|
||||||
|
|
||||||
export const reservedCoupons = mf.table('reserved_coupons', {
|
export const reservedCoupons = mf.table('reserved_coupons', {
|
||||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
secretCode: varchar('secret_code', { length: 50 }).notNull().unique(),
|
secretCode: varchar('secret_code', { length: 50 }).notNull().unique(),
|
||||||
|
|
@ -473,6 +483,7 @@ export const usersRelations = relations(users, ({ many, one }) => ({
|
||||||
applicableCoupons: many(couponApplicableUsers),
|
applicableCoupons: many(couponApplicableUsers),
|
||||||
userDetails: one(userDetails),
|
userDetails: one(userDetails),
|
||||||
notifCreds: many(notifCreds),
|
notifCreds: many(notifCreds),
|
||||||
|
userIncidents: many(userIncidents),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const userCredsRelations = relations(userCreds, ({ one }) => ({
|
export const userCredsRelations = relations(userCreds, ({ one }) => ({
|
||||||
|
|
@ -542,6 +553,7 @@ export const ordersRelations = relations(orders, ({ one, many }) => ({
|
||||||
orderStatus: many(orderStatus),
|
orderStatus: many(orderStatus),
|
||||||
refunds: many(refunds),
|
refunds: many(refunds),
|
||||||
couponUsages: many(couponUsage),
|
couponUsages: many(couponUsage),
|
||||||
|
userIncidents: many(userIncidents),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const orderItemsRelations = relations(orderItems, ({ one }) => ({
|
export const orderItemsRelations = relations(orderItems, ({ one }) => ({
|
||||||
|
|
@ -669,3 +681,9 @@ export const staffRolePermissionsRelations = relations(staffRolePermissions, ({
|
||||||
role: one(staffRoles, { fields: [staffRolePermissions.staffRoleId], references: [staffRoles.id] }),
|
role: one(staffRoles, { fields: [staffRolePermissions.staffRoleId], references: [staffRoles.id] }),
|
||||||
permission: one(staffPermissions, { fields: [staffRolePermissions.staffPermissionId], references: [staffPermissions.id] }),
|
permission: one(staffPermissions, { fields: [staffRolePermissions.staffPermissionId], references: [staffPermissions.id] }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const userIncidentsRelations = relations(userIncidents, ({ one }) => ({
|
||||||
|
user: one(users, { fields: [userIncidents.userId], references: [users.id] }),
|
||||||
|
order: one(orders, { fields: [userIncidents.orderId], references: [orders.id] }),
|
||||||
|
addedBy: one(staffUsers, { fields: [userIncidents.addedBy], references: [staffUsers.id] }),
|
||||||
|
}));
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { protectedProcedure } from '../trpc-index';
|
import { protectedProcedure } from '../trpc-index';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { db } from '../../db/db_index';
|
import { db } from '../../db/db_index';
|
||||||
import { users, complaints, orders, orderItems, notifCreds, unloggedUserTokens, userDetails } from '../../db/schema';
|
import { users, complaints, orders, orderItems, notifCreds, unloggedUserTokens, userDetails, userIncidents } from '../../db/schema';
|
||||||
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
|
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
|
||||||
import { ApiError } from '../../lib/api-error';
|
import { ApiError } from '../../lib/api-error';
|
||||||
import { notificationQueue } from '../../lib/notif-job';
|
import { notificationQueue } from '../../lib/notif-job';
|
||||||
|
|
@ -418,4 +418,76 @@ export const userRouter = {
|
||||||
message: `Notification queued for ${queuedCount} users`,
|
message: `Notification queued for ${queuedCount} users`,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
|
||||||
|
getUserIncidents: protectedProcedure
|
||||||
|
.input(z.object({
|
||||||
|
userId: z.number(),
|
||||||
|
}))
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { userId } = input;
|
||||||
|
|
||||||
|
const incidents = await db.query.userIncidents.findMany({
|
||||||
|
where: eq(userIncidents.userId, userId),
|
||||||
|
with: {
|
||||||
|
order: {
|
||||||
|
with: {
|
||||||
|
orderStatus: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addedBy: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(userIncidents.dateAdded),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
incidents: incidents.map(incident => ({
|
||||||
|
id: incident.id,
|
||||||
|
userId: incident.userId,
|
||||||
|
orderId: incident.orderId,
|
||||||
|
dateAdded: incident.dateAdded,
|
||||||
|
adminComment: incident.adminComment,
|
||||||
|
addedBy: incident.addedBy?.name || 'Unknown',
|
||||||
|
negativityScore: incident.negativityScore,
|
||||||
|
orderStatus: incident.order?.orderStatus?.[0]?.isCancelled ? 'cancelled' : 'active',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
addUserIncident: protectedProcedure
|
||||||
|
.input(z.object({
|
||||||
|
userId: z.number(),
|
||||||
|
orderId: z.number().optional(),
|
||||||
|
adminComment: z.string().optional(),
|
||||||
|
negativityScore: z.number().optional(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { userId, orderId, adminComment, negativityScore } = input;
|
||||||
|
|
||||||
|
const adminUserId = ctx.staffUser?.id;
|
||||||
|
|
||||||
|
if (!adminUserId) {
|
||||||
|
throw new ApiError('Admin user not authenticated', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const incidentObj = {userId, orderId, adminComment, addedBy: adminUserId, negativityScore}
|
||||||
|
|
||||||
|
const [incident] = await db.insert(userIncidents)
|
||||||
|
.values(
|
||||||
|
// {
|
||||||
|
// userId,
|
||||||
|
// orderId,
|
||||||
|
// adminComment,
|
||||||
|
// addedBy: adminUserId,
|
||||||
|
// negativityScore,
|
||||||
|
// }
|
||||||
|
{...incidentObj}
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: incident,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue