mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 23:22:54 +00:00
new shop calcs
This commit is contained in:
parent
064a10578b
commit
c9643e3df4
7 changed files with 98 additions and 7 deletions
9
backend/dist/index.js
vendored
9
backend/dist/index.js
vendored
|
|
@ -32329,6 +32329,7 @@ var shopOrdersTable = pgTable("shop_orders", {
|
|||
shippingAddress: text("shipping_address"),
|
||||
phone: varchar(),
|
||||
notes: text(),
|
||||
trackingNumber: varchar("tracking_number"),
|
||||
isFulfilled: boolean("is_fulfilled").notNull().default(false),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
||||
|
|
@ -33203,6 +33204,7 @@ shop.get("/orders", async ({ headers }) => {
|
|||
status: shopOrdersTable.status,
|
||||
orderType: shopOrdersTable.orderType,
|
||||
shippingAddress: shopOrdersTable.shippingAddress,
|
||||
trackingNumber: shopOrdersTable.trackingNumber,
|
||||
isFulfilled: shopOrdersTable.isFulfilled,
|
||||
createdAt: shopOrdersTable.createdAt,
|
||||
itemId: shopItemsTable.id,
|
||||
|
|
@ -35497,6 +35499,7 @@ admin.get("/orders", async ({ headers, query, status: status2 }) => {
|
|||
status: shopOrdersTable.status,
|
||||
orderType: shopOrdersTable.orderType,
|
||||
notes: shopOrdersTable.notes,
|
||||
trackingNumber: shopOrdersTable.trackingNumber,
|
||||
isFulfilled: shopOrdersTable.isFulfilled,
|
||||
shippingAddress: shopOrdersTable.shippingAddress,
|
||||
phone: shopOrdersTable.phone,
|
||||
|
|
@ -35526,7 +35529,8 @@ admin.patch("/orders/:id", async ({ params, body, headers, status: status2 }) =>
|
|||
const {
|
||||
status: orderStatus,
|
||||
notes,
|
||||
isFulfilled
|
||||
isFulfilled,
|
||||
trackingNumber
|
||||
} = body;
|
||||
const validStatuses = [
|
||||
"pending",
|
||||
|
|
@ -35544,6 +35548,8 @@ admin.patch("/orders/:id", async ({ params, body, headers, status: status2 }) =>
|
|||
updateData.status = orderStatus;
|
||||
if (notes !== undefined)
|
||||
updateData.notes = notes;
|
||||
if (trackingNumber !== undefined)
|
||||
updateData.trackingNumber = trackingNumber;
|
||||
if (isFulfilled !== undefined)
|
||||
updateData.isFulfilled = isFulfilled;
|
||||
const updated = await db.update(shopOrdersTable).set(updateData).where(eq(shopOrdersTable.id, parseInt(params.id))).returning({
|
||||
|
|
@ -35554,6 +35560,7 @@ admin.patch("/orders/:id", async ({ params, body, headers, status: status2 }) =>
|
|||
status: shopOrdersTable.status,
|
||||
orderType: shopOrdersTable.orderType,
|
||||
notes: shopOrdersTable.notes,
|
||||
trackingNumber: shopOrdersTable.trackingNumber,
|
||||
isFulfilled: shopOrdersTable.isFulfilled,
|
||||
shippingAddress: shopOrdersTable.shippingAddress,
|
||||
createdAt: shopOrdersTable.createdAt
|
||||
|
|
|
|||
1
backend/drizzle/0015_add_tracking_number.sql
Normal file
1
backend/drizzle/0015_add_tracking_number.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "shop_orders" ADD COLUMN "tracking_number" varchar;
|
||||
|
|
@ -2140,6 +2140,7 @@ admin.get("/orders", async ({ headers, query, status }) => {
|
|||
status: shopOrdersTable.status,
|
||||
orderType: shopOrdersTable.orderType,
|
||||
notes: shopOrdersTable.notes,
|
||||
trackingNumber: shopOrdersTable.trackingNumber,
|
||||
isFulfilled: shopOrdersTable.isFulfilled,
|
||||
shippingAddress: shopOrdersTable.shippingAddress,
|
||||
phone: shopOrdersTable.phone,
|
||||
|
|
@ -2183,7 +2184,8 @@ admin.patch("/orders/:id", async ({ params, body, headers, status }) => {
|
|||
status: orderStatus,
|
||||
notes,
|
||||
isFulfilled,
|
||||
} = body as { status?: string; notes?: string; isFulfilled?: boolean };
|
||||
trackingNumber,
|
||||
} = body as { status?: string; notes?: string; isFulfilled?: boolean; trackingNumber?: string };
|
||||
|
||||
const validStatuses = [
|
||||
"pending",
|
||||
|
|
@ -2200,6 +2202,7 @@ admin.patch("/orders/:id", async ({ params, body, headers, status }) => {
|
|||
const updateData: Record<string, unknown> = { updatedAt: new Date() };
|
||||
if (orderStatus) updateData.status = orderStatus;
|
||||
if (notes !== undefined) updateData.notes = notes;
|
||||
if (trackingNumber !== undefined) updateData.trackingNumber = trackingNumber;
|
||||
if (isFulfilled !== undefined) updateData.isFulfilled = isFulfilled;
|
||||
|
||||
const updated = await db
|
||||
|
|
@ -2214,6 +2217,7 @@ admin.patch("/orders/:id", async ({ params, body, headers, status }) => {
|
|||
status: shopOrdersTable.status,
|
||||
orderType: shopOrdersTable.orderType,
|
||||
notes: shopOrdersTable.notes,
|
||||
trackingNumber: shopOrdersTable.trackingNumber,
|
||||
isFulfilled: shopOrdersTable.isFulfilled,
|
||||
shippingAddress: shopOrdersTable.shippingAddress,
|
||||
createdAt: shopOrdersTable.createdAt,
|
||||
|
|
|
|||
|
|
@ -499,6 +499,7 @@ shop.get("/orders", async ({ headers }) => {
|
|||
status: shopOrdersTable.status,
|
||||
orderType: shopOrdersTable.orderType,
|
||||
shippingAddress: shopOrdersTable.shippingAddress,
|
||||
trackingNumber: shopOrdersTable.trackingNumber,
|
||||
isFulfilled: shopOrdersTable.isFulfilled,
|
||||
createdAt: shopOrdersTable.createdAt,
|
||||
itemId: shopItemsTable.id,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export const shopOrdersTable = pgTable("shop_orders", {
|
|||
shippingAddress: text("shipping_address"),
|
||||
phone: varchar(),
|
||||
notes: text(),
|
||||
trackingNumber: varchar("tracking_number"),
|
||||
isFulfilled: boolean("is_fulfilled").notNull().default(false),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
status: string;
|
||||
orderType: string;
|
||||
notes: string | null;
|
||||
trackingNumber: string | null;
|
||||
isFulfilled: boolean;
|
||||
shippingAddress: string | null;
|
||||
phone: string | null;
|
||||
|
|
@ -82,9 +83,19 @@
|
|||
let actionLoading = $state(false);
|
||||
let expandedOrders = $state<Record<number, boolean>>({});
|
||||
let collapsedGroups = $state<Record<string, boolean>>({});
|
||||
let trackingInputs = $state<Record<number, string>>({});
|
||||
let lastDeleted = $state<Order | null>(null);
|
||||
let lastDeletedTimer = $state<number | null>(null);
|
||||
let lastDeletedError = $state<string | null>(null);
|
||||
let filterItem = $state('');
|
||||
let filterUser = $state('');
|
||||
|
||||
let uniqueItems = $derived(
|
||||
[...new Map(orders.map((o) => [o.itemName, o.itemName])).values()].sort()
|
||||
);
|
||||
let uniqueUsers = $derived(
|
||||
[...new Map(orders.map((o) => [o.userId, o.username])).values()].sort()
|
||||
);
|
||||
|
||||
let filteredOrders = $derived.by(() => {
|
||||
let result =
|
||||
|
|
@ -94,6 +105,14 @@
|
|||
? orders.filter((o) => !o.isFulfilled)
|
||||
: orders.filter((o) => o.isFulfilled);
|
||||
|
||||
if (filterItem) {
|
||||
result = result.filter((o) => o.itemName === filterItem);
|
||||
}
|
||||
|
||||
if (filterUser) {
|
||||
result = result.filter((o) => o.username === filterUser);
|
||||
}
|
||||
|
||||
if (searchQuery.trim()) {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
result = result.filter((o) => {
|
||||
|
|
@ -181,14 +200,23 @@
|
|||
async function toggleFulfilled(order: Order) {
|
||||
actionLoading = true;
|
||||
try {
|
||||
const patchBody: Record<string, unknown> = { isFulfilled: !order.isFulfilled };
|
||||
// Include tracking number when fulfilling (not when unfulfilling)
|
||||
if (!order.isFulfilled) {
|
||||
const tracking = trackingInputs[order.id]?.trim() || null;
|
||||
patchBody.trackingNumber = tracking;
|
||||
}
|
||||
const response = await fetch(`${API_URL}/admin/orders/${order.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ isFulfilled: !order.isFulfilled })
|
||||
body: JSON.stringify(patchBody)
|
||||
});
|
||||
if (response.ok) {
|
||||
orders = orders.map((o) => (o.id === order.id ? { ...o, isFulfilled: !o.isFulfilled } : o));
|
||||
const trackingValue = !order.isFulfilled ? (trackingInputs[order.id]?.trim() || null) : order.trackingNumber;
|
||||
orders = orders.map((o) =>
|
||||
o.id === order.id ? { ...o, isFulfilled: !o.isFulfilled, trackingNumber: trackingValue } : o
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to update order:', e);
|
||||
|
|
@ -381,6 +409,32 @@
|
|||
class="w-full rounded-full border-4 border-black py-2 pr-4 pl-10 font-bold transition-all duration-200 placeholder:text-gray-400 focus:border-dashed focus:ring-0 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label for="filter-item" class="mb-1 text-xs font-bold text-gray-500 uppercase">item</label>
|
||||
<select
|
||||
id="filter-item"
|
||||
bind:value={filterItem}
|
||||
class="cursor-pointer rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 focus:border-dashed focus:outline-none {filterItem ? 'bg-black text-white' : ''}"
|
||||
>
|
||||
<option value="">all items</option>
|
||||
{#each uniqueItems as item}
|
||||
<option value={item}>{item}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label for="filter-user" class="mb-1 text-xs font-bold text-gray-500 uppercase">user</label>
|
||||
<select
|
||||
id="filter-user"
|
||||
bind:value={filterUser}
|
||||
class="cursor-pointer rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 focus:border-dashed focus:outline-none {filterUser ? 'bg-black text-white' : ''}"
|
||||
>
|
||||
<option value="">all users</option>
|
||||
{#each uniqueUsers as username}
|
||||
<option value={username}>@{username}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<div class="flex flex-col">
|
||||
<label for="date-from" class="mb-1 text-xs font-bold text-gray-500 uppercase">from</label>
|
||||
|
|
@ -400,14 +454,16 @@
|
|||
class="rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 focus:border-dashed focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
{#if dateFrom || dateTo}
|
||||
{#if dateFrom || dateTo || filterItem || filterUser}
|
||||
<button
|
||||
onclick={() => {
|
||||
dateFrom = '';
|
||||
dateTo = '';
|
||||
filterItem = '';
|
||||
filterUser = '';
|
||||
}}
|
||||
class="cursor-pointer rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
||||
title="clear dates"
|
||||
title="clear filters"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
|
|
@ -600,6 +656,19 @@
|
|||
|
||||
<!-- Actions -->
|
||||
<div class="flex shrink-0 flex-col gap-3 md:w-48">
|
||||
{#if !order.isFulfilled}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="tracking # (optional)"
|
||||
bind:value={trackingInputs[order.id]}
|
||||
class="w-full rounded-lg border-2 border-black px-3 py-2 text-sm font-bold transition-all duration-200 placeholder:text-gray-400 focus:border-dashed focus:outline-none"
|
||||
/>
|
||||
{:else if order.trackingNumber}
|
||||
<div class="rounded-lg border-2 border-gray-300 bg-gray-50 px-3 py-2 text-sm">
|
||||
<p class="text-xs font-bold text-gray-500 uppercase">tracking</p>
|
||||
<p class="font-bold break-all">{order.trackingNumber}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
onclick={() => toggleFulfilled(order)}
|
||||
class="flex w-full cursor-pointer items-center justify-center gap-2 rounded-full border-4 border-black px-4 py-2 font-bold transition-all duration-200 {order.isFulfilled
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
status: string;
|
||||
orderType: string;
|
||||
shippingAddress: string | null;
|
||||
trackingNumber: string | null;
|
||||
isFulfilled: boolean;
|
||||
createdAt: string;
|
||||
itemId: number;
|
||||
|
|
@ -256,13 +257,20 @@
|
|||
{#if order.shippingAddress}
|
||||
<div class="mt-3 flex items-start gap-2 text-sm text-gray-600">
|
||||
<MapPin size={16} class="mt-0.5 shrink-0" />
|
||||
<span class="break-words">{formatAddress(order.shippingAddress)}</span>
|
||||
<span class="wrap-break-word">{formatAddress(order.shippingAddress)}</span>
|
||||
</div>
|
||||
{:else if !order.isFulfilled}
|
||||
<p class="mt-3 text-sm font-bold text-yellow-600">
|
||||
{$t.orders.noShippingAddress}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if order.trackingNumber}
|
||||
<div class="mt-3 flex items-start gap-2 text-sm text-gray-600">
|
||||
<Truck size={16} class="mt-0.5 shrink-0" />
|
||||
<span class="font-bold">tracking: {order.trackingNumber}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue