Rename Forte to LunarFront, generalize for any small business
Rebrand from Forte (music-store-specific) to LunarFront (any small business): - Package namespace @forte/* → @lunarfront/* - Database forte/forte_test → lunarfront/lunarfront_test - Docker containers, volumes, connection strings - UI branding, localStorage keys, test emails - All documentation and planning docs Generalize music-specific terminology: - instrumentDescription → itemDescription - instrumentCount → itemCount - instrumentType → itemCategory (on service templates) - New migration 0027_generalize_terminology for column renames - Seed data updated with generic examples - RBAC descriptions updated
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { Account } from '@/types/account'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const accountKeys = {
|
||||
all: ['accounts'] as const,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { MemberIdentifier } from '@/types/account'
|
||||
import type { PaginatedResponse } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const identifierKeys = {
|
||||
all: (memberId: string) => ['members', memberId, 'identifiers'] as const,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { Member } from '@/types/account'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
interface MemberWithAccount extends Member {
|
||||
accountName: string | null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { PaymentMethod } from '@/types/account'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const paymentMethodKeys = {
|
||||
all: (accountId: string) => ['accounts', accountId, 'payment-methods'] as const,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { ProcessorLink } from '@/types/account'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const processorLinkKeys = {
|
||||
all: (accountId: string) => ['accounts', accountId, 'processor-links'] as const,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { Permission, Role } from '@/types/rbac'
|
||||
import type { PaginationInput, PaginatedResponse } from '@forte/shared/schemas'
|
||||
import type { PaginationInput, PaginatedResponse } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const rbacKeys = {
|
||||
permissions: ['permissions'] as const,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { RepairTicket, RepairLineItem, RepairBatch, RepairNote, RepairServiceTemplate } from '@/types/repair'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
// --- Repair Tickets ---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { StorageFolder, StorageFolderPermission, StorageFile } from '@/types/storage'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
// --- Folders ---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { TaxExemption } from '@/types/account'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const taxExemptionKeys = {
|
||||
all: (accountId: string) => ['accounts', accountId, 'tax-exemptions'] as const,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { PaginationInput, PaginatedResponse } from '@forte/shared/schemas'
|
||||
import type { PaginationInput, PaginatedResponse } from '@lunarfront/shared/schemas'
|
||||
|
||||
export interface UserRole {
|
||||
id: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import type { VaultStatus, VaultCategory, VaultCategoryPermission, VaultEntry } from '@/types/vault'
|
||||
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
// --- Keys ---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
import { AccountCreateSchema } from '@forte/shared/schemas'
|
||||
import { AccountCreateSchema } from '@lunarfront/shared/schemas'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PaymentMethodCreateSchema } from '@forte/shared/schemas'
|
||||
import type { PaymentMethodCreateInput } from '@forte/shared/schemas'
|
||||
import { PaymentMethodCreateSchema } from '@lunarfront/shared/schemas'
|
||||
import type { PaymentMethodCreateInput } from '@lunarfront/shared/schemas'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { ProcessorLinkCreateSchema } from '@forte/shared/schemas'
|
||||
import type { ProcessorLinkCreateInput } from '@forte/shared/schemas'
|
||||
import { ProcessorLinkCreateSchema } from '@lunarfront/shared/schemas'
|
||||
import type { ProcessorLinkCreateInput } from '@lunarfront/shared/schemas'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { TaxExemptionCreateSchema } from '@forte/shared/schemas'
|
||||
import type { TaxExemptionCreateInput } from '@forte/shared/schemas'
|
||||
import { TaxExemptionCreateSchema } from '@lunarfront/shared/schemas'
|
||||
import type { TaxExemptionCreateInput } from '@lunarfront/shared/schemas'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
@@ -24,7 +24,7 @@ interface GeneratePdfOptions {
|
||||
companyName?: string
|
||||
}
|
||||
|
||||
export function generateRepairPdf({ ticket, lineItems, notes, includeNotes, companyName = 'Forte Music' }: GeneratePdfOptions): jsPDF {
|
||||
export function generateRepairPdf({ ticket, lineItems, notes, includeNotes, companyName = 'LunarFront' }: GeneratePdfOptions): jsPDF {
|
||||
const doc = new jsPDF()
|
||||
let y = 20
|
||||
|
||||
@@ -57,11 +57,11 @@ export function generateRepairPdf({ ticket, lineItems, notes, includeNotes, comp
|
||||
doc.setFontSize(10)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text('Customer', 14, y)
|
||||
doc.text('Instrument', 110, y)
|
||||
doc.text('Item', 110, y)
|
||||
y += 5
|
||||
doc.setFont('helvetica', 'normal')
|
||||
doc.text(ticket.customerName, 14, y)
|
||||
doc.text(ticket.instrumentDescription ?? '-', 110, y)
|
||||
doc.text(ticket.itemDescription ?? '-', 110, y)
|
||||
y += 5
|
||||
if (ticket.customerPhone) { doc.text(ticket.customerPhone, 14, y); y += 5 }
|
||||
if (ticket.serialNumber) { doc.text(`S/N: ${ticket.serialNumber}`, 110, y - 5) }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useNavigate, useSearch } from '@tanstack/react-router'
|
||||
import type { PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
interface PaginationSearch {
|
||||
page?: number
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Route as AuthenticatedUsersRouteImport } from './routes/_authenticated/
|
||||
import { Route as AuthenticatedSettingsRouteImport } from './routes/_authenticated/settings'
|
||||
import { Route as AuthenticatedProfileRouteImport } from './routes/_authenticated/profile'
|
||||
import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help'
|
||||
import { Route as AuthenticatedVaultIndexRouteImport } from './routes/_authenticated/vault/index'
|
||||
import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index'
|
||||
import { Route as AuthenticatedRepairsIndexRouteImport } from './routes/_authenticated/repairs/index'
|
||||
import { Route as AuthenticatedRepairBatchesIndexRouteImport } from './routes/_authenticated/repair-batches/index'
|
||||
@@ -72,6 +73,11 @@ const AuthenticatedHelpRoute = AuthenticatedHelpRouteImport.update({
|
||||
path: '/help',
|
||||
getParentRoute: () => AuthenticatedRoute,
|
||||
} as any)
|
||||
const AuthenticatedVaultIndexRoute = AuthenticatedVaultIndexRouteImport.update({
|
||||
id: '/vault/',
|
||||
path: '/vault/',
|
||||
getParentRoute: () => AuthenticatedRoute,
|
||||
} as any)
|
||||
const AuthenticatedRolesIndexRoute = AuthenticatedRolesIndexRouteImport.update({
|
||||
id: '/roles/',
|
||||
path: '/roles/',
|
||||
@@ -218,6 +224,7 @@ export interface FileRoutesByFullPath {
|
||||
'/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
|
||||
'/repairs/': typeof AuthenticatedRepairsIndexRoute
|
||||
'/roles/': typeof AuthenticatedRolesIndexRoute
|
||||
'/vault/': typeof AuthenticatedVaultIndexRoute
|
||||
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
|
||||
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
||||
'/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
||||
@@ -246,6 +253,7 @@ export interface FileRoutesByTo {
|
||||
'/repair-batches': typeof AuthenticatedRepairBatchesIndexRoute
|
||||
'/repairs': typeof AuthenticatedRepairsIndexRoute
|
||||
'/roles': typeof AuthenticatedRolesIndexRoute
|
||||
'/vault': typeof AuthenticatedVaultIndexRoute
|
||||
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
|
||||
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
||||
'/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
||||
@@ -277,6 +285,7 @@ export interface FileRoutesById {
|
||||
'/_authenticated/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
|
||||
'/_authenticated/repairs/': typeof AuthenticatedRepairsIndexRoute
|
||||
'/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute
|
||||
'/_authenticated/vault/': typeof AuthenticatedVaultIndexRoute
|
||||
'/_authenticated/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
|
||||
'/_authenticated/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
||||
'/_authenticated/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
||||
@@ -308,6 +317,7 @@ export interface FileRouteTypes {
|
||||
| '/repair-batches/'
|
||||
| '/repairs/'
|
||||
| '/roles/'
|
||||
| '/vault/'
|
||||
| '/accounts/$accountId/members'
|
||||
| '/accounts/$accountId/payment-methods'
|
||||
| '/accounts/$accountId/processor-links'
|
||||
@@ -336,6 +346,7 @@ export interface FileRouteTypes {
|
||||
| '/repair-batches'
|
||||
| '/repairs'
|
||||
| '/roles'
|
||||
| '/vault'
|
||||
| '/accounts/$accountId/members'
|
||||
| '/accounts/$accountId/payment-methods'
|
||||
| '/accounts/$accountId/processor-links'
|
||||
@@ -366,6 +377,7 @@ export interface FileRouteTypes {
|
||||
| '/_authenticated/repair-batches/'
|
||||
| '/_authenticated/repairs/'
|
||||
| '/_authenticated/roles/'
|
||||
| '/_authenticated/vault/'
|
||||
| '/_authenticated/accounts/$accountId/members'
|
||||
| '/_authenticated/accounts/$accountId/payment-methods'
|
||||
| '/_authenticated/accounts/$accountId/processor-links'
|
||||
@@ -429,6 +441,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedHelpRouteImport
|
||||
parentRoute: typeof AuthenticatedRoute
|
||||
}
|
||||
'/_authenticated/vault/': {
|
||||
id: '/_authenticated/vault/'
|
||||
path: '/vault'
|
||||
fullPath: '/vault/'
|
||||
preLoaderRoute: typeof AuthenticatedVaultIndexRouteImport
|
||||
parentRoute: typeof AuthenticatedRoute
|
||||
}
|
||||
'/_authenticated/roles/': {
|
||||
id: '/_authenticated/roles/'
|
||||
path: '/roles'
|
||||
@@ -628,6 +647,7 @@ interface AuthenticatedRouteChildren {
|
||||
AuthenticatedRepairBatchesIndexRoute: typeof AuthenticatedRepairBatchesIndexRoute
|
||||
AuthenticatedRepairsIndexRoute: typeof AuthenticatedRepairsIndexRoute
|
||||
AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute
|
||||
AuthenticatedVaultIndexRoute: typeof AuthenticatedVaultIndexRoute
|
||||
}
|
||||
|
||||
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
||||
@@ -654,6 +674,7 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
||||
AuthenticatedRepairBatchesIndexRoute: AuthenticatedRepairBatchesIndexRoute,
|
||||
AuthenticatedRepairsIndexRoute: AuthenticatedRepairsIndexRoute,
|
||||
AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute,
|
||||
AuthenticatedVaultIndexRoute: AuthenticatedVaultIndexRoute,
|
||||
}
|
||||
|
||||
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
|
||||
|
||||
@@ -34,7 +34,7 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
|
||||
const ticketColumns: Column<RepairTicket>[] = [
|
||||
{ key: 'ticket_number', header: 'Ticket #', sortable: true, render: (t) => <span className="font-mono text-sm">{t.ticketNumber}</span> },
|
||||
{ key: 'instrument', header: 'Instrument', render: (t) => <>{t.instrumentDescription ?? '-'}</> },
|
||||
{ key: 'item_description', header: 'Item', render: (t) => <>{t.itemDescription ?? '-'}</> },
|
||||
{ key: 'problem', header: 'Problem', render: (t) => <span className="truncate max-w-[200px] block">{t.problemDescription}</span> },
|
||||
{ key: 'status', header: 'Status', sortable: true, render: (t) => <Badge variant="outline">{STATUS_LABELS[t.status] ?? t.status}</Badge> },
|
||||
{
|
||||
@@ -111,7 +111,7 @@ function RepairBatchDetailPage() {
|
||||
|
||||
doc.setFontSize(18)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text('Forte Music', 14, y)
|
||||
doc.text('LunarFront', 14, y)
|
||||
y += 8
|
||||
doc.setFontSize(12)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
@@ -169,7 +169,7 @@ function RepairBatchDetailPage() {
|
||||
doc.setFillColor(245, 245, 245)
|
||||
doc.rect(14, y - 3, 182, 6, 'F')
|
||||
doc.text('Ticket #', 16, y)
|
||||
doc.text('Instrument', 40, y)
|
||||
doc.text('Item', 40, y)
|
||||
doc.text('Problem', 100, y)
|
||||
doc.text('Status', 155, y)
|
||||
doc.text('Estimate', 190, y, { align: 'right' })
|
||||
@@ -179,7 +179,7 @@ function RepairBatchDetailPage() {
|
||||
for (const ticket of tickets) {
|
||||
if (y > 270) { doc.addPage(); y = 20 }
|
||||
doc.text(ticket.ticketNumber ?? '-', 16, y)
|
||||
doc.text((ticket.instrumentDescription ?? '-').slice(0, 30), 40, y)
|
||||
doc.text((ticket.itemDescription ?? '-').slice(0, 30), 40, y)
|
||||
doc.text(ticket.problemDescription.slice(0, 28), 100, y)
|
||||
doc.text(STATUS_LABELS[ticket.status] ?? ticket.status, 155, y)
|
||||
doc.text(ticket.estimatedCost ? `$${ticket.estimatedCost}` : '-', 190, y, { align: 'right' })
|
||||
|
||||
@@ -49,9 +49,9 @@ const columns: Column<RepairBatch>[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'instruments',
|
||||
header: 'Instruments',
|
||||
render: (b) => <>{b.receivedCount}/{b.instrumentCount}</>,
|
||||
key: 'items',
|
||||
header: 'Items',
|
||||
render: (b) => <>{b.receivedCount}/{b.itemCount}</>,
|
||||
},
|
||||
{
|
||||
key: 'due_date',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, useNavigate, Link } from '@tanstack/react-router'
|
||||
import { useQuery, useMutation } from '@tanstack/react-query'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { RepairBatchCreateSchema } from '@forte/shared/schemas'
|
||||
import { RepairBatchCreateSchema } from '@lunarfront/shared/schemas'
|
||||
import { repairBatchMutations } from '@/api/repairs'
|
||||
import { accountListOptions } from '@/api/accounts'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -167,7 +167,7 @@ function NewRepairBatchPage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Notes</Label>
|
||||
<Textarea {...register('notes')} rows={3} placeholder="e.g. Annual instrument checkup, multiple guitars needing setups" />
|
||||
<Textarea {...register('notes')} rows={3} placeholder="e.g. Annual checkup, multiple items needing service" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -118,7 +118,7 @@ function RepairTicketDetailPage() {
|
||||
setEditFields({
|
||||
customerName: ticket!.customerName,
|
||||
customerPhone: ticket!.customerPhone ?? '',
|
||||
instrumentDescription: ticket!.instrumentDescription ?? '',
|
||||
itemDescription: ticket!.itemDescription ?? '',
|
||||
serialNumber: ticket!.serialNumber ?? '',
|
||||
conditionIn: ticket!.conditionIn ?? '',
|
||||
conditionInNotes: ticket!.conditionInNotes ?? '',
|
||||
@@ -134,7 +134,7 @@ function RepairTicketDetailPage() {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (editFields.customerName !== ticket!.customerName) data.customerName = editFields.customerName
|
||||
if (editFields.customerPhone !== (ticket!.customerPhone ?? '')) data.customerPhone = editFields.customerPhone || undefined
|
||||
if (editFields.instrumentDescription !== (ticket!.instrumentDescription ?? '')) data.instrumentDescription = editFields.instrumentDescription || undefined
|
||||
if (editFields.itemDescription !== (ticket!.itemDescription ?? '')) data.itemDescription = editFields.itemDescription || undefined
|
||||
if (editFields.serialNumber !== (ticket!.serialNumber ?? '')) data.serialNumber = editFields.serialNumber || undefined
|
||||
if (editFields.conditionIn !== (ticket!.conditionIn ?? '')) data.conditionIn = editFields.conditionIn || undefined
|
||||
if (editFields.conditionInNotes !== (ticket!.conditionInNotes ?? '')) data.conditionInNotes = editFields.conditionInNotes || undefined
|
||||
@@ -180,7 +180,7 @@ function RepairTicketDetailPage() {
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<h1 className="text-2xl font-bold">Ticket #{ticket.ticketNumber}</h1>
|
||||
<p className="text-sm text-muted-foreground">{ticket.customerName} — {ticket.instrumentDescription ?? 'No instrument'}</p>
|
||||
<p className="text-sm text-muted-foreground">{ticket.customerName} — {ticket.itemDescription ?? 'No item description'}</p>
|
||||
</div>
|
||||
<PdfModal ticket={ticket} lineItems={lineItemsData?.data ?? []} ticketId={ticketId} />
|
||||
</div>
|
||||
@@ -278,7 +278,7 @@ function RepairTicketDetailPage() {
|
||||
<div className="space-y-2"><Label>Phone</Label><Input value={editFields.customerPhone} onChange={(e) => setEditFields((p) => ({ ...p, customerPhone: e.target.value }))} /></div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2"><Label>Instrument</Label><Input value={editFields.instrumentDescription} onChange={(e) => setEditFields((p) => ({ ...p, instrumentDescription: e.target.value }))} /></div>
|
||||
<div className="space-y-2"><Label>Item Description</Label><Input value={editFields.itemDescription} onChange={(e) => setEditFields((p) => ({ ...p, itemDescription: e.target.value }))} /></div>
|
||||
<div className="space-y-2"><Label>Serial Number</Label><Input value={editFields.serialNumber} onChange={(e) => setEditFields((p) => ({ ...p, serialNumber: e.target.value }))} /></div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
@@ -310,7 +310,7 @@ function RepairTicketDetailPage() {
|
||||
<div><span className="text-muted-foreground">Account:</span> {ticket.accountId ?? 'Walk-in'}</div>
|
||||
</div>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div><span className="text-muted-foreground">Instrument:</span> {ticket.instrumentDescription ?? '-'}</div>
|
||||
<div><span className="text-muted-foreground">Item:</span> {ticket.itemDescription ?? '-'}</div>
|
||||
<div><span className="text-muted-foreground">Serial:</span> {ticket.serialNumber ?? '-'}</div>
|
||||
<div><span className="text-muted-foreground">Condition:</span> {ticket.conditionIn ?? '-'}</div>
|
||||
</div>
|
||||
@@ -411,8 +411,8 @@ function AddLineItemDialog({ ticketId, open, onOpenChange }: { ticketId: string;
|
||||
setDescription(''); setQty('1'); setUnitPrice('0'); setCost(''); setItemType('labor'); setTemplateSearch(''); setShowTemplates(false)
|
||||
}
|
||||
|
||||
function selectTemplate(template: { name: string; instrumentType: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
|
||||
const desc = [template.name, template.instrumentType, template.size].filter(Boolean).join(' — ')
|
||||
function selectTemplate(template: { name: string; itemCategory: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
|
||||
const desc = [template.name, template.itemCategory, template.size].filter(Boolean).join(' — ')
|
||||
setDescription(desc); setItemType(template.itemType); setUnitPrice(template.defaultPrice); setCost(template.defaultCost ?? ''); setShowTemplates(false); setTemplateSearch('')
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ function AddLineItemDialog({ ticketId, open, onOpenChange }: { ticketId: string;
|
||||
<div className="absolute z-50 mt-1 w-full rounded-md border bg-popover shadow-lg max-h-48 overflow-auto">
|
||||
{templates.length === 0 ? <div className="p-3 text-sm text-muted-foreground">No templates found</div> : templates.map((t) => (
|
||||
<button key={t.id} type="button" className="w-full text-left px-3 py-2 text-sm hover:bg-accent flex justify-between" onClick={() => selectTemplate(t)}>
|
||||
<span>{t.name}{t.instrumentType ? ` — ${t.instrumentType}` : ''}{t.size ? ` ${t.size}` : ''}</span>
|
||||
<span>{t.name}{t.itemCategory ? ` — ${t.itemCategory}` : ''}{t.size ? ` ${t.size}` : ''}</span>
|
||||
<span className="text-muted-foreground">${t.defaultPrice}</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Plus, Search } from 'lucide-react'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import type { RepairTicket } from '@/types/repair'
|
||||
import type { PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/repairs/')({
|
||||
validateSearch: (search: Record<string, unknown>) => ({
|
||||
@@ -70,9 +70,9 @@ const columns: Column<RepairTicket>[] = [
|
||||
render: (t) => <span className="font-medium">{t.customerName}</span>,
|
||||
},
|
||||
{
|
||||
key: 'instrument',
|
||||
header: 'Instrument',
|
||||
render: (t) => <>{t.instrumentDescription ?? '-'}</>,
|
||||
key: 'item_description',
|
||||
header: 'Item',
|
||||
render: (t) => <>{t.itemDescription ?? '-'}</>,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, useNavigate, Link } from '@tanstack/react-router'
|
||||
import { useQuery, useMutation } from '@tanstack/react-query'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { RepairTicketCreateSchema } from '@forte/shared/schemas'
|
||||
import { RepairTicketCreateSchema } from '@lunarfront/shared/schemas'
|
||||
import { repairTicketMutations, repairLineItemMutations, repairServiceTemplateListOptions } from '@/api/repairs'
|
||||
import { accountListOptions } from '@/api/accounts'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
@@ -88,7 +88,7 @@ function NewRepairPage() {
|
||||
defaultValues: {
|
||||
customerName: linkedContactName ?? '',
|
||||
customerPhone: '',
|
||||
instrumentDescription: '',
|
||||
itemDescription: '',
|
||||
serialNumber: '',
|
||||
problemDescription: '',
|
||||
conditionIn: undefined,
|
||||
@@ -157,8 +157,8 @@ function NewRepairPage() {
|
||||
setValue('customerPhone', '')
|
||||
}
|
||||
|
||||
function addFromTemplate(template: { name: string; instrumentType: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
|
||||
const desc = [template.name, template.instrumentType, template.size].filter(Boolean).join(' — ')
|
||||
function addFromTemplate(template: { name: string; itemCategory: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
|
||||
const desc = [template.name, template.itemCategory, template.size].filter(Boolean).join(' — ')
|
||||
setLineItems((prev) => [...prev, {
|
||||
itemType: template.itemType,
|
||||
description: desc,
|
||||
@@ -297,14 +297,14 @@ function NewRepairPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Instrument Section */}
|
||||
{/* Item Section */}
|
||||
<Card>
|
||||
<CardHeader><CardTitle className="text-lg">Instrument</CardTitle></CardHeader>
|
||||
<CardHeader><CardTitle className="text-lg">Item</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Instrument Description</Label>
|
||||
<Input {...register('instrumentDescription')} placeholder="e.g. Yamaha Trumpet YTR-2330" />
|
||||
<Label>Item Description</Label>
|
||||
<Input {...register('itemDescription')} placeholder="e.g. Brand, model, description" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Serial Number</Label>
|
||||
@@ -364,7 +364,7 @@ function NewRepairPage() {
|
||||
) : (
|
||||
templates.map((t) => (
|
||||
<button key={t.id} type="button" className="w-full text-left px-3 py-2 text-sm hover:bg-accent flex justify-between" onClick={() => addFromTemplate(t)}>
|
||||
<span>{t.name}{t.instrumentType ? ` — ${t.instrumentType}` : ''}{t.size ? ` ${t.size}` : ''}</span>
|
||||
<span>{t.name}{t.itemCategory ? ` — ${t.itemCategory}` : ''}{t.size ? ` ${t.size}` : ''}</span>
|
||||
<span className="text-muted-foreground">${t.defaultPrice}</span>
|
||||
</button>
|
||||
))
|
||||
@@ -462,7 +462,7 @@ function NewRepairPage() {
|
||||
<Card>
|
||||
<CardHeader><CardTitle className="text-lg">Intake Photos</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">Optional — document instrument condition at intake</p>
|
||||
<p className="text-sm text-muted-foreground">Optional — document item condition at intake</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{photos.map((photo, i) => (
|
||||
<div key={i} className="relative group">
|
||||
|
||||
@@ -28,7 +28,7 @@ export const Route = createFileRoute('/_authenticated/repairs/templates')({
|
||||
|
||||
const columns: Column<RepairServiceTemplate>[] = [
|
||||
{ key: 'name', header: 'Name', sortable: true, render: (t) => <span className="font-medium">{t.name}</span> },
|
||||
{ key: 'instrument_type', header: 'Instrument', sortable: true, render: (t) => <>{t.instrumentType ?? '-'}</> },
|
||||
{ key: 'item_category', header: 'Item Category', sortable: true, render: (t) => <>{t.itemCategory ?? '-'}</> },
|
||||
{ key: 'size', header: 'Size', render: (t) => <>{t.size ?? '-'}</> },
|
||||
{ key: 'item_type', header: 'Type', render: (t) => <Badge variant="outline">{t.itemType.replace('_', ' ')}</Badge> },
|
||||
{ key: 'default_price', header: 'Price', sortable: true, render: (t) => <>${t.defaultPrice}</> },
|
||||
@@ -110,7 +110,7 @@ function RepairTemplatesPage() {
|
||||
function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
|
||||
const queryClient = useQueryClient()
|
||||
const [name, setName] = useState('')
|
||||
const [instrumentType, setInstrumentType] = useState('')
|
||||
const [itemCategory, setItemCategory] = useState('')
|
||||
const [size, setSize] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [itemType, setItemType] = useState('flat_rate')
|
||||
@@ -131,7 +131,7 @@ function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenCha
|
||||
|
||||
function resetForm() {
|
||||
setName('')
|
||||
setInstrumentType('')
|
||||
setItemCategory('')
|
||||
setSize('')
|
||||
setDescription('')
|
||||
setItemType('flat_rate')
|
||||
@@ -144,7 +144,7 @@ function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenCha
|
||||
e.preventDefault()
|
||||
mutation.mutate({
|
||||
name,
|
||||
instrumentType: instrumentType || undefined,
|
||||
itemCategory: itemCategory || undefined,
|
||||
size: size || undefined,
|
||||
description: description || undefined,
|
||||
itemType,
|
||||
@@ -168,8 +168,8 @@ function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenCha
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Instrument Type</Label>
|
||||
<Input value={instrumentType} onChange={(e) => setInstrumentType(e.target.value)} placeholder="e.g. Violin, Trumpet, Guitar" />
|
||||
<Label>Item Category</Label>
|
||||
<Input value={itemCategory} onChange={(e) => setItemCategory(e.target.value)} placeholder="e.g. Electronics, Appliances, Furniture" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Size</Label>
|
||||
|
||||
@@ -48,8 +48,8 @@ function LoginPage() {
|
||||
style={{ backgroundColor: '#131c2e', borderColor: '#1e2d45' }}
|
||||
>
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#d8dfe9' }}>Forte</h1>
|
||||
<p className="text-sm mt-1" style={{ color: '#6b7a8d' }}>Music Store Management</p>
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#d8dfe9' }}>LunarFront</h1>
|
||||
<p className="text-sm mt-1" style={{ color: '#6b7a8d' }}>Small Business Management</p>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -50,7 +50,7 @@ function expandPermissions(slugs: string[]): Set<string> {
|
||||
|
||||
function loadSession(): { token: string; user: User; permissions?: string[] } | null {
|
||||
try {
|
||||
const raw = sessionStorage.getItem('forte-auth')
|
||||
const raw = sessionStorage.getItem('lunarfront-auth')
|
||||
if (!raw) return null
|
||||
return JSON.parse(raw)
|
||||
} catch {
|
||||
@@ -59,11 +59,11 @@ function loadSession(): { token: string; user: User; permissions?: string[] } |
|
||||
}
|
||||
|
||||
function saveSession(token: string, user: User, permissions?: string[]) {
|
||||
sessionStorage.setItem('forte-auth', JSON.stringify({ token, user, permissions }))
|
||||
sessionStorage.setItem('lunarfront-auth', JSON.stringify({ token, user, permissions }))
|
||||
}
|
||||
|
||||
function clearSession() {
|
||||
sessionStorage.removeItem('forte-auth')
|
||||
sessionStorage.removeItem('lunarfront-auth')
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => {
|
||||
|
||||
@@ -22,8 +22,8 @@ function apply(mode: Mode, colorTheme: string) {
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeState>((set) => {
|
||||
const initialMode = (typeof window !== 'undefined' ? localStorage.getItem('forte-mode') as Mode : null) ?? 'system'
|
||||
const initialColor = (typeof window !== 'undefined' ? localStorage.getItem('forte-color-theme') : null) ?? 'slate'
|
||||
const initialMode = (typeof window !== 'undefined' ? localStorage.getItem('lunarfront-mode') as Mode : null) ?? 'system'
|
||||
const initialColor = (typeof window !== 'undefined' ? localStorage.getItem('lunarfront-color-theme') : null) ?? 'slate'
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
apply(initialMode, initialColor)
|
||||
@@ -34,14 +34,14 @@ export const useThemeStore = create<ThemeState>((set) => {
|
||||
colorTheme: initialColor,
|
||||
|
||||
setMode: (mode) => {
|
||||
localStorage.setItem('forte-mode', mode)
|
||||
localStorage.setItem('lunarfront-mode', mode)
|
||||
const colorTheme = useThemeStore.getState().colorTheme
|
||||
apply(mode, colorTheme)
|
||||
set({ mode })
|
||||
},
|
||||
|
||||
setColorTheme: (name) => {
|
||||
localStorage.setItem('forte-color-theme', name)
|
||||
localStorage.setItem('lunarfront-color-theme', name)
|
||||
const mode = useThemeStore.getState().mode
|
||||
apply(mode, name)
|
||||
set({ colorTheme: name })
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface RepairTicket {
|
||||
customerName: string
|
||||
customerPhone: string | null
|
||||
inventoryUnitId: string | null
|
||||
instrumentDescription: string | null
|
||||
itemDescription: string | null
|
||||
serialNumber: string | null
|
||||
conditionIn: 'excellent' | 'good' | 'fair' | 'poor' | null
|
||||
conditionInNotes: string | null
|
||||
@@ -55,7 +55,7 @@ export interface RepairBatch {
|
||||
dueDate: string | null
|
||||
completedDate: string | null
|
||||
deliveredDate: string | null
|
||||
instrumentCount: number
|
||||
itemCount: number
|
||||
receivedCount: number
|
||||
estimatedTotal: string | null
|
||||
actualTotal: string | null
|
||||
@@ -79,7 +79,7 @@ export interface RepairNote {
|
||||
export interface RepairServiceTemplate {
|
||||
id: string
|
||||
name: string
|
||||
instrumentType: string | null
|
||||
itemCategory: string | null
|
||||
size: string | null
|
||||
description: string | null
|
||||
itemType: 'labor' | 'part' | 'flat_rate' | 'misc'
|
||||
|
||||
@@ -16,17 +16,17 @@ const pages: WikiPage[] = [
|
||||
title: 'Getting Started',
|
||||
category: 'General',
|
||||
content: `
|
||||
# Getting Started with Forte
|
||||
# Getting Started with LunarFront
|
||||
|
||||
Welcome to Forte — your music store management platform.
|
||||
Welcome to LunarFront — your small business management platform.
|
||||
|
||||
## Signing In
|
||||
|
||||
1. Open Forte in your browser
|
||||
1. Open LunarFront in your browser
|
||||
2. Enter your email and password
|
||||
3. Click **Sign in**
|
||||
|
||||
If you don't have an account, ask your store manager to create one for you.
|
||||
If you don't have an account, ask your admin to create one for you.
|
||||
|
||||
## Navigation
|
||||
|
||||
@@ -34,13 +34,13 @@ Use the sidebar on the left to navigate between sections:
|
||||
|
||||
- **Accounts** — manage customer accounts and their members
|
||||
- **Members** — find and manage individual people across all accounts
|
||||
- **Repairs** — track instrument repair tickets
|
||||
- **Repairs** — track repair tickets
|
||||
- **Repair Batches** — manage bulk school repair jobs
|
||||
- **Help** — you're here!
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you can't find what you're looking for, contact your store manager or system administrator.
|
||||
If you can't find what you're looking for, contact your admin or system administrator.
|
||||
`.trim(),
|
||||
},
|
||||
{
|
||||
@@ -89,7 +89,7 @@ Every account gets a unique 6-digit number automatically. This number appears in
|
||||
content: `
|
||||
# Members
|
||||
|
||||
A **member** is an individual person associated with an account. This could be a parent, a child, a student, a band director — anyone who takes lessons, rents instruments, or needs to be tracked.
|
||||
A **member** is an individual person associated with an account. This could be a parent, a child, a student, a staff member — anyone who takes lessons, uses services, or needs to be tracked.
|
||||
|
||||
## Adding a Member
|
||||
|
||||
@@ -136,7 +136,7 @@ Payment methods are cards on file for an account. These are used for recurring b
|
||||
5. Optionally enter card brand, last four digits, and expiration
|
||||
6. Click **Add Payment Method**
|
||||
|
||||
**Note:** Card details are stored securely with the payment processor — Forte only keeps a reference and display info (last 4 digits, brand).
|
||||
**Note:** Card details are stored securely with the payment processor — LunarFront only keeps a reference and display info (last 4 digits, brand).
|
||||
|
||||
## Default Payment Method
|
||||
|
||||
@@ -154,7 +154,7 @@ If a payment method was migrated from an old system, it may show a "Needs Update
|
||||
content: `
|
||||
# Tax Exemptions
|
||||
|
||||
Schools, churches, and resellers may be exempt from sales tax. Forte tracks tax exemption certificates per account.
|
||||
Schools, churches, and resellers may be exempt from sales tax. LunarFront tracks tax exemption certificates per account.
|
||||
|
||||
## Adding a Tax Exemption
|
||||
|
||||
@@ -191,7 +191,7 @@ All approvals and revocations are logged with who did it and when.
|
||||
content: `
|
||||
# Identity Documents
|
||||
|
||||
You can store identity documents (driver's license, passport, school ID) for any member. This is useful for verifying identity during instrument pickups, rentals, or trade-ins.
|
||||
You can store identity documents (driver's license, passport, school ID) for any member. This is useful for verifying identity during pickups, rentals, or trade-ins.
|
||||
|
||||
## Adding an ID
|
||||
|
||||
@@ -225,7 +225,7 @@ If a member has multiple IDs, mark one as **Primary** — this is the one shown
|
||||
content: `
|
||||
# Users & Roles
|
||||
|
||||
Forte uses a permission-based access control system. **Permissions** are specific actions (like "view accounts" or "edit inventory"). **Roles** are named groups of permissions that you assign to users.
|
||||
LunarFront uses a permission-based access control system. **Permissions** are specific actions (like "view accounts" or "edit inventory"). **Roles** are named groups of permissions that you assign to users.
|
||||
|
||||
## Managing Users
|
||||
|
||||
@@ -306,16 +306,16 @@ Your preferences are saved in your browser and persist across sessions.
|
||||
content: `
|
||||
# Repairs
|
||||
|
||||
The Repairs module tracks instrument repair tickets from intake through completion. It supports walk-in customers, account-linked repairs, and bulk school batch jobs.
|
||||
The Repairs module tracks repair tickets from intake through completion. It supports walk-in customers, account-linked repairs, and bulk school batch jobs.
|
||||
|
||||
## Creating a Repair Ticket
|
||||
|
||||
1. Go to **Repairs** in the sidebar
|
||||
2. Click **New Repair**
|
||||
3. Search for an existing account or enter customer details manually for walk-ins
|
||||
4. Describe the instrument and the problem
|
||||
4. Describe the item and the problem
|
||||
5. Optionally add line items for the estimate (use templates for common services)
|
||||
6. Add intake photos to document the instrument's condition
|
||||
6. Add intake photos to document the item's condition
|
||||
7. Click **Create Ticket**
|
||||
|
||||
## Ticket Status Flow
|
||||
@@ -323,16 +323,16 @@ The Repairs module tracks instrument repair tickets from intake through completi
|
||||
Each ticket moves through these stages:
|
||||
|
||||
- **New** — ticket just created, not yet examined
|
||||
- **In Transit** — instrument being transported to the shop (for school pickups or shipped instruments)
|
||||
- **Intake** — instrument received, condition documented
|
||||
- **Diagnosing** — technician examining the instrument
|
||||
- **In Transit** — item being transported to the shop (for pickups or shipped items)
|
||||
- **Intake** — item received, condition documented
|
||||
- **Diagnosing** — technician examining the item
|
||||
- **Pending Approval** — estimate provided, waiting for customer OK
|
||||
- **Approved** — customer authorized the work
|
||||
- **In Progress** — actively being repaired
|
||||
- **Pending Parts** — waiting on parts order
|
||||
- **Ready** — repair complete, awaiting pickup
|
||||
- **Picked Up** — customer collected the instrument
|
||||
- **Delivered** — instrument returned via delivery (for school batches)
|
||||
- **Picked Up** — customer collected the item
|
||||
- **Delivered** — item returned via delivery (for school batches)
|
||||
|
||||
Click the status buttons on the ticket detail page to advance through the workflow. You can also click steps on the progress bar.
|
||||
|
||||
@@ -340,7 +340,7 @@ Click the status buttons on the ticket detail page to advance through the workfl
|
||||
|
||||
The ticket detail has four tabs:
|
||||
|
||||
- **Details** — customer info, instrument, condition, costs. Click **Edit** to modify.
|
||||
- **Details** — customer info, item, condition, costs. Click **Edit** to modify.
|
||||
- **Line Items** — labor, parts, flat-rate services, and misc charges. Use the template picker for common repairs.
|
||||
- **Notes** — running journal of notes. Choose **Internal** (staff only) or **Customer Visible**. You can attach photos to notes.
|
||||
- **Photos & Docs** — photos organized by repair phase (intake, in progress, completed) plus a documents section for signed approvals, quotes, and receipts.
|
||||
@@ -372,7 +372,7 @@ Templates are pre-defined common repairs (e.g. "Bow Rehair — Violin — 4/4")
|
||||
2. Click **New Template**
|
||||
3. Fill in:
|
||||
- **Name** — e.g. "Bow Rehair", "String Change", "Valve Overhaul"
|
||||
- **Instrument Type** — e.g. "Violin", "Guitar", "Trumpet"
|
||||
- **Item Category** — e.g. "Violin", "Guitar", "Trumpet"
|
||||
- **Size** — e.g. "4/4", "3/4", "Full"
|
||||
- **Type** — Labor, Part, Flat Rate, or Misc
|
||||
- **Default Price** — the customer-facing price
|
||||
@@ -400,19 +400,19 @@ When creating a ticket or adding line items, type in the **Quick Add from Templa
|
||||
content: `
|
||||
# Repair Batches
|
||||
|
||||
Batches group multiple repair tickets under one job — typically for schools bringing in many instruments at once.
|
||||
Batches group multiple repair tickets under one job — typically for schools bringing in many items at once.
|
||||
|
||||
## Creating a Batch
|
||||
|
||||
1. Go to **Repair Batches** in the sidebar
|
||||
2. Click **New Batch**
|
||||
3. Select the school's account
|
||||
4. Enter contact info, instrument count, and any notes
|
||||
4. Enter contact info, item count, and any notes
|
||||
5. Click **Create Batch**
|
||||
|
||||
## Adding Tickets to a Batch
|
||||
|
||||
When creating new repair tickets, select the batch in the form. Each instrument gets its own ticket linked to the batch.
|
||||
When creating new repair tickets, select the batch in the form. Each item gets its own ticket linked to the batch.
|
||||
|
||||
## Batch Approval
|
||||
|
||||
@@ -426,10 +426,10 @@ Only admins can approve or reject batches.
|
||||
|
||||
## Batch Status
|
||||
|
||||
- **Intake** — receiving instruments
|
||||
- **Intake** — receiving items
|
||||
- **In Progress** — work underway
|
||||
- **Completed** — all repairs done
|
||||
- **Delivered** — instruments returned to school
|
||||
- **Delivered** — items returned to customer
|
||||
|
||||
## Filtering
|
||||
|
||||
@@ -468,7 +468,7 @@ Photos appear inline in the note entry. Click to view full size.
|
||||
|
||||
The Photos & Docs tab organizes photos into categories:
|
||||
|
||||
- **Intake Photos** — document instrument condition when received
|
||||
- **Intake Photos** — document item condition when received
|
||||
- **Work in Progress** — during the repair
|
||||
- **Completed** — final result after repair
|
||||
- **Documents** — signed approvals, quotes, receipts (accepts PDFs)
|
||||
|
||||
Reference in New Issue
Block a user