Here's the Gurobi code to solve the optimization problem:

```python
import gurobipy as gp

# Create a new model
m = gp.Model("storage_optimization")

# Create variables
napkins = m.addVar(vtype=gp.GRB.INTEGER, name="napkins")
dish_soap = m.addVar(vtype=gp.GRB.INTEGER, name="dish_soap")
paper_towels = m.addVar(vtype=gp.GRB.INTEGER, name="paper_towels")
toilet_paper = m.addVar(vtype=gp.GRB.INTEGER, name="toilet_paper")

# Set objective function
m.setObjective(10.0 * napkins + 6.84 * dish_soap + 5.05 * paper_towels + 1.44 * toilet_paper, gp.GRB.MINIMIZE)

# Add constraints
m.addConstr(8 * napkins + 4 * dish_soap + 3 * paper_towels + 6 * toilet_paper <= 97, "total_storage")
m.addConstr(3 * paper_towels + 6 * toilet_paper >= 14, "paper_storage")
m.addConstr(4 * dish_soap + 3 * paper_towels >= 18, "soap_paper_storage")
m.addConstr(8 * napkins + 3 * paper_towels >= 24, "napkins_paper_storage")
m.addConstr(8 * napkins + 4 * dish_soap >= 11, "napkins_soap_storage")
m.addConstr(8 * napkins + 4 * dish_soap + 3 * paper_towels + 6 * toilet_paper >= 11, "all_items_storage")
m.addConstr(-7 * napkins + 8 * paper_towels >= 0, "napkins_paper_relation")
m.addConstr(8 * napkins + 4 * dish_soap <= 62, "napkins_soap_storage_upper")
m.addConstr(8 * napkins + 4 * dish_soap + 3 * paper_towels <= 73, "napkins_soap_paper_storage_upper")
m.addConstr(8 * napkins + 4 * dish_soap + 6 * toilet_paper <= 96, "napkins_soap_toiletpaper_storage_upper")
m.addConstr(4 * dish_soap + 3 * paper_towels + 6 * toilet_paper <= 59, "soap_paper_toiletpaper_storage_upper")


# Optimize model
m.optimize()

# Print results
if m.status == gp.GRB.OPTIMAL:
    print('Obj: %g' % m.objVal)
    print('napkins:', napkins.x)
    print('dish_soap:', dish_soap.x)
    print('paper_towels:', paper_towels.x)
    print('toilet_paper:', toilet_paper.x)
elif m.status == gp.GRB.INFEASIBLE:
    print("The model is infeasible.")
else:
    print("Optimization ended with status:", m.status)

```
