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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user