mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-15 10:34:43 -05:00
Add business nickname support across app and API
This commit is contained in:
@@ -1,130 +0,0 @@
|
|||||||
CREATE TABLE "beenvoice_account" (
|
|
||||||
"userId" varchar(255) NOT NULL,
|
|
||||||
"type" varchar(255) NOT NULL,
|
|
||||||
"provider" varchar(255) NOT NULL,
|
|
||||||
"providerAccountId" varchar(255) NOT NULL,
|
|
||||||
"refresh_token" text,
|
|
||||||
"access_token" text,
|
|
||||||
"expires_at" integer,
|
|
||||||
"token_type" varchar(255),
|
|
||||||
"scope" varchar(255),
|
|
||||||
"id_token" text,
|
|
||||||
"session_state" varchar(255),
|
|
||||||
CONSTRAINT "beenvoice_account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_business" (
|
|
||||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"email" varchar(255),
|
|
||||||
"phone" varchar(50),
|
|
||||||
"addressLine1" varchar(255),
|
|
||||||
"addressLine2" varchar(255),
|
|
||||||
"city" varchar(100),
|
|
||||||
"state" varchar(50),
|
|
||||||
"postalCode" varchar(20),
|
|
||||||
"country" varchar(100),
|
|
||||||
"website" varchar(255),
|
|
||||||
"taxId" varchar(100),
|
|
||||||
"logoUrl" varchar(500),
|
|
||||||
"isDefault" boolean DEFAULT false,
|
|
||||||
"resendApiKey" varchar(255),
|
|
||||||
"resendDomain" varchar(255),
|
|
||||||
"emailFromName" varchar(255),
|
|
||||||
"createdById" varchar(255) NOT NULL,
|
|
||||||
"createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
"updatedAt" timestamp
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_client" (
|
|
||||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"email" varchar(255),
|
|
||||||
"phone" varchar(50),
|
|
||||||
"addressLine1" varchar(255),
|
|
||||||
"addressLine2" varchar(255),
|
|
||||||
"city" varchar(100),
|
|
||||||
"state" varchar(50),
|
|
||||||
"postalCode" varchar(20),
|
|
||||||
"country" varchar(100),
|
|
||||||
"defaultHourlyRate" real DEFAULT 100 NOT NULL,
|
|
||||||
"createdById" varchar(255) NOT NULL,
|
|
||||||
"createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
"updatedAt" timestamp
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_invoice_item" (
|
|
||||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"invoiceId" varchar(255) NOT NULL,
|
|
||||||
"date" timestamp NOT NULL,
|
|
||||||
"description" varchar(500) NOT NULL,
|
|
||||||
"hours" real NOT NULL,
|
|
||||||
"rate" real NOT NULL,
|
|
||||||
"amount" real NOT NULL,
|
|
||||||
"position" integer DEFAULT 0 NOT NULL,
|
|
||||||
"createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_invoice" (
|
|
||||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"invoiceNumber" varchar(100) NOT NULL,
|
|
||||||
"businessId" varchar(255),
|
|
||||||
"clientId" varchar(255) NOT NULL,
|
|
||||||
"issueDate" timestamp NOT NULL,
|
|
||||||
"dueDate" timestamp NOT NULL,
|
|
||||||
"status" varchar(50) DEFAULT 'draft' NOT NULL,
|
|
||||||
"totalAmount" real DEFAULT 0 NOT NULL,
|
|
||||||
"taxRate" real DEFAULT 0 NOT NULL,
|
|
||||||
"notes" varchar(1000),
|
|
||||||
"createdById" varchar(255) NOT NULL,
|
|
||||||
"createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
"updatedAt" timestamp
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_session" (
|
|
||||||
"sessionToken" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"userId" varchar(255) NOT NULL,
|
|
||||||
"expires" timestamp NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_user" (
|
|
||||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"name" varchar(255),
|
|
||||||
"email" varchar(255) NOT NULL,
|
|
||||||
"password" varchar(255),
|
|
||||||
"emailVerified" timestamp DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"image" varchar(255)
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "beenvoice_verification_token" (
|
|
||||||
"identifier" varchar(255) NOT NULL,
|
|
||||||
"token" varchar(255) NOT NULL,
|
|
||||||
"expires" timestamp NOT NULL,
|
|
||||||
CONSTRAINT "beenvoice_verification_token_identifier_token_pk" PRIMARY KEY("identifier","token")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_account" ADD CONSTRAINT "beenvoice_account_userId_beenvoice_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_business" ADD CONSTRAINT "beenvoice_business_createdById_beenvoice_user_id_fk" FOREIGN KEY ("createdById") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_client" ADD CONSTRAINT "beenvoice_client_createdById_beenvoice_user_id_fk" FOREIGN KEY ("createdById") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_invoice_item" ADD CONSTRAINT "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk" FOREIGN KEY ("invoiceId") REFERENCES "public"."beenvoice_invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_invoice" ADD CONSTRAINT "beenvoice_invoice_businessId_beenvoice_business_id_fk" FOREIGN KEY ("businessId") REFERENCES "public"."beenvoice_business"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_invoice" ADD CONSTRAINT "beenvoice_invoice_clientId_beenvoice_client_id_fk" FOREIGN KEY ("clientId") REFERENCES "public"."beenvoice_client"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_invoice" ADD CONSTRAINT "beenvoice_invoice_createdById_beenvoice_user_id_fk" FOREIGN KEY ("createdById") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "beenvoice_session" ADD CONSTRAINT "beenvoice_session_userId_beenvoice_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
CREATE INDEX "account_user_id_idx" ON "beenvoice_account" USING btree ("userId");--> statement-breakpoint
|
|
||||||
CREATE INDEX "business_created_by_idx" ON "beenvoice_business" USING btree ("createdById");--> statement-breakpoint
|
|
||||||
CREATE INDEX "business_name_idx" ON "beenvoice_business" USING btree ("name");--> statement-breakpoint
|
|
||||||
CREATE INDEX "business_email_idx" ON "beenvoice_business" USING btree ("email");--> statement-breakpoint
|
|
||||||
CREATE INDEX "business_is_default_idx" ON "beenvoice_business" USING btree ("isDefault");--> statement-breakpoint
|
|
||||||
CREATE INDEX "client_created_by_idx" ON "beenvoice_client" USING btree ("createdById");--> statement-breakpoint
|
|
||||||
CREATE INDEX "client_name_idx" ON "beenvoice_client" USING btree ("name");--> statement-breakpoint
|
|
||||||
CREATE INDEX "client_email_idx" ON "beenvoice_client" USING btree ("email");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_item_invoice_id_idx" ON "beenvoice_invoice_item" USING btree ("invoiceId");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_item_date_idx" ON "beenvoice_invoice_item" USING btree ("date");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_item_position_idx" ON "beenvoice_invoice_item" USING btree ("position");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_business_id_idx" ON "beenvoice_invoice" USING btree ("businessId");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_client_id_idx" ON "beenvoice_invoice" USING btree ("clientId");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_created_by_idx" ON "beenvoice_invoice" USING btree ("createdById");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_number_idx" ON "beenvoice_invoice" USING btree ("invoiceNumber");--> statement-breakpoint
|
|
||||||
CREATE INDEX "invoice_status_idx" ON "beenvoice_invoice" USING btree ("status");--> statement-breakpoint
|
|
||||||
CREATE INDEX "session_userId_idx" ON "beenvoice_session" USING btree ("userId");
|
|
||||||
@@ -1,995 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "c86256e7-28b4-48ea-8104-933cbff6e217",
|
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "postgresql",
|
|
||||||
"tables": {
|
|
||||||
"public.beenvoice_account": {
|
|
||||||
"name": "beenvoice_account",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"userId": {
|
|
||||||
"name": "userId",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"provider": {
|
|
||||||
"name": "provider",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"providerAccountId": {
|
|
||||||
"name": "providerAccountId",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"refresh_token": {
|
|
||||||
"name": "refresh_token",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"access_token": {
|
|
||||||
"name": "access_token",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"expires_at": {
|
|
||||||
"name": "expires_at",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"token_type": {
|
|
||||||
"name": "token_type",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"scope": {
|
|
||||||
"name": "scope",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"id_token": {
|
|
||||||
"name": "id_token",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"session_state": {
|
|
||||||
"name": "session_state",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"account_user_id_idx": {
|
|
||||||
"name": "account_user_id_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "userId",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"beenvoice_account_userId_beenvoice_user_id_fk": {
|
|
||||||
"name": "beenvoice_account_userId_beenvoice_user_id_fk",
|
|
||||||
"tableFrom": "beenvoice_account",
|
|
||||||
"tableTo": "beenvoice_user",
|
|
||||||
"columnsFrom": [
|
|
||||||
"userId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {
|
|
||||||
"beenvoice_account_provider_providerAccountId_pk": {
|
|
||||||
"name": "beenvoice_account_provider_providerAccountId_pk",
|
|
||||||
"columns": [
|
|
||||||
"provider",
|
|
||||||
"providerAccountId"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_business": {
|
|
||||||
"name": "beenvoice_business",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"phone": {
|
|
||||||
"name": "phone",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"addressLine1": {
|
|
||||||
"name": "addressLine1",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"addressLine2": {
|
|
||||||
"name": "addressLine2",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"city": {
|
|
||||||
"name": "city",
|
|
||||||
"type": "varchar(100)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"name": "state",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"postalCode": {
|
|
||||||
"name": "postalCode",
|
|
||||||
"type": "varchar(20)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"country": {
|
|
||||||
"name": "country",
|
|
||||||
"type": "varchar(100)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"website": {
|
|
||||||
"name": "website",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"taxId": {
|
|
||||||
"name": "taxId",
|
|
||||||
"type": "varchar(100)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"logoUrl": {
|
|
||||||
"name": "logoUrl",
|
|
||||||
"type": "varchar(500)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"isDefault": {
|
|
||||||
"name": "isDefault",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"resendApiKey": {
|
|
||||||
"name": "resendApiKey",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"resendDomain": {
|
|
||||||
"name": "resendDomain",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"emailFromName": {
|
|
||||||
"name": "emailFromName",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"createdById": {
|
|
||||||
"name": "createdById",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"name": "createdAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "CURRENT_TIMESTAMP"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"name": "updatedAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"business_created_by_idx": {
|
|
||||||
"name": "business_created_by_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "createdById",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"business_name_idx": {
|
|
||||||
"name": "business_name_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "name",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"business_email_idx": {
|
|
||||||
"name": "business_email_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "email",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"business_is_default_idx": {
|
|
||||||
"name": "business_is_default_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "isDefault",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"beenvoice_business_createdById_beenvoice_user_id_fk": {
|
|
||||||
"name": "beenvoice_business_createdById_beenvoice_user_id_fk",
|
|
||||||
"tableFrom": "beenvoice_business",
|
|
||||||
"tableTo": "beenvoice_user",
|
|
||||||
"columnsFrom": [
|
|
||||||
"createdById"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_client": {
|
|
||||||
"name": "beenvoice_client",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"phone": {
|
|
||||||
"name": "phone",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"addressLine1": {
|
|
||||||
"name": "addressLine1",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"addressLine2": {
|
|
||||||
"name": "addressLine2",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"city": {
|
|
||||||
"name": "city",
|
|
||||||
"type": "varchar(100)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"name": "state",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"postalCode": {
|
|
||||||
"name": "postalCode",
|
|
||||||
"type": "varchar(20)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"country": {
|
|
||||||
"name": "country",
|
|
||||||
"type": "varchar(100)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"defaultHourlyRate": {
|
|
||||||
"name": "defaultHourlyRate",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": 100
|
|
||||||
},
|
|
||||||
"createdById": {
|
|
||||||
"name": "createdById",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"name": "createdAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "CURRENT_TIMESTAMP"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"name": "updatedAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"client_created_by_idx": {
|
|
||||||
"name": "client_created_by_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "createdById",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"client_name_idx": {
|
|
||||||
"name": "client_name_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "name",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"client_email_idx": {
|
|
||||||
"name": "client_email_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "email",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"beenvoice_client_createdById_beenvoice_user_id_fk": {
|
|
||||||
"name": "beenvoice_client_createdById_beenvoice_user_id_fk",
|
|
||||||
"tableFrom": "beenvoice_client",
|
|
||||||
"tableTo": "beenvoice_user",
|
|
||||||
"columnsFrom": [
|
|
||||||
"createdById"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_invoice_item": {
|
|
||||||
"name": "beenvoice_invoice_item",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"invoiceId": {
|
|
||||||
"name": "invoiceId",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"date": {
|
|
||||||
"name": "date",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "varchar(500)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"hours": {
|
|
||||||
"name": "hours",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"rate": {
|
|
||||||
"name": "rate",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"amount": {
|
|
||||||
"name": "amount",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"name": "position",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"name": "createdAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "CURRENT_TIMESTAMP"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"invoice_item_invoice_id_idx": {
|
|
||||||
"name": "invoice_item_invoice_id_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "invoiceId",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"invoice_item_date_idx": {
|
|
||||||
"name": "invoice_item_date_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "date",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"invoice_item_position_idx": {
|
|
||||||
"name": "invoice_item_position_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "position",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk": {
|
|
||||||
"name": "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk",
|
|
||||||
"tableFrom": "beenvoice_invoice_item",
|
|
||||||
"tableTo": "beenvoice_invoice",
|
|
||||||
"columnsFrom": [
|
|
||||||
"invoiceId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_invoice": {
|
|
||||||
"name": "beenvoice_invoice",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"invoiceNumber": {
|
|
||||||
"name": "invoiceNumber",
|
|
||||||
"type": "varchar(100)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"businessId": {
|
|
||||||
"name": "businessId",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"clientId": {
|
|
||||||
"name": "clientId",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"issueDate": {
|
|
||||||
"name": "issueDate",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"dueDate": {
|
|
||||||
"name": "dueDate",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"name": "status",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "'draft'"
|
|
||||||
},
|
|
||||||
"totalAmount": {
|
|
||||||
"name": "totalAmount",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"taxRate": {
|
|
||||||
"name": "taxRate",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"name": "notes",
|
|
||||||
"type": "varchar(1000)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"createdById": {
|
|
||||||
"name": "createdById",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"name": "createdAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "CURRENT_TIMESTAMP"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"name": "updatedAt",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"invoice_business_id_idx": {
|
|
||||||
"name": "invoice_business_id_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "businessId",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"invoice_client_id_idx": {
|
|
||||||
"name": "invoice_client_id_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "clientId",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"invoice_created_by_idx": {
|
|
||||||
"name": "invoice_created_by_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "createdById",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"invoice_number_idx": {
|
|
||||||
"name": "invoice_number_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "invoiceNumber",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
},
|
|
||||||
"invoice_status_idx": {
|
|
||||||
"name": "invoice_status_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "status",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"beenvoice_invoice_businessId_beenvoice_business_id_fk": {
|
|
||||||
"name": "beenvoice_invoice_businessId_beenvoice_business_id_fk",
|
|
||||||
"tableFrom": "beenvoice_invoice",
|
|
||||||
"tableTo": "beenvoice_business",
|
|
||||||
"columnsFrom": [
|
|
||||||
"businessId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"beenvoice_invoice_clientId_beenvoice_client_id_fk": {
|
|
||||||
"name": "beenvoice_invoice_clientId_beenvoice_client_id_fk",
|
|
||||||
"tableFrom": "beenvoice_invoice",
|
|
||||||
"tableTo": "beenvoice_client",
|
|
||||||
"columnsFrom": [
|
|
||||||
"clientId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"beenvoice_invoice_createdById_beenvoice_user_id_fk": {
|
|
||||||
"name": "beenvoice_invoice_createdById_beenvoice_user_id_fk",
|
|
||||||
"tableFrom": "beenvoice_invoice",
|
|
||||||
"tableTo": "beenvoice_user",
|
|
||||||
"columnsFrom": [
|
|
||||||
"createdById"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_session": {
|
|
||||||
"name": "beenvoice_session",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"sessionToken": {
|
|
||||||
"name": "sessionToken",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"name": "userId",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"expires": {
|
|
||||||
"name": "expires",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"session_userId_idx": {
|
|
||||||
"name": "session_userId_idx",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "userId",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": false,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"beenvoice_session_userId_beenvoice_user_id_fk": {
|
|
||||||
"name": "beenvoice_session_userId_beenvoice_user_id_fk",
|
|
||||||
"tableFrom": "beenvoice_session",
|
|
||||||
"tableTo": "beenvoice_user",
|
|
||||||
"columnsFrom": [
|
|
||||||
"userId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_user": {
|
|
||||||
"name": "beenvoice_user",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"name": "password",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"emailVerified": {
|
|
||||||
"name": "emailVerified",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"default": "CURRENT_TIMESTAMP"
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"name": "image",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.beenvoice_verification_token": {
|
|
||||||
"name": "beenvoice_verification_token",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"identifier": {
|
|
||||||
"name": "identifier",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"name": "token",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"expires": {
|
|
||||||
"name": "expires",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {
|
|
||||||
"beenvoice_verification_token_identifier_token_pk": {
|
|
||||||
"name": "beenvoice_verification_token_identifier_token_pk",
|
|
||||||
"columns": [
|
|
||||||
"identifier",
|
|
||||||
"token"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"enums": {},
|
|
||||||
"schemas": {},
|
|
||||||
"sequences": {},
|
|
||||||
"roles": {},
|
|
||||||
"policies": {},
|
|
||||||
"views": {},
|
|
||||||
"_meta": {
|
|
||||||
"columns": {},
|
|
||||||
"schemas": {},
|
|
||||||
"tables": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "postgresql",
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"idx": 0,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1753825898609,
|
|
||||||
"tag": "0000_warm_squadron_sinister",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -45,7 +45,7 @@ export default async function BusinessDetailPage({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6 pb-32">
|
<div className="space-y-6 pb-32">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={business.name}
|
title={`${business.name}${business.nickname ? ` (${business.nickname})` : ""}`}
|
||||||
description="View business details and information"
|
description="View business details and information"
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
>
|
>
|
||||||
@@ -69,7 +69,7 @@ export default async function BusinessDetailPage({
|
|||||||
<Card className="bg-card border-border border">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-primary h-5 w-5" />
|
<Building className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Business Information</span>
|
<span>Business Information</span>
|
||||||
@@ -84,7 +84,7 @@ export default async function BusinessDetailPage({
|
|||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{business.email && (
|
{business.email && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Mail className="text-primary h-4 w-4" />
|
<Mail className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -100,7 +100,7 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{business.phone && (
|
{business.phone && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Phone className="text-primary h-4 w-4" />
|
<Phone className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -116,7 +116,7 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{business.website && (
|
{business.website && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Globe className="text-primary h-4 w-4" />
|
<Globe className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -137,7 +137,7 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{business.taxId && (
|
{business.taxId && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Hash className="text-primary h-4 w-4" />
|
<Hash className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -162,7 +162,7 @@ export default async function BusinessDetailPage({
|
|||||||
Business Address
|
Business Address
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<MapPin className="text-primary h-4 w-4" />
|
<MapPin className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 text-sm">
|
<div className="space-y-1 text-sm">
|
||||||
@@ -205,7 +205,7 @@ export default async function BusinessDetailPage({
|
|||||||
<h3 className="mb-4 text-lg font-semibold">Business Details</h3>
|
<h3 className="mb-4 text-lg font-semibold">Business Details</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Calendar className="text-primary h-4 w-4" />
|
<Calendar className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -218,10 +218,31 @@ export default async function BusinessDetailPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{business.nickname && (
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="bg-primary/10 p-2">
|
||||||
|
<Building className="text-primary h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
|
Nickname
|
||||||
|
</p>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
Internal only
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-foreground text-sm">
|
||||||
|
{business.nickname}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Default Business Badge */}
|
{/* Default Business Badge */}
|
||||||
{business.isDefault && (
|
{business.isDefault && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-primary h-4 w-4" />
|
<Building className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -248,7 +269,7 @@ export default async function BusinessDetailPage({
|
|||||||
<Card className="bg-card border-border border">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-primary h-5 w-5" />
|
<Building className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Quick Actions</span>
|
<span>Quick Actions</span>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { toast } from "sonner";
|
|||||||
interface Business {
|
interface Business {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
nickname: string | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
phone: string | null;
|
phone: string | null;
|
||||||
addressLine1: string | null;
|
addressLine1: string | null;
|
||||||
@@ -61,6 +62,11 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) {
|
|||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const searchableBusinesses = businesses.map((b) => ({
|
||||||
|
...b,
|
||||||
|
searchValue: `${b.name} ${b.nickname ?? ""}`.trim(),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteBusinessMutation = api.businesses.delete.useMutation({
|
const deleteBusinessMutation = api.businesses.delete.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Business deleted successfully");
|
toast.success("Business deleted successfully");
|
||||||
@@ -91,7 +97,7 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) {
|
|||||||
const business = row.original;
|
const business = row.original;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-primary/10 hidden p-2 sm:flex">
|
<div className="bg-primary/10 hidden p-2 sm:flex">
|
||||||
<Building className="text-primary h-4 w-4" />
|
<Building className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
@@ -104,6 +110,17 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "nickname",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader column={column} title="Nickname" />
|
||||||
|
),
|
||||||
|
cell: ({ row }) => row.original.nickname ?? "—",
|
||||||
|
meta: {
|
||||||
|
headerClassName: "hidden sm:table-cell",
|
||||||
|
cellClassName: "hidden sm:table-cell",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "phone",
|
accessorKey: "phone",
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
@@ -175,6 +192,15 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "searchValue",
|
||||||
|
header: "Search",
|
||||||
|
cell: () => null,
|
||||||
|
meta: {
|
||||||
|
headerClassName: "hidden",
|
||||||
|
cellClassName: "hidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -210,9 +236,9 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) {
|
|||||||
<>
|
<>
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={businesses}
|
data={searchableBusinesses}
|
||||||
searchKey="name"
|
searchKey="searchValue"
|
||||||
searchPlaceholder="Search businesses..."
|
searchPlaceholder="Search by name or nickname..."
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default async function BusinessesPage() {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<HydrateClient>
|
<HydrateClient>
|
||||||
<Suspense fallback={<DataTableSkeleton columns={6} rows={5} />}>
|
<Suspense fallback={<DataTableSkeleton columns={7} rows={5} />}>
|
||||||
<BusinessesTable />
|
<BusinessesTable />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</HydrateClient>
|
</HydrateClient>
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ export default function SendEmailPage() {
|
|||||||
business: invoiceData.business
|
business: invoiceData.business
|
||||||
? {
|
? {
|
||||||
name: invoiceData.business.name,
|
name: invoiceData.business.name,
|
||||||
|
nickname: invoiceData.business.nickname,
|
||||||
email: invoiceData.business.email,
|
email: invoiceData.business.email,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export function DataTable<TData, TValue>({
|
|||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
const [globalFilter, setGlobalFilter] = React.useState("");
|
const [globalFilter, setGlobalFilter] = React.useState("");
|
||||||
|
const [searchInput, setSearchInput] = React.useState("");
|
||||||
|
|
||||||
// Mobile detection hook
|
// Mobile detection hook
|
||||||
const [isMobile, setIsMobile] = React.useState(false);
|
const [isMobile, setIsMobile] = React.useState(false);
|
||||||
@@ -171,6 +172,19 @@ export function DataTable<TData, TValue>({
|
|||||||
table.setPageSize(isMobile ? 5 : pageSize);
|
table.setPageSize(isMobile ? 5 : pageSize);
|
||||||
}, [isMobile, pageSize, table]);
|
}, [isMobile, pageSize, table]);
|
||||||
|
|
||||||
|
// Debounce search input updates to the table's global filter
|
||||||
|
React.useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setGlobalFilter(searchInput);
|
||||||
|
}, 300);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [searchInput]);
|
||||||
|
|
||||||
|
// Keep search input in sync when globalFilter is changed externally (e.g., "Clear filters")
|
||||||
|
React.useEffect(() => {
|
||||||
|
setSearchInput(globalFilter ?? "");
|
||||||
|
}, [globalFilter]);
|
||||||
|
|
||||||
const pageSizeOptions = [5, 10, 20, 30, 50, 100];
|
const pageSizeOptions = [5, 10, 20, 30, 50, 100];
|
||||||
|
|
||||||
// Handle row click
|
// Handle row click
|
||||||
@@ -223,8 +237,8 @@ export function DataTable<TData, TValue>({
|
|||||||
<Search className="text-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
<Search className="text-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
||||||
<Input
|
<Input
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
value={globalFilter ?? ""}
|
value={searchInput ?? ""}
|
||||||
onChange={(event) => setGlobalFilter(event.target.value)}
|
onChange={(event) => setSearchInput(event.target.value)}
|
||||||
className="h-9 w-full pr-3 pl-9"
|
className="h-9 w-full pr-3 pl-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<FileText className="text-primary h-6 w-6" />
|
<FileText className="text-primary h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -239,7 +239,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
>
|
>
|
||||||
{isExportingPDF ? (
|
{isExportingPDF ? (
|
||||||
<>
|
<>
|
||||||
<div className="mr-2 h-4 w-4 animate-spin border-2 border-white border-t-transparent" />
|
<div className="mr-2 h-4 w-4 animate-spin border-2 border-white border-t-transparent" />
|
||||||
Generating PDF...
|
Generating PDF...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -326,7 +326,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="border-border overflow-hidden border">
|
<div className="border-border overflow-hidden border">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-muted">
|
<thead className="bg-muted">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -479,7 +479,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Danger Zone */}
|
{/* Danger Zone */}
|
||||||
<Card className="bg-card border-border border border-destructive/20">
|
<Card className="bg-card border-destructive/20 border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-destructive">Danger Zone</CardTitle>
|
<CardTitle className="text-destructive">Danger Zone</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ interface BusinessFormProps {
|
|||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
|
nickname: string;
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
addressLine1: string;
|
addressLine1: string;
|
||||||
@@ -64,6 +65,7 @@ interface FormData {
|
|||||||
|
|
||||||
interface FormErrors {
|
interface FormErrors {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
nickname?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
addressLine1?: string;
|
addressLine1?: string;
|
||||||
@@ -80,6 +82,7 @@ interface FormErrors {
|
|||||||
|
|
||||||
const initialFormData: FormData = {
|
const initialFormData: FormData = {
|
||||||
name: "",
|
name: "",
|
||||||
|
nickname: "",
|
||||||
email: "",
|
email: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
addressLine1: "",
|
addressLine1: "",
|
||||||
@@ -153,6 +156,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
if (business && mode === "edit") {
|
if (business && mode === "edit") {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: business.name,
|
name: business.name,
|
||||||
|
nickname: business.nickname ?? "",
|
||||||
email: business.email ?? "",
|
email: business.email ?? "",
|
||||||
phone: business.phone ?? "",
|
phone: business.phone ?? "",
|
||||||
addressLine1: business.addressLine1 ?? "",
|
addressLine1: business.addressLine1 ?? "",
|
||||||
@@ -198,6 +202,10 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
if (!formData.name.trim()) {
|
if (!formData.name.trim()) {
|
||||||
newErrors.name = VALIDATION_MESSAGES.required;
|
newErrors.name = VALIDATION_MESSAGES.required;
|
||||||
}
|
}
|
||||||
|
// Nickname validation (optional, max 255 chars)
|
||||||
|
if (formData.nickname && formData.nickname.length > 255) {
|
||||||
|
newErrors.nickname = "Nickname must be 255 characters or less";
|
||||||
|
}
|
||||||
|
|
||||||
// Email validation
|
// Email validation
|
||||||
if (formData.email && !isValidEmail(formData.email)) {
|
if (formData.email && !isValidEmail(formData.email)) {
|
||||||
@@ -280,6 +288,8 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
// Format website URL before submission
|
// Format website URL before submission
|
||||||
const dataToSubmit = {
|
const dataToSubmit = {
|
||||||
...formData,
|
...formData,
|
||||||
|
name: formData.name.trim(),
|
||||||
|
nickname: formData.nickname.trim(),
|
||||||
website: formData.website ? formatWebsiteUrl(formData.website) : "",
|
website: formData.website ? formatWebsiteUrl(formData.website) : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -287,6 +297,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
// Create business data (excluding email config fields)
|
// Create business data (excluding email config fields)
|
||||||
const businessData = {
|
const businessData = {
|
||||||
name: dataToSubmit.name,
|
name: dataToSubmit.name,
|
||||||
|
nickname: dataToSubmit.nickname,
|
||||||
email: dataToSubmit.email,
|
email: dataToSubmit.email,
|
||||||
phone: dataToSubmit.phone,
|
phone: dataToSubmit.phone,
|
||||||
addressLine1: dataToSubmit.addressLine1,
|
addressLine1: dataToSubmit.addressLine1,
|
||||||
@@ -320,6 +331,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
// Update business data (excluding email config fields)
|
// Update business data (excluding email config fields)
|
||||||
const businessData = {
|
const businessData = {
|
||||||
name: dataToSubmit.name,
|
name: dataToSubmit.name,
|
||||||
|
nickname: dataToSubmit.nickname,
|
||||||
email: dataToSubmit.email,
|
email: dataToSubmit.email,
|
||||||
phone: dataToSubmit.phone,
|
phone: dataToSubmit.phone,
|
||||||
addressLine1: dataToSubmit.addressLine1,
|
addressLine1: dataToSubmit.addressLine1,
|
||||||
@@ -442,7 +454,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<Card className="bg-card border-border border">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center">
|
||||||
<Building className="text-muted-foreground h-5 w-5" />
|
<Building className="text-muted-foreground h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -475,6 +487,29 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nickname" className="text-sm font-medium">
|
||||||
|
Nickname
|
||||||
|
<span className="text-muted-foreground ml-1 text-xs font-normal">
|
||||||
|
(Optional)
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="nickname"
|
||||||
|
value={formData.nickname}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange("nickname", e.target.value)
|
||||||
|
}
|
||||||
|
placeholder="e.g., Personal, Work, LLC"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
{errors.nickname && (
|
||||||
|
<p className="text-destructive text-sm">
|
||||||
|
{errors.nickname}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="taxId" className="text-sm font-medium">
|
<Label htmlFor="taxId" className="text-sm font-medium">
|
||||||
Tax ID (EIN)
|
Tax ID (EIN)
|
||||||
@@ -569,7 +604,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<Card className="bg-card border-border border">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
className="text-muted-foreground h-5 w-5"
|
className="text-muted-foreground h-5 w-5"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -617,7 +652,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<Card className="bg-card border-border border">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center">
|
||||||
<Mail className="text-muted-foreground h-5 w-5" />
|
<Mail className="text-muted-foreground h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -632,7 +667,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* Current Status */}
|
{/* Current Status */}
|
||||||
{mode === "edit" && (
|
{mode === "edit" && (
|
||||||
<div className="flex items-center justify-between bg-gray-50 p-4">
|
<div className="flex items-center justify-between bg-gray-50 p-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
Current Status:
|
Current Status:
|
||||||
@@ -806,7 +841,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<Card className="bg-card border-border border">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center">
|
||||||
<Star className="text-muted-foreground h-5 w-5" />
|
<Star className="text-muted-foreground h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -818,7 +853,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="bg-muted border-border/40 flex items-center justify-between border p-4">
|
<div className="bg-muted border-border/40 flex items-center justify-between border p-4">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label
|
<Label
|
||||||
htmlFor="isDefault"
|
htmlFor="isDefault"
|
||||||
@@ -848,7 +883,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<FloatingActionBar
|
<FloatingActionBar
|
||||||
leftContent={
|
leftContent={
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-primary/10 p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<FileText className="text-primary h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -624,16 +624,22 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
updateField("businessId", value)
|
updateField("businessId", value)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger
|
||||||
<SelectValue placeholder="Select your business" />
|
aria-label="From Business"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<span className="min-w-0 flex-1 truncate text-left">
|
||||||
|
<SelectValue placeholder="Select your business (nickname shown)" />
|
||||||
|
</span>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="w-[--radix-select-trigger-width] min-w-[--radix-select-trigger-width]">
|
||||||
{businesses?.map((business) => (
|
{businesses?.map((business) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={business.id}
|
key={business.id}
|
||||||
value={business.id}
|
value={business.id}
|
||||||
|
className="truncate"
|
||||||
>
|
>
|
||||||
{business.name}
|
<span className="block truncate">{`${business.name}${business.nickname ? ` (${business.nickname})` : ""}`}</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -647,13 +653,24 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
updateField("clientId", value)
|
updateField("clientId", value)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger
|
||||||
<SelectValue placeholder="Select a client" />
|
aria-label="Bill To Client"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<span className="min-w-0 flex-1 truncate text-left">
|
||||||
|
<SelectValue placeholder="Select a client" />
|
||||||
|
</span>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="w-[--radix-select-trigger-width] min-w-[--radix-select-trigger-width]">
|
||||||
{clients?.map((client) => (
|
{clients?.map((client) => (
|
||||||
<SelectItem key={client.id} value={client.id}>
|
<SelectItem
|
||||||
{client.name}
|
key={client.id}
|
||||||
|
value={client.id}
|
||||||
|
className="truncate"
|
||||||
|
>
|
||||||
|
<span className="block truncate">
|
||||||
|
{client.name}
|
||||||
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -789,10 +806,18 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted-foreground">Business:</span>
|
<span className="text-muted-foreground">
|
||||||
|
Business (nickname shown):
|
||||||
|
</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{businesses?.find((b) => b.id === formData.businessId)
|
{(() => {
|
||||||
?.name ?? "Not selected"}
|
const b = businesses?.find(
|
||||||
|
(b) => b.id === formData.businessId,
|
||||||
|
);
|
||||||
|
return b
|
||||||
|
? `${b.name}${b.nickname ? ` (${b.nickname})` : ""}`
|
||||||
|
: "Not selected";
|
||||||
|
})()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,14 +42,16 @@ function SelectTrigger({
|
|||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[placeholder]:text-muted-foreground border-input bg-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex h-10 w-full items-center justify-between gap-2 border px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
"data-[placeholder]:text-muted-foreground border-input bg-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 relative flex h-10 w-full items-center justify-start gap-2 border px-3 py-2 pr-8 text-left text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
<span className="min-w-0 flex-1 truncate text-left">{children}</span>
|
||||||
<SelectPrimitive.Icon asChild>
|
<SelectPrimitive.Icon asChild>
|
||||||
<ChevronDownIcon className="size-4 opacity-50" />
|
<span className="pointer-events-none absolute inset-y-0 right-2 flex items-center">
|
||||||
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
|
</span>
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
);
|
);
|
||||||
@@ -66,7 +68,7 @@ function SelectContent({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto border-0 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto border-0 shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className,
|
className,
|
||||||
@@ -112,7 +114,7 @@ function SelectItem({
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-foreground-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"focus:bg-accent focus:text-foreground-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -210,7 +212,7 @@ function SelectContentWithSearch({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden border-0 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden border-0 shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className,
|
className,
|
||||||
@@ -235,7 +237,7 @@ function SelectContentWithSearch({
|
|||||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
<input
|
<input
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
className="placeholder:text-muted-foreground text-foreground flex h-8 w-full border-0 bg-transparent py-2 text-sm outline-none focus:ring-0 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
className="placeholder:text-muted-foreground text-foreground flex h-8 w-full border-0 bg-transparent py-2 text-sm outline-none focus:ring-0 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface InvoiceEmailTemplateProps {
|
|||||||
};
|
};
|
||||||
business?: {
|
business?: {
|
||||||
name: string;
|
name: string;
|
||||||
|
nickname?: string | null;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
phone?: string | null;
|
phone?: string | null;
|
||||||
addressLine1?: string | null;
|
addressLine1?: string | null;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface InvoiceData {
|
|||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
business?: {
|
business?: {
|
||||||
name: string;
|
name: string;
|
||||||
|
nickname?: string | null;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
phone?: string | null;
|
phone?: string | null;
|
||||||
addressLine1?: string | null;
|
addressLine1?: string | null;
|
||||||
|
|||||||
@@ -6,7 +6,17 @@ import { invoices } from "~/server/db/schema";
|
|||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
|
|
||||||
const businessSchema = z.object({
|
const businessSchema = z.object({
|
||||||
name: z.string().min(1, "Business name is required"),
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Business name is required")
|
||||||
|
.max(255, "Business name must be 255 characters or less"),
|
||||||
|
nickname: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(255, "Nickname must be 255 characters or less")
|
||||||
|
.optional()
|
||||||
|
.or(z.literal("")),
|
||||||
email: z.string().email().optional().or(z.literal("")),
|
email: z.string().email().optional().or(z.literal("")),
|
||||||
phone: z.string().optional().or(z.literal("")),
|
phone: z.string().optional().or(z.literal("")),
|
||||||
addressLine1: z.string().optional().or(z.literal("")),
|
addressLine1: z.string().optional().or(z.literal("")),
|
||||||
@@ -96,7 +106,54 @@ export const businessesRouter = createTRPCRouter({
|
|||||||
const [newBusiness] = await ctx.db
|
const [newBusiness] = await ctx.db
|
||||||
.insert(businesses)
|
.insert(businesses)
|
||||||
.values({
|
.values({
|
||||||
...input,
|
name: input.name.trim(),
|
||||||
|
nickname:
|
||||||
|
input.nickname && input.nickname.trim() !== ""
|
||||||
|
? input.nickname.trim()
|
||||||
|
: null,
|
||||||
|
email:
|
||||||
|
input.email && input.email.trim() !== ""
|
||||||
|
? input.email.trim()
|
||||||
|
: null,
|
||||||
|
phone:
|
||||||
|
input.phone && input.phone.trim() !== ""
|
||||||
|
? input.phone.trim()
|
||||||
|
: null,
|
||||||
|
addressLine1:
|
||||||
|
input.addressLine1 && input.addressLine1.trim() !== ""
|
||||||
|
? input.addressLine1.trim()
|
||||||
|
: null,
|
||||||
|
addressLine2:
|
||||||
|
input.addressLine2 && input.addressLine2.trim() !== ""
|
||||||
|
? input.addressLine2.trim()
|
||||||
|
: null,
|
||||||
|
city:
|
||||||
|
input.city && input.city.trim() !== "" ? input.city.trim() : null,
|
||||||
|
state:
|
||||||
|
input.state && input.state.trim() !== ""
|
||||||
|
? input.state.trim()
|
||||||
|
: null,
|
||||||
|
postalCode:
|
||||||
|
input.postalCode && input.postalCode.trim() !== ""
|
||||||
|
? input.postalCode.trim()
|
||||||
|
: null,
|
||||||
|
country:
|
||||||
|
input.country && input.country.trim() !== ""
|
||||||
|
? input.country.trim()
|
||||||
|
: null,
|
||||||
|
website:
|
||||||
|
input.website && input.website.trim() !== ""
|
||||||
|
? input.website.trim()
|
||||||
|
: null,
|
||||||
|
taxId:
|
||||||
|
input.taxId && input.taxId.trim() !== ""
|
||||||
|
? input.taxId.trim()
|
||||||
|
: null,
|
||||||
|
logoUrl:
|
||||||
|
input.logoUrl && input.logoUrl.trim() !== ""
|
||||||
|
? input.logoUrl.trim()
|
||||||
|
: null,
|
||||||
|
isDefault: input.isDefault ?? false,
|
||||||
createdById: ctx.session.user.id,
|
createdById: ctx.session.user.id,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
@@ -126,7 +183,56 @@ export const businessesRouter = createTRPCRouter({
|
|||||||
const [updatedBusiness] = await ctx.db
|
const [updatedBusiness] = await ctx.db
|
||||||
.update(businesses)
|
.update(businesses)
|
||||||
.set({
|
.set({
|
||||||
...updateData,
|
name: (updateData.name ?? "").trim(),
|
||||||
|
nickname:
|
||||||
|
updateData.nickname && updateData.nickname.trim() !== ""
|
||||||
|
? updateData.nickname.trim()
|
||||||
|
: null,
|
||||||
|
email:
|
||||||
|
updateData.email && updateData.email.trim() !== ""
|
||||||
|
? updateData.email.trim()
|
||||||
|
: null,
|
||||||
|
phone:
|
||||||
|
updateData.phone && updateData.phone.trim() !== ""
|
||||||
|
? updateData.phone.trim()
|
||||||
|
: null,
|
||||||
|
addressLine1:
|
||||||
|
updateData.addressLine1 && updateData.addressLine1.trim() !== ""
|
||||||
|
? updateData.addressLine1.trim()
|
||||||
|
: null,
|
||||||
|
addressLine2:
|
||||||
|
updateData.addressLine2 && updateData.addressLine2.trim() !== ""
|
||||||
|
? updateData.addressLine2.trim()
|
||||||
|
: null,
|
||||||
|
city:
|
||||||
|
updateData.city && updateData.city.trim() !== ""
|
||||||
|
? updateData.city.trim()
|
||||||
|
: null,
|
||||||
|
state:
|
||||||
|
updateData.state && updateData.state.trim() !== ""
|
||||||
|
? updateData.state.trim()
|
||||||
|
: null,
|
||||||
|
postalCode:
|
||||||
|
updateData.postalCode && updateData.postalCode.trim() !== ""
|
||||||
|
? updateData.postalCode.trim()
|
||||||
|
: null,
|
||||||
|
country:
|
||||||
|
updateData.country && updateData.country.trim() !== ""
|
||||||
|
? updateData.country.trim()
|
||||||
|
: null,
|
||||||
|
website:
|
||||||
|
updateData.website && updateData.website.trim() !== ""
|
||||||
|
? updateData.website.trim()
|
||||||
|
: null,
|
||||||
|
taxId:
|
||||||
|
updateData.taxId && updateData.taxId.trim() !== ""
|
||||||
|
? updateData.taxId.trim()
|
||||||
|
: null,
|
||||||
|
logoUrl:
|
||||||
|
updateData.logoUrl && updateData.logoUrl.trim() !== ""
|
||||||
|
? updateData.logoUrl.trim()
|
||||||
|
: null,
|
||||||
|
isDefault: updateData.isDefault ?? false,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const emailRouter = createTRPCRouter({
|
|||||||
// Create email content
|
// Create email content
|
||||||
const subject =
|
const subject =
|
||||||
input.customSubject ??
|
input.customSubject ??
|
||||||
`Invoice ${invoice.invoiceNumber} from ${invoice.business?.name ?? "Your Business"}`;
|
`Invoice ${invoice.invoiceNumber} from ${invoice.business ? `${invoice.business.name}${invoice.business.nickname ? ` (${invoice.business.nickname})` : ""}` : "Your Business"}`;
|
||||||
|
|
||||||
const userName =
|
const userName =
|
||||||
invoice.business?.emailFromName ??
|
invoice.business?.emailFromName ??
|
||||||
@@ -124,7 +124,11 @@ export const emailRouter = createTRPCRouter({
|
|||||||
// Use business's custom Resend setup
|
// Use business's custom Resend setup
|
||||||
resendInstance = new Resend(invoice.business.resendApiKey);
|
resendInstance = new Resend(invoice.business.resendApiKey);
|
||||||
const fromName =
|
const fromName =
|
||||||
invoice.business.emailFromName ?? invoice.business.name ?? userName;
|
invoice.business.emailFromName ??
|
||||||
|
(invoice.business.nickname
|
||||||
|
? `${invoice.business.name} (${invoice.business.nickname})`
|
||||||
|
: invoice.business.name) ??
|
||||||
|
userName;
|
||||||
fromEmail = `${fromName} <noreply@${invoice.business.resendDomain}>`;
|
fromEmail = `${fromName} <noreply@${invoice.business.resendDomain}>`;
|
||||||
} else if (env.RESEND_DOMAIN) {
|
} else if (env.RESEND_DOMAIN) {
|
||||||
// Use system Resend configuration
|
// Use system Resend configuration
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const ClientBackupSchema = z.object({
|
|||||||
|
|
||||||
const BusinessBackupSchema = z.object({
|
const BusinessBackupSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
nickname: z.string().optional(),
|
||||||
email: z.string().optional(),
|
email: z.string().optional(),
|
||||||
phone: z.string().optional(),
|
phone: z.string().optional(),
|
||||||
addressLine1: z.string().optional(),
|
addressLine1: z.string().optional(),
|
||||||
@@ -51,6 +52,7 @@ const InvoiceItemBackupSchema = z.object({
|
|||||||
const InvoiceBackupSchema = z.object({
|
const InvoiceBackupSchema = z.object({
|
||||||
invoiceNumber: z.string(),
|
invoiceNumber: z.string(),
|
||||||
businessName: z.string().optional(),
|
businessName: z.string().optional(),
|
||||||
|
businessNickname: z.string().optional(),
|
||||||
clientName: z.string(),
|
clientName: z.string(),
|
||||||
issueDate: z.string().transform((str) => new Date(str)),
|
issueDate: z.string().transform((str) => new Date(str)),
|
||||||
dueDate: z.string().transform((str) => new Date(str)),
|
dueDate: z.string().transform((str) => new Date(str)),
|
||||||
@@ -205,6 +207,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
nickname: true,
|
||||||
email: true,
|
email: true,
|
||||||
phone: true,
|
phone: true,
|
||||||
addressLine1: true,
|
addressLine1: true,
|
||||||
@@ -232,6 +235,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
business: {
|
business: {
|
||||||
columns: {
|
columns: {
|
||||||
name: true,
|
name: true,
|
||||||
|
nickname: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
@@ -269,6 +273,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
})),
|
})),
|
||||||
businesses: userBusinesses.map((business) => ({
|
businesses: userBusinesses.map((business) => ({
|
||||||
name: business.name,
|
name: business.name,
|
||||||
|
nickname: business.nickname ?? undefined,
|
||||||
email: business.email ?? undefined,
|
email: business.email ?? undefined,
|
||||||
phone: business.phone ?? undefined,
|
phone: business.phone ?? undefined,
|
||||||
addressLine1: business.addressLine1 ?? undefined,
|
addressLine1: business.addressLine1 ?? undefined,
|
||||||
@@ -285,6 +290,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
invoices: userInvoices.map((invoice) => ({
|
invoices: userInvoices.map((invoice) => ({
|
||||||
invoiceNumber: invoice.invoiceNumber,
|
invoiceNumber: invoice.invoiceNumber,
|
||||||
businessName: invoice.business?.name,
|
businessName: invoice.business?.name,
|
||||||
|
businessNickname: invoice.business?.nickname,
|
||||||
clientName: invoice.client.name,
|
clientName: invoice.client.name,
|
||||||
issueDate: invoice.issueDate,
|
issueDate: invoice.issueDate,
|
||||||
dueDate: invoice.dueDate,
|
dueDate: invoice.dueDate,
|
||||||
@@ -337,6 +343,9 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (newBusiness) {
|
if (newBusiness) {
|
||||||
businessIdMap.set(businessData.name, newBusiness.id);
|
businessIdMap.set(businessData.name, newBusiness.id);
|
||||||
|
if (businessData.nickname) {
|
||||||
|
businessIdMap.set(businessData.nickname, newBusiness.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,9 +356,14 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
throw new Error(`Client ${invoiceData.clientName} not found`);
|
throw new Error(`Client ${invoiceData.clientName} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const businessId = invoiceData.businessName
|
const businessId = invoiceData.businessNickname
|
||||||
? businessIdMap.get(invoiceData.businessName)
|
? (businessIdMap.get(invoiceData.businessNickname) ??
|
||||||
: null;
|
(invoiceData.businessName
|
||||||
|
? (businessIdMap.get(invoiceData.businessName) ?? null)
|
||||||
|
: null))
|
||||||
|
: invoiceData.businessName
|
||||||
|
? (businessIdMap.get(invoiceData.businessName) ?? null)
|
||||||
|
: null;
|
||||||
|
|
||||||
const [newInvoice] = await tx
|
const [newInvoice] = await tx
|
||||||
.insert(invoices)
|
.insert(invoices)
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ export const businesses = createTable(
|
|||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => crypto.randomUUID()),
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
name: d.varchar({ length: 255 }).notNull(),
|
name: d.varchar({ length: 255 }).notNull(),
|
||||||
|
nickname: d.varchar({ length: 255 }),
|
||||||
email: d.varchar({ length: 255 }),
|
email: d.varchar({ length: 255 }),
|
||||||
phone: d.varchar({ length: 50 }),
|
phone: d.varchar({ length: 50 }),
|
||||||
addressLine1: d.varchar({ length: 255 }),
|
addressLine1: d.varchar({ length: 255 }),
|
||||||
@@ -172,6 +173,7 @@ export const businesses = createTable(
|
|||||||
(t) => [
|
(t) => [
|
||||||
index("business_created_by_idx").on(t.createdById),
|
index("business_created_by_idx").on(t.createdById),
|
||||||
index("business_name_idx").on(t.name),
|
index("business_name_idx").on(t.name),
|
||||||
|
index("business_nickname_idx").on(t.nickname),
|
||||||
index("business_email_idx").on(t.email),
|
index("business_email_idx").on(t.email),
|
||||||
index("business_is_default_idx").on(t.isDefault),
|
index("business_is_default_idx").on(t.isDefault),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface InvoiceWithRelations extends Invoice {
|
|||||||
business: {
|
business: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
nickname: string | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
invoiceItems: Array<{
|
invoiceItems: Array<{
|
||||||
|
|||||||
Reference in New Issue
Block a user